Cara Menelusuri Graf Adegan 3D di Python

Cara Menelusuri Graf Adegan 3D di Python

Grafis adegan dalam Aspose.3D FOSS adalah sebuah pohon dari Node objek yang berakar di scene.root_node. Setiap file 3D, baik yang dimuat dari OBJ, glTF, STL, COLLADA, atau 3MF, menghasilkan struktur pohon yang sama. Mengetahui cara menelusurnya memungkinkan Anda memeriksa geometri, menghitung poligon, menyaring objek berdasarkan tipe, dan memproses bagian tertentu dari adegan yang kompleks.

Panduan Langkah-demi-Langkah

Langkah 1: Instal dan Impor

Instal Aspose.3D FOSS dari PyPI:

pip install aspose-3d-foss

Impor kelas yang Anda butuhkan:

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

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


Langkah 2: Muat Adegan dari File

Gunakan static Scene.from_file() metode untuk membuka format yang didukung apa pun. Format dideteksi secara otomatis dari ekstensi file:

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

Anda juga dapat membuka dengan opsi pemuatan 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)

Setelah memuat, scene.root_node adalah akar dari pohon. Semua node yang diimpor adalah anak atau keturunan dari node ini.


Langkah 3: Tulis Fungsi Traversal Rekursif

Traversal paling sederhana mengunjungi setiap node dalam urutan 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:

[-]
  [-] 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>

Catatan: Node akar memiliki nama kosong (""), sehingga baris pertama menampilkan [-] tanpa nama yang mengikuti.

node.child_nodes mengembalikan anak dalam urutan penyisipan (urutan di mana pengimpor atau pengguna menambahkannya).


Langkah 4: Akses Properti Entitas pada Setiap Node

Gunakan node.entity untuk mendapatkan entitas pertama yang terlampir pada sebuah node, atau node.entities untuk mengiterasi semuanya. Periksa tipe sebelum mengakses properti 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 memberikan transformasi ruang-lokal. node.global_transform memberikan hasil ruang-dunia yang dievaluasi (dengan global_transform.scale untuk skala ruang-dunia).


Langkah 5: Filter Node berdasarkan Tipe Entitas

Untuk beroperasi hanya pada tipe entitas tertentu, tambahkan sebuah isinstance guard di dalam traversal:

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

Pola ini berguna untuk operasi massal: menerapkan transformasi ke setiap node mesh, mengganti material, atau mengekspor sub‑pohon.


Langkah 6: Kumpulkan Semua Mesh dan Cetak Jumlah Vertex

Gabungkan statistik mesh dalam satu kali lintasan:

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

Masalah Umum

MasalahSolusi
AttributeError: 'NoneType' object has no attribute 'polygons'Guard dengan if node.entity is not None atau isinstance(node.entity, Mesh) sebelum mengakses properti mesh. Node tanpa entitas mengembalikan None.
Traversal berhenti lebih awalPastikan rekursi mencapai ke node.child_nodes. Jika Anda hanya mengiterasi scene.root_node.child_nodes (tidak secara rekursif), Anda akan melewatkan semua keturunan.
Mesh tidak ada dalam hasil yang dikumpulkanPeriksa bahwa format file mempertahankan hierarki. OBJ meratakan semua geometri menjadi satu level node. Gunakan glTF atau COLLADA untuk hierarki yang dalam.
node.entity mengembalikan hanya entitas pertamaGunakan node.entities (daftar lengkap) ketika sebuah node membawa beberapa entitas. node.entity adalah singkatan untuk node.entities[0] ketika entities tidak kosong.
collect_meshes mengembalikan 0 hasil untuk STL yang dimuatFile STL biasanya menghasilkan satu node datar dengan satu entitas mesh langsung di bawahnya root_node. Periksa root_node.child_nodes[0].entity langsung.
Nama node adalah string kosongBeberapa format (binary STL, beberapa file OBJ) tidak menyimpan nama objek. Node akan memiliki string kosong name string; gunakan indeks untuk identifikasi sebagai gantinya.

Pertanyaan yang Sering Diajukan

Seberapa dalam sebuah scene graph dapat menjadi?

Tidak ada batas keras. Batas rekursi default Python (1000 frame) berlaku untuk fungsi traversal rekursif. Untuk hierarki yang sangat dalam, ubah rekursi menjadi stack 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))

Bisakah saya memodifikasi pohon saat menelusurnya?

Jangan menambah atau menghapus node dari child_nodes saat mengiterasinya. Kumpulkan node yang akan dimodifikasi dalam satu pass pertama, kemudian terapkan perubahan dalam pass kedua.

Bagaimana cara menemukan node tertentu berdasarkan nama?

Gunakan node.get_child(name) untuk menemukan anak langsung berdasarkan nama. Untuk pencarian mendalam, telusuri pohon dengan filter 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")

Apakah node.entity selalu mengembalikan Mesh?

Tidak. Sebuah node dapat menampung tipe entitas apa pun: Mesh, Camera, Light, atau entitas kustom. Selalu periksa dengan isinstance(node.entity, Mesh) sebelum menggunakan properti khusus mesh.

Bagaimana cara mendapatkan posisi ruang dunia dari sebuah node?

Baca node.global_transform.translation. Ini adalah posisi yang dievaluasi dalam ruang dunia, memperhitungkan semua transformasi nenek moyang. Ini bersifat read-only; ubah node.transform.translation untuk memposisikan ulang node.

Apakah saya dapat menghitung total poligon dalam sebuah scene tanpa menulis traversal?

Tidak langsung melalui API; tidak ada scene.total_polygon_count properti. Gunakan collect_meshes dan jumlahkan mesh.polygon_count di seluruh hasil, seperti yang ditunjukkan pada Langkah 6.

 Bahasa Indonesia