Cara Menelusuri Graf Adegan 3D dalam Python

Cara Menelusuri Graf Adegan 3D dalam Python

Graf adegan dalam Aspose.3D FOSS adalah pokok bagi Node objek yang berakar pada scene.root_node. Setiap fail 3D, sama ada dimuatkan daripada OBJ, glTF, STL, COLLADA, atau 3MF, menghasilkan struktur pokok yang sama. Mengetahui cara menelusurnya membolehkan anda memeriksa geometri, mengira poligon, menapis objek mengikut jenis, dan memproses bahagian tertentu dalam adegan yang kompleks.

Panduan Langkah demi Langkah

Langkah 1: Pasang dan Import

Pasang Aspose.3D FOSS dari PyPI:

pip install aspose-3d-foss

Import kelas yang anda perlukan:

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

Semua kelas awam berada di bawah aspose.threed atau sub-pakejnya (aspose.threed.entities, aspose.threed.utilities).


Langkah 2: Muatkan Adegan dari Fail

Gunakan statik Scene.from_file() kaedah untuk membuka mana-mana format yang disokong. Format dikesan secara automatik daripada sambungan fail:

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

Anda juga boleh membuka dengan pilihan muat yang eksplisit:

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

options = ObjLoadOptions()
options.enable_materials = True

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

Selepas memuatkan, scene.root_node adalah akar pokok. Semua nod yang diimport adalah anak atau keturunan nod ini.


Langkah 3: Tulis Fungsi Traversal Rekursif

Traversal paling mudah melawat setiap nod dalam susunan depth-first:

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)

Contoh output:

[-] RootNode
  [-] 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 mengembalikan anak dalam susunan sisipan (susunan di mana pengimport atau pengguna menambahnya).


Langkah 4: Akses Sifat Entiti pada Setiap Nod

Gunakan node.entity untuk mendapatkan entiti pertama yang dilampirkan pada nod, atau node.entities untuk mengulangi semua. Periksa jenisnya sebelum mengakses sifat khusus format:

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, dan node.transform.scaling memberi transformasi ruang setempat. node.global_transform memberi hasil ruang dunia yang dinilai (dengan global_transform.scale untuk skala ruang-dunia).


Langkah 5: Tapis Nod mengikut Jenis Entiti

Untuk beroperasi hanya pada jenis entiti tertentu, tambahkan satu isinstance penjaga di dalam penelusuran:

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}")

Corak ini berguna untuk operasi pukal: menerapkan transformasi pada setiap nod mesh, menggantikan bahan, atau mengeksport sub-pokok.


Langkah 6: Kumpulkan Semua Mesh dan Cetak Bilangan Vertex

Kumpulkan statistik mesh dalam satu laluan:

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

Isu Umum

IsuResolusi
AttributeError: 'NoneType' object has no attribute 'polygons'Lindungi dengan if node.entity is not None atau isinstance(node.entity, Mesh) sebelum mengakses sifat mesh. Nod tanpa entiti mengembalikan None.
Penelusuran berhenti lebih awalPastikan rekursi mencapai ke dalam node.child_nodes. Jika anda hanya mengulangi scene.root_node.child_nodes (tidak secara rekursif), anda terlepas semua keturunan.
Mesh tiada dalam hasil yang dikumpulkanPastikan format fail mengekalkan hierarki. OBJ meratakan semua geometri ke dalam satu tahap nod. Gunakan glTF atau COLLADA untuk hierarki yang mendalam.
node.entity mengembalikan hanya entiti pertamaGunakan node.entities (senarai penuh) apabila satu nod membawa pelbagai entiti. node.entity adalah singkatan untuk node.entities[0] bila entities tidak kosong.
collect_meshes mengembalikan 0 keputusan untuk STL yang dimuatkanFail STL biasanya menghasilkan satu nod rata dengan satu entiti mesh secara langsung di bawah root_node. Periksa root_node.child_nodes[0].entity secara langsung.
Nama nod adalah rentetan kosongBeberapa format (STL binari, beberapa fail OBJ) tidak menyimpan nama objek. Nod akan mempunyai rentetan kosong name rentetan; gunakan indeks untuk mengenal pasti sebaliknya.

Soalan Lazim

Sejauh mana kedalaman graf adegan?

Tiada had keras. Had rekursi lalai Python (1000 bingkai) dikenakan pada fungsi penelusuran rekursif. Untuk hierarki yang sangat dalam, tukar rekursi kepada timbunan eksplisit:

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

Bolehkah saya mengubah pokok semasa menelusurnya?

Jangan menambah atau membuang nod daripada child_nodes semasa mengiterasinya. Kumpulkan nod yang hendak diubah dalam satu laluan pertama, kemudian terapkan perubahan dalam laluan kedua.

Bagaimana saya mencari nod tertentu mengikut nama?

Gunakan node.get_child(name) untuk mencari anak langsung mengikut nama. Untuk carian mendalam, lalui pokok dengan penapis nama:

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

Adakah node.entity sentiasa mengembalikan Mesh?

Tidak. Sebuah nod boleh memegang sebarang jenis entiti: Mesh, Camera, Light, atau entiti tersuai. Sentiasa semak dengan isinstance(node.entity, Mesh) sebelum menggunakan sifat khusus mesh.

Bagaimana saya mendapatkan kedudukan ruang dunia bagi sebuah nod?

Baca node.global_transform.translation. Ini adalah kedudukan yang dinilai dalam ruang dunia, mengambil kira semua transformasi nenek moyang. Ia hanya-baca; ubah node.transform.translation untuk menempat semula nod itu.

Bolehkah saya mengira jumlah poligon dalam satu adegan tanpa menulis penelusuran?

Tidak secara langsung melalui API; tiada scene.total_polygon_count harta. Gunakan collect_meshes dan jumlahkan mesh.polygon_count di seluruh keputusan, seperti yang ditunjukkan dalam Langkah 6.

 Bahasa Melayu