Pythonで3Dシーングラフをトラバースする方法

Pythonで3Dシーングラフをトラバースする方法

Aspose.3D FOSS のシーン グラフはツリーです Node オブジェクトは scene.root_node. すべての3Dファイルは、OBJ、glTF、STL、COLLADA、または3MFからロードされたかどうかにかかわらず、同じツリー構造を生成します。ツリーの走査方法を知っていれば、ジオメトリを検査したり、ポリゴン数を数えたり、タイプでオブジェクトをフィルタリングしたり、複雑なシーンの特定部分を処理したりできます。.

ステップバイステップ ガイド

ステップ 1: インストールとインポート

PyPI から Aspose.3D FOSS をインストールします:

pip install aspose-3d-foss

必要なクラスをインポートします:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

すべての公開クラスは aspose.threed またはそのサブパッケージ(aspose.threed.entities, aspose.threed.utilities).


ステップ 2: ファイルからシーンをロードする

static を使用してください Scene.from_file() メソッドで任意のサポートされたフォーマットを開きます。フォーマットはファイル拡張子から自動的に検出されます:

scene = Scene.from_file("model.gltf")

明示的なロードオプションを指定して開くこともできます:

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

options = ObjLoadOptions()
options.enable_materials = True

scene = Scene()
scene.open("model.obj", options)

ロード後、, scene.root_node はツリーのルートです。インポートされたすべてのノードはこのノードの子または子孫です。.


ステップ 3: 再帰的走査関数を書く

最もシンプルな走査は深さ優先順にすべてのノードを訪問します:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

def traverse(node, depth=0):
    prefix = "  " * depth
    entity_name = type(node.entity).__name__ if node.entity else "-"
    print(f"{prefix}[{entity_name}] {node.name}")
    for child in node.child_nodes:
        traverse(child, depth + 1)

scene = Scene.from_file("model.gltf")
traverse(scene.root_node)

出力例:

[-]
  [-] Armature
    [Mesh] Body
    [Mesh] Eyes
  [-] Ground
    [Mesh] Plane

<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”

<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>

注意:: ルートノードの名前は空です(""), したがって最初の行が表示されます [-] 後に名前が続きません。.

node.child_nodes 挿入順(インポーターまたはユーザーが追加した順)で子要素を返します。.


ステップ 4: 各ノードのエンティティプロパティにアクセスする

使用 node.entity ノードに添付された最初のエンティティを取得するには、または node.entities すべてを反復処理します。フォーマット固有のプロパティにアクセスする前に型を確認してください::

from aspose.threed import Scene
from aspose.threed.entities import Mesh

def print_entity_details(node, depth=0):
    indent = "  " * depth
    for entity in node.entities:
        if isinstance(entity, Mesh):
            mesh = entity
            print(f"{indent}Mesh '{node.name}':")
            print(f"{indent}  vertices  : {len(mesh.control_points)}")
            print(f"{indent}  polygons  : {mesh.polygon_count}")
            print(f"{indent}  cast_shadows   : {mesh.cast_shadows}")
            print(f"{indent}  receive_shadows: {mesh.receive_shadows}")
    for child in node.child_nodes:
        print_entity_details(child, depth + 1)

scene = Scene.from_file("model.gltf")
print_entity_details(scene.root_node)

node.transform.translation, node.transform.rotation,、および node.transform.scaling ローカル空間の変換を提供します。. node.global_transform 評価されたワールド空間の結果を返します( global_transform.scale ワールド空間のスケール用)。.


ステップ5: エンティティタイプでノードをフィルタリング

特定のエンティティタイプのみに対して操作するには、次を追加します isinstance トラバーサル内のガード:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

def find_mesh_nodes(node, results=None):
    if results is None:
        results = []
    for entity in node.entities:
        if isinstance(entity, Mesh):
            results.append(node)
            break   # One match per node is enough
    for child in node.child_nodes:
        find_mesh_nodes(child, results)
    return results

scene = Scene.from_file("model.gltf")
mesh_nodes = find_mesh_nodes(scene.root_node)
print(f"Found {len(mesh_nodes)} mesh node(s)")
for n in mesh_nodes:
    print(f"  {n.name}")

このパターンはバルク操作に便利です。すべてのメッシュノードに変換を適用したり、マテリアルを置き換えたり、サブツリーをエクスポートしたりできます。.


ステップ6: すべてのメッシュを収集し、頂点数を出力する

単一パスでメッシュ統計を集計します:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

def collect_meshes(node, results=None):
    if results is None:
        results = []
    if isinstance(node.entity, Mesh):
        results.append((node.name, node.entity))
    for child in node.child_nodes:
        collect_meshes(child, results)
    return results

scene = Scene.from_file("model.gltf")
meshes = collect_meshes(scene.root_node)

print(f"Total meshes: {len(meshes)}")
total_verts = 0
total_polys = 0

for name, mesh in meshes:
    verts = len(mesh.control_points)
    polys = mesh.polygon_count
    total_verts += verts
    total_polys += polys
    print(f"  {name}: {verts} vertices, {polys} polygons")

print(f"Scene totals: {total_verts} vertices, {total_polys} polygons")

よくある問題

問題解決策
AttributeError: 'NoneType' object has no attribute 'polygons'次でガード if node.entity is not None または isinstance(node.entity, Mesh) メッシュプロパティにアクセスする前に。エンティティがないノードは返します None.
トラバーサルが早期に停止する再帰が対象に到達することを確認してください node.child_nodes.。もし反復をのみ行う場合 scene.root_node.child_nodes (再帰的に行わない場合)、すべての子孫を見逃します。.
収集結果からメッシュが欠落していますファイル形式が階層構造を保持しているか確認してください。OBJ はすべてのジオメトリを単一のノードレベルにフラット化します。深い階層が必要な場合は glTF または COLLADA を使用してください。.
node.entity 最初のエンティティのみを返します使用する node.entities (完全なリスト) は、ノードが複数のエンティティを持つ場合に使用します。. node.entity は省略形です node.entities[0] の場合 entities が空でない場合。.
collect_meshes ロードされた STL に対して 0 件の結果を返しますSTL ファイルは通常、1 つのメッシュエンティティが直接下にある単一のフラットノードを生成します root_node.。確認してください root_node.child_nodes[0].entity 直接。.
ノード名は空文字列です一部のフォーマット(バイナリ STL、いくつかの OBJ ファイル)ではオブジェクト名が保存されません。ノードは空の name 文字列になります。代わりにインデックスを識別に使用してください。.

よくある質問

シーングラフはどれくらい深くなれますか??

ハードリミットはありません。Python のデフォルト再帰制限(1000 フレーム)は再帰的トラバーサル関数に適用されます。非常に深い階層の場合は、再帰を明示的なスタックに変換してください:

from collections import deque
from aspose.threed import Scene

scene = Scene.from_file("deep.gltf")
queue = deque([(scene.root_node, 0)])

while queue:
    node, depth = queue.popleft()
    print("  " * depth + node.name)
    for child in node.child_nodes:
        queue.append((child, depth + 1))

トラバーサル中にツリーを変更できますか??

ノードを追加したり削除したりしないでください child_nodes それをイテレートしている間に。最初のパスで変更するノードを収集し、2 回目のパスで変更を適用してください。.

名前で特定のノードを見つけるにはどうすればいいですか??

使用する node.get_child(name) 名前で直接の子を検索します。深い検索を行う場合は、名前フィルタを使ってツリーを走査してください::

def find_by_name(root, name):
    if root.name == name:
        return root
    for child in root.child_nodes:
        result = find_by_name(child, name)
        if result:
            return result
    return None

target = find_by_name(scene.root_node, "Wheel_FL")

返すか node.entity 常に Mesh を返しますか??

いいえ。ノードは任意のエンティティタイプを保持できます:: Mesh, Camera, Light,、またはカスタムエンティティ。常に次で確認してください isinstance(node.entity, Mesh) メッシュ固有のプロパティを使用する前に。.

ノードのワールド空間位置を取得するにはどうすればいいですか??

読み取り node.global_transform.translation. これは、すべての先祖変換を考慮したワールド空間で評価された位置です。読み取り専用です; 変更 node.transform.translation ノードの位置を変更するには。.

シーン内の総ポリゴン数を、トラバーサルを書かずにカウントできますか??

API を直接通じてはできません; そのようなものはありません scene.total_polygon_count プロパティです。使用してください collect_meshes そして合計します mesh.polygon_count 結果全体にわたって、ステップ 6 に示すように合計してください。.

 日本語