シーングラフ
A シーングラフ Asposeの基礎的データモデルです。Python向けの3D FOSSです。ディスクから読み込まれたものでもメモリ上で構築されたものでも、すべての3Dファイルはツリーの形で表現されます Node オブジェクトは次をルートに持ちます Scene.root_node.。各ノードは子ノードと1つ以上の Entity オブジェクト(メッシュ、カメラ、ライト)を保持できます。シーングラフを理解することで、シーン内のすべてのオブジェクトのジオメトリ、マテリアル、空間変換に直接アクセスできるようになります。.
インストールとセットアップ
PyPI からライブラリをインストールします:
pip install aspose-3d-fossネイティブ拡張やコンパイラ、追加のシステムパッケージは必要ありません。完全なインストール手順については、以下をご覧ください。 インストールガイド.
概要: シーングラフの概念
Aspose.3D FOSS のシーングラフは、シンプルな包含階層に従います:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| オブジェクト | 役割 |
|---|---|
Scene | トップレベルのコンテナです。保持します root_node, asset_info, animation_clips,、そして sub_scenes. |
Node | 名前付きツリーノード。親を持ち、0 個以上の子ノード、0 個以上のエンティティ、そしてローカル Transform. |
Entity | ノードにアタッチされたジオメトリまたはシーンオブジェクト。一般的なエンティティタイプ: Mesh, Camera, Light. |
Transform | ノードのローカル空間における位置、回転、スケール。ワールド空間の結果は以下から取得されます global_transform. |
ステップバイステップ: プログラムでシーングラフを構築する
ステップ 1: シーンを作成する
新しい Scene は常に空の root_node:
from aspose.threed import Scene
scene = Scene()
print(scene.root_node.name) # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes)) # 0Scene はすべてのエントリーポイントです:ファイルの読み込み、ファイルの保存、アニメーションクリップの作成、ノードツリーへのアクセス。.
ステップ 2: 子ノードを作成する
使用する create_child_node(name) ツリーに名前付きノードを追加するには:
from aspose.threed import Scene
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
print(parent.name) # "parent"
print(child.parent_node.name) # "parent"
print(len(scene.root_node.child_nodes)) # 1あるいは、スタンドアロンの Node そして明示的にアタッチします:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)両方のアプローチは同じ結果を生成します。. create_child_node インライン構築では、より簡潔です。.
ステップ 3: メッシュエンティティを作成してアタッチする
A Mesh 頂点データを格納します(control_points)および面のトポロジー(polygons)。1つ作成し、ジオメトリを追加してから、ノードにアタッチします::
from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)
child.add_entity(mesh)
print(f"Mesh name: {mesh.name}")
print(f"Vertex count: {len(mesh.control_points)}")
print(f"Polygon count: {mesh.polygon_count}")Vector4(x, y, z, w) 同次座標を表します。使用してください w=1 通常の点位置に使用します。.
create_polygon(*indices) 頂点インデックスを受け取り、ポリゴンリストに1つの面を登録します。三角形の場合はインデックスを3つ、四角形の場合は4つ渡してください。.
ステップ 4: ノードの変換を設定する
各ノードは Transform ローカル空間で位置、向き、サイズを制御するもの::
from aspose.threed.utilities import Vector3, Quaternion
##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)
##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)
##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)変換は累積的です:子ノードのワールド空間位置は、子自身の変換とすべての祖先の変換の合成です。評価されたワールド空間の結果は、以下から読み取ります node.global_transform (イミュータブル、読み取り専用)。.
ステップ 5: シーングラフを再帰的に走査する
再帰的にたどってツリー全体を走査する node.child_nodes:
def traverse(node, depth=0):
indent = " " * depth
entity_type = type(node.entity).__name__ if node.entity else "None"
print(f"{indent}{node.name} [{entity_type}]")
for child in node.child_nodes:
traverse(child, depth + 1)
traverse(scene.root_node)上記で構築したシーンの例出力(ルートノードの名前は空文字列です)::
[None]
parent [None]
child [Mesh]ノードごとに複数のエンティティがあるシーンでは、イテレート node.entities 代わりに node.entity:
def traverse_full(node, depth=0):
indent = " " * depth
entity_names = [type(e).__name__ for e in node.entities] or ["None"]
print(f"{indent}{node.name} [{', '.join(entity_names)}]")
for child in node.child_nodes:
traverse_full(child, depth + 1)
traverse_full(scene.root_node)ステップ 6: シーンを保存する
ファイルパスを渡す scene.save().。フォーマットはファイル拡張子から推測されます::
scene.save("scene.gltf") # JSON glTF 2.0
scene.save("scene.glb") # Binary GLB container
scene.save("scene.obj") # Wavefront OBJ
scene.save("scene.stl") # STL形式固有のオプションについては、2番目の引数に save-options オブジェクトを渡してください::
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)ヒントとベストプラクティス
- すべてのノードに名前を付けます。. ノードに意味のある名前を付けることで、トラバーサルのデバッグが格段に容易になり、エクスポートされたファイルで名前が保持されることが保証されます。.
- ノードあたり1つのメッシュ。. エンティティとノードを1対1に保つことで、変換と衝突クエリがシンプルになります。.
- 使用する
create_child_node手動でのアタッチより。. 親参照を自動的に設定し、エラーが起きにくくなります。. - 読む
global_transform階層を構築した後に。. すべての上位変換が設定された後でのみ、ワールド空間の結果は安定します。. - 走査中にツリーを変更しないでください。. イテレーション中に子ノードを追加または削除することは
child_nodes予測不可能な動作を引き起こします。まずノードを収集し、その後に変更してください。. - 制御点は
Vector4, ではなくVector3. 常に渡すw=1通常の頂点位置の場合;w=0方向ベクトルを表す(点ではない)。. mesh.control_pointsコピーを返す。. このcontrol_pointsプロパティは返すlist(self._control_points)— 返されたリストに要素を追加してもメッシュは変更されません。常に追加するのはmesh._control_points直接行ってくださいジオメトリをプログラムで構築する際は。これは既知のライブラリの制限であり、公開されたミューテーション API はまだ存在しません。.
一般的な問題
| 課題 | 解決策 |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | でガードする if node.entity is not None エンティティのプロパティにアクセスする前に。エンティティを持たないノードは entity = None. |
設定にもかかわらず、メッシュが原点に表示されます translation | transform.translation ローカルオフセットが適用されます。親ノード自体が単位変換でない場合、ワールド位置が異なる可能性があります。確認してください global_transform. |
子ノードが欠落しています scene.save() / リロード | 一部のフォーマット(OBJ)は階層をフラット化します。完全なノードツリーを保持するには glTF または COLLADA を使用してください。. |
polygon_count は 0 です mesh.create_polygon(...) | 渡された頂点インデックスが正しいことを確認してください create_polygon が範囲内であること(0 まで len(control_points) - 1). |
Node.get_child(name) 返します None | 名前は大文字と小文字を区別します。作成時に使用された正確な名前文字列を確認してください。. |
| トラバーサルが予期しない順序でノードを訪問します | child_nodes 挿入順(順序)で子要素を返します(順序 add_child_node / create_child_node が呼び出されました)。. |