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-fossImpor kelas yang Anda butuhkan:
from aspose.threed import Scene
from aspose.threed.entities import MeshSemua 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
| Masalah | Solusi |
|---|---|
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 awal | Pastikan 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 dikumpulkan | Periksa 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 pertama | Gunakan 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 dimuat | File 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 kosong | Beberapa 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.