Zenn
Open54

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

kiyukakiyuka

なんとなくキャラクターのモデリングをIfcOpenShellでやりたくなりました。

え?Blenderでbonsai使え?そもそもなんでIFCでキャラクターモデリングするんだって?
いいんだよ!やりたいからやるんだよ!!

そういう試みをする作業ログ

kiyukakiyuka

エンティティの継承を引っさらってくるとこれだけある。

IfcGeometricRepresentationItem
  • IfcAnnotationFillArea
  • IfcBooleanResult
    • IfcBooleanClippingResult
  • IfcBoundingBox
  • IfcCartesianPointList
    • IfcCartesianPointList2D
    • IfcCartesianPointList3D
  • IfcCartesianTransformationOperator
    • IfcCartesianTransformationOperator2D
      • IfcCartesianTransformationOperator2DnonUniform
    • IfcCartesianTransformationOperator3D
      • IfcCartesianTransformationOperator3DnonUniform
  • IfcCsgPrimitive3D
    • IfcBlock
    • IfcRectangularPyramid
    • IfcRightCircularCone
    • IfcRightCircularCylinder
    • IfcSphere
  • IfcCurve
    • IfcBoundedCurve
      • IfcBSplineCurve
        • IfcBSplineCurveWithKnots
          • IfcRationalBSplineCurveWithKnots
      • IfcCompositeCurve
        • IfcCompositeCurveOnSurface
          • IfcBoundaryCurve
            • IfcOuterBoundaryCurve
        • IfcGradientCurve
        • IfcSegmentedReferenceCurve
      • IfcIndexedPolyCurve
      • IfcPolyline
      • IfcTrimmedCurve
    • IfcConic
      • IfcCircle
      • IfcEllipse
    • IfcLine
    • IfcOffsetCurve
      • IfcOffsetCurve2D
      • IfcOffsetCurve3D
      • IfcOffsetCurveByDistances
    • IfcPcurve
    • IfcPolynomialCurve
    • IfcSpiral
      • IfcClothoid
      • IfcCosineSpiral
      • IfcSecondOrderPolynomialSpiral
      • IfcSeventhOrderPolynomialSpiral
      • IfcSineSpiral
      • IfcThirdOrderPolynomialSpiral
    • IfcSurfaceCurve
      • IfcIntersectionCurve
      • IfcSeamCurve
  • 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
  • IfcShellBasedSurfaceModel
  • IfcSolidModel
    • IfcCsgSolid
    • IfcManifoldSolidBrep
      • IfcAdvancedBrep
        • IfcAdvancedBrepWithVoids
      • IfcFacetedBrep
        • IfcFacetedBrepWithVoids
    • IfcSectionedSolid
      • IfcSectionedSolidHorizontal
    • IfcSweptAreaSolid
      • IfcDirectrixCurveSweptAreaSolid
        • IfcFixedReferenceSweptAreaSolid
          • IfcDirectrixDerivedReferenceSweptAreaSolid
        • IfcSurfaceCurveSweptAreaSolid
      • IfcExtrudedAreaSolid
        • IfcExtrudedAreaSolidTapered
      • IfcRevolvedAreaSolid
        • IfcRevolvedAreaSolidTapered
    • IfcSweptDiskSolid
      • IfcSweptDiskSolidPolygonal
  • IfcSurface
    • IfcBoundedSurface
      • IfcBSplineSurface
        • IfcBSplineSurfaceWithKnots
          • IfcRationalBSplineSurfaceWithKnots
      • IfcCurveBoundedPlane
      • IfcCurveBoundedSurface
      • IfcRectangularTrimmedSurface
    • IfcElementarySurface
      • IfcCylindricalSurface
      • IfcPlane
      • IfcSphericalSurface
      • IfcToroidalSurface
    • IfcSectionedSurface
    • IfcSweptSurface
      • IfcSurfaceOfLinearExtrusion
      • IfcSurfaceOfRevolution
  • IfcTessellatedItem
    • IfcIndexedPolygonalFace
      • IfcIndexedPolygonalFaceWithVoids
    • IfcTessellatedFaceSet
      • IfcPolygonalFaceSet
      • IfcTriangulatedFaceSet
        • IfcTriangulatedIrregularNetwork
  • IfcTextLiteral
    • IfcTextLiteralWithExtent
  • IfcVector

多いな?

kiyukakiyuka

ちなみにこれで拾ってこれる

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)
kiyukakiyuka

順に見ますか。

ひとまずここまで見た感じ、平面と立体と処理が混ざってる感じなのね?

あと、IfcBoundingBoxと IfcCsgPrimitive3DのIfcBlock 同じでは...?

kiyukakiyuka

曲線だけでこれだけある。多いので一旦スキップ。

  • IfcCurve : ABSTRACT
    • IfcBoundedCurve : ABSTRACT
      • IfcBSplineCurve : ABSTRACT
        • ❄️ IfcBSplineCurveWithKnots ← 複雑だから最後にやろう
          • IfcRationalBSplineCurveWithKnots
      • ✅ IfcCompositeCurve
        • ✅ IfcCompositeCurveOnSurface
          • ✅ IfcBoundaryCurve
            • ✅ IfcOuterBoundaryCurve
        • ✅ IfcGradientCurve
        • ✅ IfcSegmentedReferenceCurve
      • ✅ IfcIndexedPolyCurve
      • ✅ IfcPolyline
      • ❄️ IfcTrimmedCurve ← 曲線のトリムなのでほかが終わってから
    • ✅ IfcConic : ABSTRACT
      • ✅ IfcCircle
      • ✅ IfcEllipse
    • ✅ IfcLine
    • IfcOffsetCurve : ABSTRACT
      • ✅ IfcOffsetCurve2D
      • ✅ IfcOffsetCurve3D
      • ❓ IfcOffsetCurveByDistances
    • ❄️ IfcPcurve ← サーフェス参照なのでサーフェスが終わってから
    • ✅ IfcPolynomialCurve
    • ✅ IfcSpiral : ABSTRACT
      • ✅ IfcClothoid
      • ✅ IfcCosineSpiral
      • ✅ IfcSecondOrderPolynomialSpiral
      • ✅ IfcSeventhOrderPolynomialSpiral
      • ✅ IfcSineSpiral
      • ✅ IfcThirdOrderPolynomialSpiral
    • ❄️ IfcSurfaceCurve ← サーフェス参照なのでサーフェスが終わってから
      • IfcIntersectionCurve
      • IfcSeamCurve
kiyukakiyuka
  • IfcDirection: 方向。他のエンティティと組み合わせて使う。
  • IfcFaceBasedSurfaceModel: 面で構成される立体。IfcTopologicalRepresentationItem 使って構成される。三角形メッシュ的なやつ。
  • IfcFillAreaStyleHatching: ハッチング。IfcAnnotationFillArea みたいなアノテーションと組み合わせる感じかな?
  • IfcFillAreaStyleTiles: タイルのスタイル。IfcFillAreaStyleHatchingのタイル版
  • IfcGeometricSet: 点、曲線、面の集合らしい。

スタイルが混じってるのなんかフシギダネ?

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

ライティングあるんだ?知らなかった。

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

さて、あとは IfcCurve, IfcSolidModel, IfcSurface, IfcTessellatedItem ですね?

kiyukakiyuka

重そうなのを調べる前にここまでを分類してみますか。
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

多分こんな感じだと思う。

kiyukakiyuka

IfcFaceBasedSurfaceModel、IfcShellBasedSurfaceModel、IfcTessellatedItem はどれも面の組み合わせで立体を形成しているんだけど、細かい違いがわからぬ。

ChatGPTに聞くと

  • IfcTessellatedItem: 三角形メッシュに一番近いイメージ
  • IfcFaceBasedSurfaceModel: バラバラの面をつなぎ合わせたもの
  • IfcShellBasedSurfaceModel: 展開図を折りたたんで立体にしたもの

みたいな回答が返ってきたけどうーん?
大きな違いは、IfcFaceBasedSurfaceModelIfcShellBasedSurfaceModelについては面に曲面も使用できることっぽい。

kiyukakiyuka

IfcCurve

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

IfcSurface

Bスプラインはマウスで引っ張ったりして自由に面をいじるときに使われるやつだと思う。一番自由度の高い曲面。
IfcElementarySurfaceは基本図形から作られる曲面で、IfcSweptSurfaceが曲線から作られる曲面っていう感じ。

kiyukakiyuka

IfcSolidModel

  • IfcCsgSolid: bool処理で作られる形状
  • IfcManifoldSolidBrep: 面で構成される形状。IfcFaceBasedSurfaceModel、IfcShellBasedSurfaceModel、IfcTessellatedItem のお仲間??でもソリッド?
  • IfcSectionedSolid: 曲面の押し出しで作られる形状
  • IfcSweptAreaSolid: 平面の押し出し形状。直線、曲線での押し出しがある。
  • IfcSweptDiskSolid: 円の押し出し形状

IfcCurve と IfcSurface と IfcSolidModel は実際形状作ってみないとイメージできないのあるなー。

kiyukakiyuka

立体を作るのはこれら。大きく2つに分類できると思う。

  • ソリッド系?
    • IfcSolidModel, IfcCsgPrimitive3D, IfcSectionedSpine
  • 面から構成するもの
    • IfcTessellatedItem, IfcFaceBasedSurfaceModel, IfcShellBasedSurfaceModel

IfcSolidModel から詳細見ようかな。IfcCsgPrimitive3Dはシンプル形状だし、IfcSectionedSpineIfcSolidModel の応用みたいな感じだし。

kiyukakiyuka

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

kiyukakiyuka

1からジオメトリ作るサンプルなかったっけと思いつつ探したらこっちにあった。
https://docs.ifcopenshell.org/ifcopenshell-python/code_examples.html#create-a-simple-model-from-scratch

この部分を変えて 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_representationIfcExtrudedAreaSolid を簡単に作れる関数。

kiyukakiyuka

さて IfcDirectrixCurveSweptAreaSolidIfcFixedReferenceSweptAreaSolid を見ていこうと思ったのだけれどもよくわからなかったのでサンプルを公式から拾ってくる。

https://github.com/buildingSMART/IFC4.3.x-sample-models/tree/main/models/alignment-geometries-and-linear-positioning/fixed-reference-swept-area-solid

表示するとこうなる

というか4.3のデータなら一旦スキップしてもいいなと思いつつ。もっと標準的なもの使おうぜ、とか思いつつ。でもまあいいか続けよう。

kiyukakiyuka

ちなみにモデルの表示はだいたいこれ。
https://qiita.com/kiyuka/items/ef4b197fa28d3a96d068

ただ ↑ のコードは 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)
kiyukakiyuka

データを見るとこうなってる。

SweptArea で面を構成して、Directrix の線で面を押し出し処理をする。みたいな感じ?
StartParam, EndParam が (300, 600) で表示と一致してるから、押出形状をこれで切り取ってる感じなのかな?

これ先に面と線を調べるべきか。そんな気がする。IfcDerivedProfileDef, IfcGradientCurve がどういう形状化把握してないと推測にしかならないし。

そんなわけで戻って線からやります。

kiyukakiyuka

線のいちばん簡単なやつをやろうということで、 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],
)

kiyukakiyuka

IfcIndexedPolyCurve 調べてたんだけど。そして何やっているのかはわかったんだけど。そして試しになにか描画してみようと思って IfcOpenShell で表示させようとしてもうまくいかないという。IfcOpenShellのジオメトリ取得のほうが非対応なのか作り方が間違っているのか...。

IfcIndexedPolyCurve自体は直線と円弧の組み合わせ。頂点のリストを指定して、それらを使って直線と円弧を作ることができる。
円弧は3点を指定する形。IfcArcIndexをみると円弧の3点の中間点はおおよそ始点終点の中間にある方がいいとのことで。

...書いてないけどどっちの円弧使うんだこれ?2パターンあるよね...?
いや、3点あるんだから確定しているよ何言っているのよ(-_-)

kiyukakiyuka

できた。計算誤差の問題だったっぽい?小さい値だと IfcOpenShell がうまく計算できないっぽい。

というかあれだ、確認したら単位ミリで作ってた。 0.1 とか小さい値で作ろうとしてたらそりゃそうなる (´・ω・`)

kiyukakiyuka

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

kiyukakiyuka

ちなみなコード

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つよつよ

kiyukakiyuka

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

kiyukakiyuka

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.)])

kiyukakiyuka

IfcLine は直線。位置とベクトルを指定する。IfcTrimmedCurve とセットで使用する前提っぽい。
ベクトルの大きさに対してトリムのパラメータで線分の長さを決める使い方をするような使い方。

正直 IfcPolyline でいいよねってなる。わかりやすいし。

あとこれも IfcOpenShell で形状取得が失敗する...

kiyukakiyuka

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) であれば、(52,52),(100+52,10052)(5\sqrt{2}, -5\sqrt{2}), \quad (100 + 5\sqrt{2}, 100 - 5\sqrt{2}) になる (計算あってるかこれ?)
  • 半径 100 の円であれば、半径110の円になる

多分こうなるはず。なんで多分って言っているかというとIfcOpenShellでうまくいかなくて検証できてないからです(´・ω・`)

kiyukakiyuka

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

ちなみにこれも IfcOpenShell で (ry

kiyukakiyuka

IfcOffsetCurveByDistances は頂点を指定してのオフセットなんだけど、これ一意に曲線定まるの?

オフセット自体は IfcPointByDistanceExpression を使って、曲線中のどの点がどれだけオフセットされるかで指定する。

たとえば、(0, 0), (0, 100), (100, 100) の線があったとして、距離0の位置 (0, 0) は offset1 、距離80 の位置(0, 80)は offset2, 距離150の位置(50, 100)はoffset3 、みたいなoffsetの指定の仕方をする。
でもこれ、距離100の位置(0, 100) はどこになるのが正解なの?この場合だと直線だから、もしかしたら定まるのかもしれないけど、これが曲線なら?答え求まらなくない?

kiyukakiyuka

メモ代わりにコード

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は、中間は線形補間してるって感じなんだろうか?

kiyukakiyuka

IfcPolynomialCurve は多項式で表す曲線。x, y, z, のパラメータの指定は数学チックなので気が向いたらやります。y=x2y = x^2 とかじゃなくて、x=fx(u)x = f_x(u), y=fy(u)y = f_y(u), z=fz(u)z = f_z(u) で表すやつ。

kiyukakiyuka

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,
    )
kiyukakiyuka

IfcCosineSpiral: あんまり良くわかってないけど、この曲率で作られる曲線らしい。

K(s)=K02(Constantcos(πsCosineTerm))K(s) = \frac{K_0}{2} \left(\text{Constant} - \cos \left( \pi \frac{s}{\text{CosineTerm}} \right) \right)

... K0{K_0} どこから出てきた?初期曲率指定なんてないけど? ss は長さ(初期位置からの距離)だから勝手に求まるけど K0{K_0} は何かしらで指定しないと求まらなくない?
→ 他の螺旋の数式見ると ConstantTerm が K0{K_0} っぽい。だけど、これだけ 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
kiyukakiyuka

IfcSineSpiral: これもよくわかってはいないけど数式。

K=K0(sLinearTerm12πsin(2πSineTerm))K = K_0 \left( s \cdot \text{LinearTerm} - \frac{1}{2\pi} \sin\left(\frac{2\pi}{\text{SineTerm}}\right) \right)

...ConstantTerm が数式に出てきてないんですが? K0K_0 かなとも思うけど、そうすると IfcCosineSpiral と書き方違うし...。

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
kiyukakiyuka

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
kiyukakiyuka

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,
    )
kiyukakiyuka

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,
    )
kiyukakiyuka

IfcCompositeCurve は、IfcSegment を結合していったものなので、先にIfcSegmentを見る。

IfcSegment自体はIfcCurveを指定して、そのまま使うなり、パラメータで線を切り取ったりする。IfcCompositeCurveで使用する前提なので、IfcSegment 同士の結合方法の指定もある。
IfcCurve を切り取って新しい線にするという意味では、IfcTrimmedCurve と挙動としては近いかもしれない。

kiyukakiyuka

IfcCompositeCurve は IfcSegment を結合して作られる曲線。
他、サブクラスが色々あるけど、ジオメトリの形状そのものに影響するものではなくて、単に情報提供目的しか意味がなさそう。

  • IfcCompositeCurve
    • IfcCompositeCurveOnSurface: 曲線が指定の Surface を通る
      • IfcBoundaryCurve:↑ と同じだが、作られる曲線が閉じている
        • IfcOuterBoundaryCurve: 指定 Surface の外周部分の曲線
    • IfcGradientCurve: BaseCurveに対して勾配を付けた曲線
    • IfcSegmentedReferenceCurve: こちらも BaseCurveに何やらだけどよくわからない

ジオメトリに意味もたせてるのはちょっと謎。インフラ要素用に新しく作られたエンティティっぽいけど。なんか4.3でIFCの設計思想変わったの?っていうくらい謎。

kiyukakiyuka

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 で全部の曲線表せるね。やろうと思えばだけど。

一応曲線は終わったということにして、次から曲面をやるよ。

kiyukakiyuka

曲面を、と思ったけど上の方で簡単には調べてた。 IfcSweptSurface が直線の押し出しのようなのでこれを見ます。

kiyukakiyuka

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

列挙してみると思ってたよりあった。

kiyukakiyuka
  • IfcDerivedProfileDef:ProfileDef の移動や回転、ミラーリングでの変形
  • IfcOpenCrossProfileDef:線の幅と角度のリストで線を定義。IfcCurveを使わず、直接曲線を定義している。(IfcPolylineでよくない?)
  • IfcParameterizedProfileDef:特定の形状をパラメータによって定義する。L字とかT字とか円とかの形状。

全部見てみる感じ、IfcProfileDef は IfcArbitraryClosedProfileDef と IfcParameterizedProfileDef がよく使われてそうな印象。

kiyukakiyuka

うーん...IfcSurfaceOfLinearExtrusion 試してみようと思ったけど IfcOpenShell が落ちる...。
BIMvisionでも表示できないからIFCの作成をミスっているのかもしれないけど、エラーを出すんじゃなくて落ちてるから対応してないっぽい。

まあ、そんなわけで? IfcSweptAreaSolid に戻りますか...いや、IfcDirectrixCurveSweptAreaSolid を調べてて、先に曲線見たほうがいいなってなってたんですよ?だからそこに戻ります。

kiyukakiyuka

IfcDirectrixCurveSweptAreaSolid も平面の押し出しだけど、断面の切り方の指定がある形みたい。
SweptArea の平面を、 StartParam~EndParam で切り取った Directrix の曲線に沿って押し出して、押し出した断面は FixedReferenceの方向を向くように切られているっていう感じ。

...fixed-reference-swept-area-solid.ifc のデータ表示したの見ても、始点と終点で断面方向違うじゃん。わからないからスキップしよう。

ログインするとコメントできます