Cum să parcurgi un graf de scenă 3D în Python
Graful de scenă în Aspose.3D FOSS este un arbore de Node obiecte cu rădăcina la scene.root_node. Fiecare fișier 3D, indiferent dacă este încărcat din OBJ, glTF, STL, COLLADA sau 3MF, produce aceeași structură de arbore. Cunoașterea modului de a-l parcurge vă permite să inspectați geometria, să numărați poligoanele, să filtrați obiectele după tip și să procesați părți specifice ale unei scene complexe.
Ghid pas cu pas
Pasul 1: Instalați și importați
Instalați Aspose.3D FOSS de pe PyPI:
pip install aspose-3d-fossImportați clasele de care aveți nevoie:
from aspose.threed import Scene
from aspose.threed.entities import MeshToate clasele publice se află sub aspose.threed sau sub-pachetele sale (aspose.threed.entities, aspose.threed.utilities).
Pasul 2: Încărcați o scenă dintr-un fișier
Folosiți metoda statică Scene.from_file() pentru a deschide orice format suportat. Formatul este detectat automat din extensia fișierului:
scene = Scene.from_file("model.gltf")De asemenea, puteți deschide cu opțiuni explicite de încărcare:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)După încărcare, scene.root_node este rădăcina arborelui. Toate nodurile importate sunt copii sau descendenți ai acestui nod.
Pasul 3: Scrieți o funcție recursivă de traversare
Cea mai simplă traversare vizitează fiecare nod în ordine adâncime-primeiro (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)Exemplu de ieșire:
[-]
[-] 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=“Copiază codul”
<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>
Notă: Nodul rădăcină are un nume gol (""), astfel încât prima linie să afișeze [-] fără niciun nume ulterior.
node.child_nodes returnează copiii în ordinea inserării (ordinea în care importatorul sau utilizatorul i-a adăugat).
Pasul 4: Accesați proprietățile entității pentru fiecare nod
Folosiți node.entity pentru a obține prima entitate atașată unui nod, sau node.entities pentru a itera prin toate. Verificați tipul înainte de a accesa proprietățile specifice formatului:
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, și node.transform.scaling oferă transformarea în spațiul local. node.global_transform returnează rezultatul evaluat în spațiul global (cu global_transform.scale pentru scară în spațiul global).
Pasul 5: Filtrarea nodurilor după tipul entității
Pentru a opera doar pe tipuri specifice de entități, adăugați un isinstance verificare în interiorul traversării:
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}")Acest model este util pentru operații în masă: aplicarea unei transformări fiecărui nod de tip mesh, înlocuirea materialelor sau exportarea sub-arborilor.
Pasul 6: Colectați toate mesh-urile și afișați numărul de vârfuri
Agregați statisticile mesh-urilor într-un singur pas:
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")Probleme comune
| Problemă | Rezolvare |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Verificare cu if node.entity is not None sau isinstance(node.entity, Mesh) înainte de a accesa proprietățile mesh. Nodurile fără entități returnează None. |
| Traversarea se oprește devreme | Asigurați-vă că recursiunea ajunge în node.child_nodes. Dacă iterați doar scene.root_node.child_nodes (nu recursiv), pierdeți toți descendenții. |
| Mesh lipsă din rezultatele colectate | Verificați că formatul de fișier păstrează ierarhia. OBJ aplatizează toată geometria la un singur nivel de nod. Utilizați glTF sau COLLADA pentru ierarhii profunde. |
node.entity returnează doar prima entitate | Utilizați node.entities (lista completă) când un nod conține multiple entități. node.entity este o prescurtare pentru node.entities[0] când entities nu este goală. |
collect_meshes returnează 0 rezultate pentru un STL încărcat | Fișierele STL produc de obicei un singur nod plat cu o entitate mesh direct sub root_node. Verificați root_node.child_nodes[0].entity direct. |
| Numele nodurilor sunt șiruri goale | Unele formate (STL binar, unele fișiere OBJ) nu stochează numele obiectelor. Nodurile vor avea șiruri goale name șiruri; folosiți indexul pentru identificare în schimb. |
Întrebări frecvente
Cât de adânc poate fi un graf de scenă?
Nu există o limită strictă. Limita implicită de recursivitate a Python (1000 de cadre) se aplică funcțiilor de traversare recursivă. Pentru ierarhii foarte adânci, convertiți recursiunea într-un stivă explicită:
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))Pot modifica arborele în timpul traversării?
Nu adăugați sau eliminați noduri din child_nodes în timpul iterării acestuia. Colectați nodurile de modificat într-un prim pas, apoi aplicați modificările într-un al doilea pas.
Cum găsesc un nod specific după nume?
Utilizați node.get_child(name) pentru a găsi un copil direct după nume. Pentru o căutare profundă, parcurgeți arborele cu un filtru de nume:
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")Întoarce node.entity întotdeauna returnează un Mesh?
Nu. Un nod poate conține orice tip de entitate: Mesh, Camera, Light, sau entități personalizate. Verificați întotdeauna cu isinstance(node.entity, Mesh) înainte de a utiliza proprietăți specifice mesh-ului.
Cum obțin poziția în spațiul mondial a unui nod?
Citește node.global_transform.translation. Aceasta este poziția evaluată în spațiul lumii, ținând cont de toate transformările strămoșilor. Este doar în citire; modifică node.transform.translation pentru a repoziționa nodul.
Pot să număr totalul de poligoane dintr-o scenă fără să scriu o traversare?
Nu direct prin API; nu există scene.total_polygon_count proprietate. Folosește collect_meshes și însumează mesh.polygon_count pe parcursul rezultatelor, așa cum se arată în Pasul 6.