IfcOpenShellでピカチュウを作りたい

なんとなくキャラクターのモデリングをIfcOpenShellでやりたくなりました。
え?Blenderでbonsai使え?そもそもなんでIFCでキャラクターモデリングするんだって?
いいんだよ!やりたいからやるんだよ!!
そういう試みをする作業ログ

まずは手札を整えよう。あれだよ、ポケポケでモンスターボールとかオーキドとかみんな入れるでしょ?手札って大事なんです。
そんなわけでまずはここからジオメトリ作成に使えるものを見繕っていきます。

IfcTopologicalRepresentationItem もあるけど、こっちは三角形メッシュ的にジオメトリ作るのっぽいので面白くないから今回はなしで。

エンティティの継承を引っさらってくるとこれだけある。
IfcGeometricRepresentationItem
- IfcAnnotationFillArea
- IfcBooleanResult
- IfcBooleanClippingResult
- IfcBoundingBox
- IfcCartesianPointList
- IfcCartesianPointList2D
- IfcCartesianPointList3D
- IfcCartesianTransformationOperator
- IfcCartesianTransformationOperator2D
- IfcCartesianTransformationOperator2DnonUniform
- IfcCartesianTransformationOperator3D
- IfcCartesianTransformationOperator3DnonUniform
- IfcCartesianTransformationOperator2D
- IfcCsgPrimitive3D
- IfcBlock
- IfcRectangularPyramid
- IfcRightCircularCone
- IfcRightCircularCylinder
- IfcSphere
- IfcCurve
- IfcBoundedCurve
- IfcBSplineCurve
- IfcBSplineCurveWithKnots
- IfcRationalBSplineCurveWithKnots
- IfcBSplineCurveWithKnots
- IfcCompositeCurve
- IfcCompositeCurveOnSurface
- IfcBoundaryCurve
- IfcOuterBoundaryCurve
- IfcBoundaryCurve
- IfcGradientCurve
- IfcSegmentedReferenceCurve
- IfcCompositeCurveOnSurface
- IfcIndexedPolyCurve
- IfcPolyline
- IfcTrimmedCurve
- IfcBSplineCurve
- IfcConic
- IfcCircle
- IfcEllipse
- IfcLine
- IfcOffsetCurve
- IfcOffsetCurve2D
- IfcOffsetCurve3D
- IfcOffsetCurveByDistances
- IfcPcurve
- IfcPolynomialCurve
- IfcSpiral
- IfcClothoid
- IfcCosineSpiral
- IfcSecondOrderPolynomialSpiral
- IfcSeventhOrderPolynomialSpiral
- IfcSineSpiral
- IfcThirdOrderPolynomialSpiral
- IfcSurfaceCurve
- IfcIntersectionCurve
- IfcSeamCurve
- IfcBoundedCurve
- IfcDirection
- IfcFaceBasedSurfaceModel
- IfcFillAreaStyleHatching
- IfcFillAreaStyleTiles
- IfcGeometricSet
- IfcGeometricCurveSet
- IfcHalfSpaceSolid
- IfcBoxedHalfSpace
- IfcPolygonalBoundedHalfSpace
- IfcLightSource
- IfcLightSourceAmbient
- IfcLightSourceDirectional
- IfcLightSourceGoniometric
- IfcLightSourcePositional
- IfcLightSourceSpot
- IfcPlacement
- IfcAxis1Placement
- IfcAxis2Placement2D
- IfcAxis2Placement3D
- IfcAxis2PlacementLinear
- IfcPlanarExtent
- IfcPlanarBox
- IfcPoint
- IfcCartesianPoint
- IfcPointByDistanceExpression
- IfcPointOnCurve
- IfcPointOnSurface
- IfcSectionedSpine
- IfcSegment
- IfcCompositeCurveSegment
- IfcReparametrisedCompositeCurveSegment
- IfcCurveSegment
- IfcCompositeCurveSegment
- IfcShellBasedSurfaceModel
- IfcSolidModel
- IfcCsgSolid
- IfcManifoldSolidBrep
- IfcAdvancedBrep
- IfcAdvancedBrepWithVoids
- IfcFacetedBrep
- IfcFacetedBrepWithVoids
- IfcAdvancedBrep
- IfcSectionedSolid
- IfcSectionedSolidHorizontal
- IfcSweptAreaSolid
- IfcDirectrixCurveSweptAreaSolid
- IfcFixedReferenceSweptAreaSolid
- IfcDirectrixDerivedReferenceSweptAreaSolid
- IfcSurfaceCurveSweptAreaSolid
- IfcFixedReferenceSweptAreaSolid
- IfcExtrudedAreaSolid
- IfcExtrudedAreaSolidTapered
- IfcRevolvedAreaSolid
- IfcRevolvedAreaSolidTapered
- IfcDirectrixCurveSweptAreaSolid
- IfcSweptDiskSolid
- IfcSweptDiskSolidPolygonal
- IfcSurface
- IfcBoundedSurface
- IfcBSplineSurface
- IfcBSplineSurfaceWithKnots
- IfcRationalBSplineSurfaceWithKnots
- IfcBSplineSurfaceWithKnots
- IfcCurveBoundedPlane
- IfcCurveBoundedSurface
- IfcRectangularTrimmedSurface
- IfcBSplineSurface
- IfcElementarySurface
- IfcCylindricalSurface
- IfcPlane
- IfcSphericalSurface
- IfcToroidalSurface
- IfcSectionedSurface
- IfcSweptSurface
- IfcSurfaceOfLinearExtrusion
- IfcSurfaceOfRevolution
- IfcBoundedSurface
- IfcTessellatedItem
- IfcIndexedPolygonalFace
- IfcIndexedPolygonalFaceWithVoids
- IfcTessellatedFaceSet
- IfcPolygonalFaceSet
- IfcTriangulatedFaceSet
- IfcTriangulatedIrregularNetwork
- IfcIndexedPolygonalFace
- IfcTextLiteral
- IfcTextLiteralWithExtent
- IfcVector
多いな?

ちなみにこれで拾ってこれる
schema = ifcopenshell.schema_by_name("IFC4X3")
declaration = schema.declaration_by_name("IfcGeometricRepresentationItem")
def print_subtypes(declaration, indent=0):
print(" " * indent + "- " + declaration.name())
for subtype in declaration.subtypes():
print_subtypes(subtype, indent + 1)
print_subtypes(declaration)

順に見ますか。
- IfcAnnotationFillArea: アノテーションで使用するものっぽい。型抜きみたいな?
- IfcBooleanResult: 2つの形状のbool処理。 A∪B, A-B, A∩B。
- IfcBoundingBox: バウンディングボックス
- IfcCartesianPointList: 頂点のリスト。2Dと3Dがある。2Dは平面構築に使うんだと思うけど、3Dってどう使うんだ?
- IfcCartesianTransformationOperator: 形状の変換。transform matrix 的なことができる。
- IfcCsgPrimitive3D: 基本図形。立方体、四角錐、円錐、円柱、球体がある。
ひとまずここまで見た感じ、平面と立体と処理が混ざってる感じなのね?
あと、IfcBoundingBoxと IfcCsgPrimitive3DのIfcBlock 同じでは...?

曲線だけでこれだけある。多いので一旦スキップ。
-
IfcCurve : ABSTRACT
- IfcBoundedCurve : ABSTRACT
- IfcBSplineCurve : ABSTRACT
- ❄️ IfcBSplineCurveWithKnots ← 複雑だから最後にやろう
- IfcRationalBSplineCurveWithKnots
- ❄️ IfcBSplineCurveWithKnots ← 複雑だから最後にやろう
- ✅ IfcCompositeCurve
- ✅ IfcCompositeCurveOnSurface
- ✅ IfcBoundaryCurve
- ✅ IfcOuterBoundaryCurve
- ✅ IfcBoundaryCurve
- ✅ IfcGradientCurve
- ✅ IfcSegmentedReferenceCurve
- ✅ IfcCompositeCurveOnSurface
- ✅ IfcIndexedPolyCurve
- ✅ IfcPolyline
- ❄️ IfcTrimmedCurve ← 曲線のトリムなのでほかが終わってから
- IfcBSplineCurve : ABSTRACT
- ✅ IfcConic : ABSTRACT
- ✅ IfcCircle
- ✅ IfcEllipse
- ✅ IfcLine
- IfcOffsetCurve : ABSTRACT
- ✅ IfcOffsetCurve2D
- ✅ IfcOffsetCurve3D
- ❓ IfcOffsetCurveByDistances
- ❄️ IfcPcurve ← サーフェス参照なのでサーフェスが終わってから
- ✅ IfcPolynomialCurve
- ✅ IfcSpiral : ABSTRACT
- ✅ IfcClothoid
- ✅ IfcCosineSpiral
- ✅ IfcSecondOrderPolynomialSpiral
- ✅ IfcSeventhOrderPolynomialSpiral
- ✅ IfcSineSpiral
- ✅ IfcThirdOrderPolynomialSpiral
- ❄️ IfcSurfaceCurve ← サーフェス参照なのでサーフェスが終わってから
- IfcIntersectionCurve
- IfcSeamCurve
- IfcBoundedCurve : ABSTRACT

- IfcDirection: 方向。他のエンティティと組み合わせて使う。
- IfcFaceBasedSurfaceModel: 面で構成される立体。IfcTopologicalRepresentationItem 使って構成される。三角形メッシュ的なやつ。
- IfcFillAreaStyleHatching: ハッチング。IfcAnnotationFillArea みたいなアノテーションと組み合わせる感じかな?
- IfcFillAreaStyleTiles: タイルのスタイル。IfcFillAreaStyleHatchingのタイル版
- IfcGeometricSet: 点、曲線、面の集合らしい。
スタイルが混じってるのなんかフシギダネ?

- IfcHalfSpaceSolid: イメージ的には、Babylon.jsの clipPlanes だと思う。これ単体ではなくてbooleanと組み合わせて使う。ボックスや多角形の指定もある。
- IfcLightSource: ライティング。環境光、DirectionalLight などがある。
- IfcPlacement: 位置
- IfcPlanarExtent: 2D領域の範囲指定。IfcFillAreaStyle とかといっしょに使う感じ?
- IfcPoint: 点。直接3点指定する以外にも、曲線や曲面上の点を指定するということもできる。
- IfcSectionedSpine: 面を繋いで構成できる立体。ドキュメントの図を見たほうがいい。こういうの好き。
ライティングあるんだ?知らなかった。

- IfcSegment: 線分。これ単独っていうよりも、IfcCurve内 で使用される要素っぽい?
- IfcShellBasedSurfaceModel: Shell(面)で構成されている形状。IfcFaceBasedSurfaceModel に近い。(というか違いがいまいちわからない...構成方法が違うだけ?)
- IfcTextLiteral: テキスト
- IfcVector: ベクトル
さて、あとは IfcCurve, IfcSolidModel, IfcSurface, IfcTessellatedItem ですね?

重そうなのを調べる前にここまでを分類してみますか。
IfcGeometricRepresentationItem はジオメトリに関するいろいろっていう感じ。3D形状と関係なさそうなものも混ざっている。
- 形状要素
- 位置:
IfcPlacement
、方向:IfcDirection
、ベクトル:IfcVector
- 点:
IfcPoint
、線:IfcCurve
,IfcSegment
- 面:
IfcCartesianPointList
,IfcSurface
- 立体:
IfcCsgPrimitive3D
,IfcFaceBasedSurfaceModel
,IfcSectionedSpine
,IfcShellBasedSurfaceModel
,IfcSolidModel
,IfcTessellatedItem
- 形状の集合:
IfcGeometricSet
- 位置:
- 形状の処理
- 論理演算:
IfcBooleanResult
,IfcHalfSpaceSolid
- 変形:
IfcCartesianTransformationOperator
- 論理演算:
- その他
- アノテーション:
IfcAnnotationFillArea
、 - (アノテーションの?)スタイル:
IfcFillAreaStyleHatching
,IfcFillAreaStyleTiles
- 範囲指定:
IfcBoundingBox
,IfcPlanarExtent
- ライティング:
IfcLightSource
- テキスト:
IfcTextLiteral
- アノテーション:
多分こんな感じだと思う。

- IfcTessellatedItem: 三角形メッシュ的なやつ。
IfcFaceBasedSurfaceModel、IfcShellBasedSurfaceModel、IfcTessellatedItem はどれも面の組み合わせで立体を形成しているんだけど、細かい違いがわからぬ。
ChatGPTに聞くと
-
IfcTessellatedItem
: 三角形メッシュに一番近いイメージ -
IfcFaceBasedSurfaceModel
: バラバラの面をつなぎ合わせたもの -
IfcShellBasedSurfaceModel
: 展開図を折りたたんで立体にしたもの
みたいな回答が返ってきたけどうーん?
大きな違いは、IfcFaceBasedSurfaceModel
、IfcShellBasedSurfaceModel
については面に曲面も使用できることっぽい。

IfcCurve
- IfcBoundedCurve: 線分。スプライン曲線、曲線の組み合わせ、点のつなぎ合わせなど
- IfcConic: 円、楕円
- IfcLine: 直線(半直線?)
- IfcOffsetCurve: 他の曲線にoffsetした曲線。同じ曲線を位置を変えて定義したいときに使う?ObjectPlacement でもできるけど、これを使ってもできる、みたいな感じなんだと思う。
- IfcPcurve: 曲面上に沿った曲線。UV座標系で指定する必要があるみたいなのでちょっと難解。
- IfcPolynomialCurve: 多項式による曲線
- IfcSpiral: 数式による曲線。クロソイド、サイン、コサイン、2,3,7次多項式螺旋曲線。
- IfcSurfaceCurve: 曲線と曲面の関連付け。「曲線Xは曲面Yに沿っているよ」という関連付けをすることができる。後づけIfcPcurve的な感じ。これ自体は曲線の定義ではない。

IfcSurface
- IfcBoundedSurface: Bスプライン曲面、曲面の切り取り。
- IfcElementarySurface: 円柱、平面、球面、トーラス曲面
- IfcSectionedSurface: 複数曲面のつなぎ合わせ
- IfcSweptSurface: 曲線の押し出し
Bスプラインはマウスで引っ張ったりして自由に面をいじるときに使われるやつだと思う。一番自由度の高い曲面。
IfcElementarySurfaceは基本図形から作られる曲面で、IfcSweptSurfaceが曲線から作られる曲面っていう感じ。

IfcSolidModel
- IfcCsgSolid: bool処理で作られる形状
- IfcManifoldSolidBrep: 面で構成される形状。IfcFaceBasedSurfaceModel、IfcShellBasedSurfaceModel、IfcTessellatedItem のお仲間??でもソリッド?
- IfcSectionedSolid: 曲面の押し出しで作られる形状
- IfcSweptAreaSolid: 平面の押し出し形状。直線、曲線での押し出しがある。
- IfcSweptDiskSolid: 円の押し出し形状
IfcCurve と IfcSurface と IfcSolidModel は実際形状作ってみないとイメージできないのあるなー。

立体を作るのはこれら。大きく2つに分類できると思う。
- ソリッド系?
-
IfcSolidModel
,IfcCsgPrimitive3D
,IfcSectionedSpine
-
- 面から構成するもの
-
IfcTessellatedItem
,IfcFaceBasedSurfaceModel
,IfcShellBasedSurfaceModel
-
IfcSolidModel
から詳細見ようかな。IfcCsgPrimitive3D
はシンプル形状だし、IfcSectionedSpine
は IfcSolidModel
の応用みたいな感じだし。

IfcCsgSolid:
これ自体はあんまり意味なさそう?IfcBooleanResult か IfcCsgPrimitive3D のどちらかっていうだけっぽい。一応IfcBooleanResultが2D形状もOKっぽいから、明示的に3D形状と示すくらいしか意味はなさそう。

次は簡単そうなので、IfcSweptAreaSolid。平面の面の押し出し形状。
- IfcExtrudedAreaSolid: 一番シンプルなやつ。平面とそれを押し出す方向と押し出す深さで形状が作られる。
あとこれらがあるんだけど、ドキュメント見てもいまいちわかんないな?作るか。

そういうわけでジオメトリを作ります。

1からジオメトリ作るサンプルなかったっけと思いつつ探したらこっちにあった。
この部分を変えて representation
を作れば自由に形状を作れる。
# Add a new wall-like body geometry, 5 meters long, 3 meters high, and 200mm thick
representation = ifcopenshell.api.geometry.add_wall_representation(model, context=body, length=5, height=3, thickness=0.2)
ちなみに add_wall_representation
は IfcExtrudedAreaSolid
を簡単に作れる関数。

さて IfcDirectrixCurveSweptAreaSolid
の IfcFixedReferenceSweptAreaSolid
を見ていこうと思ったのだけれどもよくわからなかったのでサンプルを公式から拾ってくる。
表示するとこうなる
というか4.3のデータなら一旦スキップしてもいいなと思いつつ。もっと標準的なもの使おうぜ、とか思いつつ。でもまあいいか続けよう。

ちなみにモデルの表示はだいたいこれ。
ただ ↑ のコードは ifcopenshell 0.7.0 のときのコードなので 0.8.0 用に若干修正とかしている。
表示のコード
def create_mesh(vertices, triangles):
mesh = go.Mesh3d(
x=vertices[:, 0],
y=vertices[:, 1],
z=vertices[:, 2],
i=triangles[:, 0],
j=triangles[:, 1],
k=triangles[:, 2],
flatshading=True,
lighting=dict(ambient=1, diffuse=0),
)
return mesh
def create_wireframe(vertices, triangles):
edges = set()
for triangle in triangles:
for i in range(3):
start = triangle[i]
end = triangle[(i + 1) % 3]
edges.add(tuple(sorted([start, end])))
edges_list = np.array([vertices[list(edge)] for edge in edges])
edges_x = []
edges_y = []
edges_z = []
for edge in edges_list:
start, end = edge
edges_x.extend([start[0], end[0], None])
edges_y.extend([start[1], end[1], None])
edges_z.extend([start[2], end[2], None])
wireframe = go.Scatter3d(
x=edges_x, y=edges_y, z=edges_z, mode="lines", line=dict(color="black", width=1)
)
return wireframe
def write_plotly(model, outpath, has_wireframe=False):
settings = ifcopenshell.geom.settings()
settings.set(settings.USE_WORLD_COORDS, True)
meshes = []
for element in model.by_type("IfcProduct"):
if element.Representation is None:
continue
for representation in element.Representation.Representations:
try:
shape = ifcopenshell.geom.create_shape(
settings, element, representation
)
except Exception as e:
print(e, representation)
continue
# 頂点と面の情報を取得
verts = shape.geometry.verts
faces = shape.geometry.faces
verts = np.array(verts).reshape(-1, 3)
faces = np.array(faces).reshape(-1, 3)
# メッシュの作成
mesh = create_mesh(verts, faces)
meshes.append(mesh)
# ワイヤフレームの作成
if has_wireframe:
wireframe = create_wireframe(verts, faces)
meshes.append(wireframe)
fig = go.Figure(data=meshes)
fig.update_layout(scene_aspectmode="data")
fig.write_html(outpath, include_plotlyjs="cdn", full_html=True)

データを見るとこうなってる。
SweptArea
で面を構成して、Directrix
の線で面を押し出し処理をする。みたいな感じ?
StartParam
, EndParam
が (300, 600) で表示と一致してるから、押出形状をこれで切り取ってる感じなのかな?
これ先に面と線を調べるべきか。そんな気がする。IfcDerivedProfileDef
, IfcGradientCurve
がどういう形状化把握してないと推測にしかならないし。
そんなわけで戻って線からやります。

よし、上から順に見よう。最初はこれか、
IfcCurve - IfcBoundedCurve - IfcBSplineCurve - IfcBSplineCurveWithKnots
とか思ったら、これ4.3か。一旦スキップしよう。簡単なのからやりたいよね?

線のいちばん簡単なやつをやろうということで、 IfcPolyline です。
点をつなぐだけだから、Points
を指定するだけ。
そんなわけで特に意味はないけど螺旋を作ってみる。
def generate_spiral_3d(num_points, a=0.1, b=0.2, z_step=0.1):
theta = np.linspace(0, 4 * np.pi, num_points)
r = a + b * theta
x = r * np.cos(theta)
y = r * np.sin(theta)
z = z_step * theta
return np.stack([x, y, z], axis=1)
points = generate_spiral_3d(100)
curve = model.create_entity(
"IfcPolyline",
Points=[
model.create_entity("IfcCartesianPoint", Coordinates=p.tolist()) for p in points
],
)
representation = model.create_entity(
"IfcShapeRepresentation",
ContextOfItems=body,
RepresentationIdentifier="Axis",
RepresentationType="Curve",
Items=[curve],
)

IfcIndexedPolyCurve 調べてたんだけど。そして何やっているのかはわかったんだけど。そして試しになにか描画してみようと思って IfcOpenShell で表示させようとしてもうまくいかないという。IfcOpenShellのジオメトリ取得のほうが非対応なのか作り方が間違っているのか...。
IfcIndexedPolyCurve自体は直線と円弧の組み合わせ。頂点のリストを指定して、それらを使って直線と円弧を作ることができる。
円弧は3点を指定する形。IfcArcIndexをみると円弧の3点の中間点はおおよそ始点終点の中間にある方がいいとのことで。
...書いてないけどどっちの円弧使うんだこれ?2パターンあるよね...?
いや、3点あるんだから確定しているよ何言っているのよ(-_-)

これのIfcIndexedPolyCurve部分は表示できた。

できた。計算誤差の問題だったっぽい?小さい値だと IfcOpenShell がうまく計算できないっぽい。
というかあれだ、確認したら単位ミリで作ってた。 0.1 とか小さい値で作ろうとしてたらそりゃそうなる (´・ω・`)

そんなわけで IfcIndexedPolyCurve でコッホ曲線。ランダムに曲線を混ぜてみたり。

ちなみなコード
code
コッホ曲線
import random
def generate_koch_curve(points, depth, prob_curve=0.3):
"""
コッホ曲線を生成する関数(確率的に突起を曲線にする)
:param points: 始点と終点の座標 [(x1, y1), (x2, y2)]
:param depth: 再帰の深さ
:param prob_curve: 確率的に突起を曲線にする確率 (0.0〜1.0)
:return: コッホ曲線の座標リスト
"""
if depth == 0:
# 再帰の終了条件: そのまま始点と終点を返す
return [{"points": points, "type": "line"}]
# return points
# 始点と終点の取得
(x1, y1, z1), (x2, y2, z2) = points
# 新しい点を計算
dx = (x2 - x1) / 3.0
dy = (y2 - y1) / 3.0
dz = (z2 - z1) / 3.0
# 分割点を計算
p1 = (x1 + dx, y1 + dy, z1 + dz) # 始点から1/3の位置
p2 = (x1 + 2 * dx, y1 + 2 * dy, z1 + 2 * dz) # 始点から2/3の位置
# 中間点を作る
mid_x = (p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * (3**0.5) / 2
mid_y = (p1[1] + p2[1]) / 2 + (p1[0] - p2[0]) * (3**0.5) / 2
mid_z = (p1[2] + p2[2]) / 2
p3 = (mid_x, mid_y, mid_z)
# 再帰的に次の点を計算
left = generate_koch_curve([points[0], p1], depth - 1, prob_curve)
end = generate_koch_curve([p2, points[1]], depth - 1, prob_curve)
# 確率的に曲線にする
if random.random() < prob_curve / depth:
return [*left, {"points": [p1, p3, p2], "type": "arc"}, *end]
else:
middle = generate_koch_curve([p1, p3], depth - 1, prob_curve)
right = generate_koch_curve([p3, p2], depth - 1, prob_curve)
return [*left, *middle, *right, *end]
start_point = (0, 0, 0)
end_point = (10, 0, 10)
initial_points = [start_point, end_point]
depth = 6
prob_curve = 0.3
koch_curve_points = generate_koch_curve(initial_points, depth, prob_curve)
IfcIndexedPolyCurve
segments = []
points = []
for pt in koch_curve_points:
if pt["type"] == "line":
segments.append((len(points) + 1, len(points) + 2))
points.extend(pt["points"][:-1])
else:
segments.append((len(points) + 1, len(points) + 2, len(points) + 3))
points.extend(pt["points"][:-1])
points.append(pt["points"][-1])
ifc_segments = []
for segment in segments:
if len(segment) == 2:
ifc_segments.append(model.createIfcLineIndex(segment))
else:
ifc_segments.append(model.createIfcArcIndex(segment))
ifc_points = model.createIfcCartesianPointList3D(np.array(points).tolist())
curve = model.create_entity(
"IfcIndexedPolyCurve",
Points=ifc_points,
Segments=ifc_segments,
)
ChatGPTつよつよ

IfcTrimmedCurve は、指定の曲線をトリムした曲線。
BasisCurve の種類によって処理が変わるっぽいので、一旦スキップで。

IfcCompositeCurve は複数の曲線のつなぎ合わせだから、遊ぶのは他のが終わってからやろう。

IfcConic の2つ。
- IfcCircle はシンプルな円。円の中央座標と半径を指定する。
- IfcEllipse は楕円。楕円の中央と半長径と半短径を指定する。
なんでかIfcOpenShellで形状表示できないのだけどなんでなん?
IfcTrimmedCurve にすると取得できる不思議。
builder = ifcopenshell.util.shape_builder.ShapeBuilder(model)
curve = builder.create_ellipse_curve(5, 3, trim_points=[(5., 0.), (0., 3.)])

IfcLine は直線。位置とベクトルを指定する。IfcTrimmedCurve とセットで使用する前提っぽい。
ベクトルの大きさに対してトリムのパラメータで線分の長さを決める使い方をするような使い方。
正直 IfcPolyline でいいよねってなる。わかりやすいし。
あとこれも IfcOpenShell で形状取得が失敗する...

IfcOffsetCurve2D は指定の曲線に対して2D方向にoffsetした線を作る。
offsetの方向は Distance 一つだけの指定になっていて、線の接線方向に対して垂直方向のoffsetを指定する形になっている。
たとえば、Distance=10 とすると
- (0, 0), (100, 0) のx軸に沿った直線であれば、(0, 10), (100, 10) になる
- (0, 0), (0, 100) のy軸に沿った直線であれば、(10, 0), (10, 100) になる
- (0, 0), (100, 100) であれば、
になる (計算あってるかこれ?) - 半径 100 の円であれば、半径110の円になる
多分こうなるはず。なんで多分って言っているかというとIfcOpenShellでうまくいかなくて検証できてないからです(´・ω・`)

IfcOffsetCurve3D は、曲線全体に offsetの分だけ移動する形。名前だけ見ると、IfcOffsetCurve2D と IfcOffsetCurve3D で単純に次元が違うだけのように見えるけど、そもそも処理そのものが全く異なる。
指定のベクトル方向に、指定の距離だけ、曲線全体を移動するような処理が行われる。offsetと言われて一般的に想像する処理を行うのはこっちの方。
ちなみにこれも IfcOpenShell で (ry

IfcOffsetCurveByDistances は頂点を指定してのオフセットなんだけど、これ一意に曲線定まるの?
オフセット自体は IfcPointByDistanceExpression を使って、曲線中のどの点がどれだけオフセットされるかで指定する。
たとえば、(0, 0), (0, 100), (100, 100) の線があったとして、距離0の位置 (0, 0) は offset1 、距離80 の位置(0, 80)は offset2, 距離150の位置(50, 100)はoffset3 、みたいなoffsetの指定の仕方をする。
でもこれ、距離100の位置(0, 100) はどこになるのが正解なの?この場合だと直線だから、もしかしたら定まるのかもしれないけど、これが曲線なら?答え求まらなくない?

メモ代わりにコード
code
points = [
np.array([0.0, 0.0]),
np.array([0.0, 100.0]),
np.array([100.0, 100.0]),
]
base_curve = model.create_entity(
"IfcPolyline",
Points=[
model.create_entity("IfcCartesianPoint", Coordinates=p.tolist()) for p in points
],
)
offset_values = [
model.create_entity(
"IfcPointByDistanceExpression",
DistanceAlong=model.create_entity("IfcLengthMeasure", 0.0),
OffsetLateral=10.0,
OffsetVertical=10.0,
OffsetLongitudinal=0.0,
BasisCurve=base_curve,
),
model.create_entity(
"IfcPointByDistanceExpression",
DistanceAlong=model.create_entity("IfcLengthMeasure", 80.0),
OffsetLateral=30.0,
OffsetVertical=5.0,
OffsetLongitudinal=0.0,
BasisCurve=base_curve,
),
model.create_entity(
"IfcPointByDistanceExpression",
DistanceAlong=model.create_entity("IfcLengthMeasure", 150.0),
OffsetLateral=20.0,
OffsetVertical=0.0,
OffsetLongitudinal=0.0,
BasisCurve=base_curve,
),
]
curve = model.create_entity(
"IfcOffsetCurveByDistances",
BasisCurve=base_curve,
OffsetValues=offset_values,
)
IfcOpenShellだとこうなる。offsetなしが直線の方。offsetありがブニャット(ΦωΦ)してる方。
これだと曲がってるけど、offset 後も (0, 100), (100, 100) は直線一本だとしても間違ってないと思うんだよね。だって中間がどうなっている必要があるかの指定がないんだし。
IfcOpenShellは、中間は線形補間してるって感じなんだろうか?

IfcPolynomialCurve は多項式で表す曲線。x, y, z, のパラメータの指定は数学チックなので気が向いたらやります。

IfcClothoid :クロソイド曲線。長さとパラメータを指定する。ただ、長さ無限の線なのでTrimするなり、Segmentにするなりで線分にする必要がある。
コード
builder = ifcopenshell.util.shape_builder.ShapeBuilder(model)
for c in [1, 2, 5, 10, 100]:
curve = model.create_entity(
"IfcClothoid",
Position=model.createIfcAxis2Placement2D(
model.createIfcCartesianPoint([0., 0.]),
model.createIfcDirection([1., 0.])
),
ClothoidConstant=c,
)
curve = model.create_entity(
"IfcCurveSegment",
Transition="CONTSAMEGRADIENTSAMECURVATURE",
Placement=builder.create_axis2_placement_3d(),
SegmentStart=model.createIfcLengthMeasure(0.),
SegmentLength=model.createIfcLengthMeasure(10.),
ParentCurve=curve,
)
curve = model.create_entity(
"IfcCompositeCurve",
Segments=[curve],
SelfIntersect=False,
)

IfcCosineSpiral: あんまり良くわかってないけど、この曲率で作られる曲線らしい。
...
→ 他の螺旋の数式見ると ConstantTerm が Constant
が別にあって結局謎。
プロットするとこんな感じになる。
code
data = [
dict(cosine_term=1., constant_term=1., position=(0., 0., 0.)),
dict(cosine_term=10., constant_term=1., position=(0., 0., 1.)),
dict(cosine_term=1., constant_term=10., position=(0., 0., 2.)),
dict(cosine_term=10., constant_term=10., position=(0., 0., 3.)),
]
for d in data:
curve = model.create_entity(
"IfcCosineSpiral",
CosineTerm=d["cosine_term"],
ConstantTerm=d["constant_term"],
)
curve = model.create_entity(
"IfcCurveSegment",
Transition="CONTSAMEGRADIENTSAMECURVATURE",
Placement=builder.create_axis2_placement_3d(d["position"]),
SegmentStart=model.createIfcLengthMeasure(0.),
SegmentLength=model.createIfcLengthMeasure(100.),
ParentCurve=curve,
)
curve = model.create_entity(
"IfcCompositeCurve",
Segments=[curve],
SelfIntersect=False,
)
d["curve"] = curve

IfcSineSpiral: これもよくわかってはいないけど数式。
...ConstantTerm
が数式に出てきてないんですが?
code
data = [
dict(sine_term=1., linear_term=1., constant_term=1.,),
dict(sine_term=10., linear_term=1., constant_term=1.,),
dict(sine_term=1., linear_term=10., constant_term=1.,),
dict(sine_term=10., linear_term=10., constant_term=1.,),
dict(sine_term=1., linear_term=1., constant_term=10.,),
dict(sine_term=10., linear_term=1., constant_term=10.,),
dict(sine_term=1., linear_term=10., constant_term=10.,),
dict(sine_term=10., linear_term=10., constant_term=10.,),
]
for i, d in enumerate(data):
curve = model.create_entity(
"IfcSineSpiral",
SineTerm=d["sine_term"],
LinearTerm=d["linear_term"],
ConstantTerm=d["constant_term"],
)
curve = model.create_entity(
"IfcCurveSegment",
Transition="CONTSAMEGRADIENTSAMECURVATURE",
Placement=builder.create_axis2_placement_3d((0., 0., i * 2.), ),
SegmentStart=model.createIfcLengthMeasure(0.),
SegmentLength=model.createIfcLengthMeasure(100.),
ParentCurve=curve,
)
curve = model.create_entity(
"IfcCompositeCurve",
Segments=[curve],
SelfIntersect=False,
)
d["curve"] = curve

IfcSecondOrderPolynomialSpiral:二次式の螺旋とかなんとか。
code
c1 = 10.
c2 = 100.
data = [
dict(quadratic_term=c1, linear_term=c1, constant_term=c1,),
dict(quadratic_term=c2, linear_term=c1, constant_term=c1,),
dict(quadratic_term=c1, linear_term=c2, constant_term=c1,),
dict(quadratic_term=c2, linear_term=c2, constant_term=c1,),
dict(quadratic_term=c1, linear_term=c1, constant_term=c2,),
dict(quadratic_term=c2, linear_term=c1, constant_term=c2,),
dict(quadratic_term=c1, linear_term=c2, constant_term=c2,),
dict(quadratic_term=c2, linear_term=c2, constant_term=c2,),
]
for i, d in enumerate(data):
curve = model.create_entity(
"IfcSecondOrderPolynomialSpiral",
QuadraticTerm=d["quadratic_term"],
LinearTerm=d["linear_term"],
ConstantTerm=d["constant_term"],
)
curve = model.create_entity(
"IfcCurveSegment",
Transition="CONTSAMEGRADIENTSAMECURVATURE",
Placement=builder.create_axis2_placement_3d((0., 0., i * 2.), ),
SegmentStart=model.createIfcLengthMeasure(0.),
SegmentLength=model.createIfcLengthMeasure(100.),
ParentCurve=curve,
)
curve = model.create_entity(
"IfcCompositeCurve",
Segments=[curve],
SelfIntersect=False,
)
d["curve"] = curve

IfcThirdOrderPolynomialSpiral:3次式の螺旋とかなんとか。
code
変数多いのでランダムでいくつかプロット
np.random.seed(42)
for i, d in enumerate(np.random.choice((10., 100., 1000.), (10, 4))):
curve = model.create_entity(
"IfcThirdOrderPolynomialSpiral",
builder.create_axis2_placement_3d((0., 0., i * 2.)),
*d,
)
curve = model.create_entity(
"IfcCurveSegment",
Transition="CONTSAMEGRADIENTSAMECURVATURE",
Placement=builder.create_axis2_placement_3d((0., 0., i * 2.), ),
SegmentStart=model.createIfcLengthMeasure(0.),
SegmentLength=model.createIfcLengthMeasure(100.),
ParentCurve=curve,
)
curve = model.create_entity(
"IfcCompositeCurve",
Segments=[curve],
SelfIntersect=False,
)

IfcSeventhOrderPolynomialSpiral:7次式の螺旋とかなんとか。
code
適当にパラメータ設定するとぐちゃぐちゃになるので、いい感じに調整してランダムに。
np.random.seed(42)
data = (np.random.sample((10, 8)) * 10000 + 1000)
for i in range(8):
data[:, i] = data[:, i] * 0.5 ** (i + 1)
for i, d in enumerate(data):
curve = model.create_entity(
"IfcSeventhOrderPolynomialSpiral",
builder.create_axis2_placement_3d((0., 0., i * 2.)),
*d,
)
curve = model.create_entity(
"IfcCurveSegment",
Transition="CONTSAMEGRADIENTSAMECURVATURE",
Placement=builder.create_axis2_placement_3d((0., 0., i * 2.), ),
SegmentStart=model.createIfcLengthMeasure(0.),
SegmentLength=model.createIfcLengthMeasure(100.),
ParentCurve=curve,
)
curve = model.create_entity(
"IfcCompositeCurve",
Segments=[curve],
SelfIntersect=False,
)

IfcCompositeCurve は、IfcSegment を結合していったものなので、先にIfcSegmentを見る。
IfcSegment自体はIfcCurveを指定して、そのまま使うなり、パラメータで線を切り取ったりする。IfcCompositeCurveで使用する前提なので、IfcSegment 同士の結合方法の指定もある。
IfcCurve を切り取って新しい線にするという意味では、IfcTrimmedCurve と挙動としては近いかもしれない。
- IfcSegment: ABSTRACT
-
IfcCompositeCurveSegment: IfcCurve を指定してそのまま使う
- IfcReparametrisedCompositeCurveSegment : ↑ に長さのパラメータが追加されている。IfcLineなどを指定する場合はこっちかな。
- IfcCurveSegment: こちらは線の始点と終点を指定する形
-
IfcCompositeCurveSegment: IfcCurve を指定してそのまま使う

IfcCompositeCurve は IfcSegment を結合して作られる曲線。
他、サブクラスが色々あるけど、ジオメトリの形状そのものに影響するものではなくて、単に情報提供目的しか意味がなさそう。
- IfcCompositeCurve
- IfcCompositeCurveOnSurface: 曲線が指定の Surface を通る
- IfcBoundaryCurve:↑ と同じだが、作られる曲線が閉じている
- IfcOuterBoundaryCurve: 指定 Surface の外周部分の曲線
- IfcBoundaryCurve:↑ と同じだが、作られる曲線が閉じている
- IfcGradientCurve: BaseCurveに対して勾配を付けた曲線
- IfcSegmentedReferenceCurve: こちらも BaseCurveに何やらだけどよくわからない
- IfcCompositeCurveOnSurface: 曲線が指定の Surface を通る
ジオメトリに意味もたせてるのはちょっと謎。インフラ要素用に新しく作られたエンティティっぽいけど。なんか4.3でIFCの設計思想変わったの?っていうくらい謎。

IfcCurve 全部終わり。いやスキップしたのもあるけど一旦ここまででまとめる。
IfcCurveには、長さ無限の曲線(IfcLine、IfcClothoidなど)と有限の曲線(IfcCompositeCurve、IfcPolylineなど)がある。長さ無限の曲線については IfcTrimmedCurve か IfcSegment で曲線を切り取って線分にする必要がある。
分類していくと多分こんな感じかな?若干無理やり分類してるのもあるけど。
- 基本形
- 一般的な線分:IfcPolyline、IfcIndexedPolyCurve
- 直線:IfcLine、円:IfcConic
- 参照系:他の曲線や曲面を参照して曲線を作る
- サーフェス参照:IfcPcurve、IfcSurfaceCurve
- 他の曲線:IfcOffsetCurve、IfcTrimmedCurve
- 数式系
- IfcPolynomialCurve、IfcSpiral、IfcBSplineCurve
- 線分の合成
- IfcCompositeCurve
こうしてみると IfcPolyline、IfcIndexedPolyCurve、IfcCompositeCurve の3つでほぼ完結しそう。
なんなら IfcIndexedPolyCurve で全部の曲線表せるね。やろうと思えばだけど。
一応曲線は終わったということにして、次から曲面をやるよ。

曲面を、と思ったけど上の方で簡単には調べてた。 IfcSweptSurface が直線の押し出しのようなのでこれを見ます。
- IfcSweptSurface
- IfcSurfaceOfLinearExtrusion: 単純な押し出し。指定の方向に指定の長さ押し出す。
- IfcSurfaceOfRevolution: 回転の押し出し。回転軸を指定して一周回った形状。

IfcSweptSurface の形状を作ろうと思ったんだけど、IfcProfileDef を調べる必要がありそう。押し出し元の形状定義はすべてこれで定義されるっぽい。
IfcProfileDef 自体には AREA, CURVE の指定のみがあって、サブクラスがいくつかある形。
- IfcProfileDef
- IfcArbitraryClosedProfileDef
- IfcArbitraryProfileDefWithVoids
- IfcArbitraryOpenProfileDef
- IfcCenterLineProfileDef
- IfcCompositeProfileDef
- IfcDerivedProfileDef
- IfcMirroredProfileDef
- IfcOpenCrossProfileDef
- IfcParameterizedProfileDef : ABSTRACT
- IfcAsymmetricIShapeProfileDef
- IfcCShapeProfileDef
- IfcCircleProfileDef
- IfcCircleHollowProfileDef
- IfcEllipseProfileDef
- IfcIShapeProfileDef
- IfcLShapeProfileDef
- IfcRectangleProfileDef
- IfcRectangleHollowProfileDef
- IfcRoundedRectangleProfileDef
- IfcTShapeProfileDef
- IfcTrapeziumProfileDef
- IfcUShapeProfileDef
- IfcZShapeProfileDef
- IfcArbitraryClosedProfileDef
列挙してみると思ってたよりあった。

-
IfcArbitraryClosedProfileDef: 面の外周の曲線で面を定義。曲線は閉じている。
- IfcArbitraryProfileDefWithVoids: ↑ に穴を開けたもの
-
IfcArbitraryOpenProfileDef: ClosedProfileDefと違って開いた曲線。そのため押し出しすると面になる。
- IfcCenterLineProfileDef:開いた曲線に幅を持たせて面を定義。↑と違って押し出しで立体。
- IfcCompositeProfileDef:ProfileDefの合成

- IfcDerivedProfileDef:ProfileDef の移動や回転、ミラーリングでの変形
- IfcOpenCrossProfileDef:線の幅と角度のリストで線を定義。IfcCurveを使わず、直接曲線を定義している。(IfcPolylineでよくない?)
- IfcParameterizedProfileDef:特定の形状をパラメータによって定義する。L字とかT字とか円とかの形状。
全部見てみる感じ、IfcProfileDef は IfcArbitraryClosedProfileDef と IfcParameterizedProfileDef がよく使われてそうな印象。

うーん...IfcSurfaceOfLinearExtrusion 試してみようと思ったけど IfcOpenShell が落ちる...。
BIMvisionでも表示できないからIFCの作成をミスっているのかもしれないけど、エラーを出すんじゃなくて落ちてるから対応してないっぽい。
まあ、そんなわけで? IfcSweptAreaSolid に戻りますか...いや、IfcDirectrixCurveSweptAreaSolid を調べてて、先に曲線見たほうがいいなってなってたんですよ?だからそこに戻ります。

IfcDirectrixCurveSweptAreaSolid も平面の押し出しだけど、断面の切り方の指定がある形みたい。
SweptArea
の平面を、 StartParam
~EndParam
で切り取った Directrix
の曲線に沿って押し出して、押し出した断面は FixedReference
の方向を向くように切られているっていう感じ。
...fixed-reference-swept-area-solid.ifc のデータ表示したの見ても、始点と終点で断面方向違うじゃん。わからないからスキップしよう。