Com recórrer un gràfic d'escena 3D en Python
El gràfic de escena a Aspose.3D FOSS és un arbre de Node objectes arrelats a scene.root_node. Cada fitxer 3D, ja sigui carregat des d’OBJ, glTF, STL, COLLADA o 3MF, produeix la mateixa estructura d’arbre. Saber com recórrer-lo et permet inspeccionar la geometria, comptar polígons, filtrar objectes per tipus i processar parts específiques d’una escena complexa.
Guia pas a pas
Pas 1: Instal·lar i Importar
Instal·la Aspose.3D FOSS des de PyPI:
pip install aspose-3d-fossImporta les classes que necessites:
from aspose.threed import Scene
from aspose.threed.entities import MeshTotes les classes públiques es troben a aspose.threed o els seus subpaquets (aspose.threed.entities, aspose.threed.utilities).
Pas 2: Carregar una Escena des d’un Fitxer
Utilitza el estàtic Scene.from_file() mètode per obrir qualsevol format compatible. El format es detecta automàticament a partir de l’extensió del fitxer:
scene = Scene.from_file("model.gltf")També pots obrir amb opcions de càrrega explícites:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Després de carregar, scene.root_node és l’arrel de l’arbre. Tots els nodes importats són fills o descendents d’aquest node.
Pas 3: Escriure una Funció de Recórrer Recursiva
El recórrer més senzill visita cada node en ordre de profunditat primer:
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)Sortida d’exemple:
[-]
[-] 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>
Nota: El node arrel té un nom buit (""), de manera que la primera línia mostra [-] sense cap nom a continuació.
node.child_nodes retorna els fills en l’ordre d’inserció (l’ordre en què l’importador o l’usuari els va afegir).
Pas 4: Accedir a les Propietats de l’Entitat a Cada Node
Utilitza node.entity per obtenir la primera entitat adjunta a un node, o node.entities per iterar totes elles. Comproveu el tipus abans d’accedir a les propietats específiques del 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, i node.transform.scaling donen la transformació en l’espai local. node.global_transform dóna el resultat avaluat en l’espai mundial (amb global_transform.scale per a l’escala en l’espai mundial).
Pas 5: Filtrar nodes per tipus d’entitat
Per operar només amb tipus d’entitat específics, afegiu un isinstance guarda dins del recorregut:
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}")Aquest patró és útil per a operacions massives: aplicar una transformació a cada node de malla, substituir materials o exportar subarbres.
Pas 6: Recollir totes les malles i imprimir el recompte de vèrtexs
Agrega les estadístiques de les malles en una sola passada:
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")Problemes comuns
| Problema | Resolució |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guarda amb if node.entity is not None o isinstance(node.entity, Mesh) abans d’accedir a les propietats de la malla. Els nodes sense entitats retornen None. |
| El recorregut s’atura aviat | Assegura que la recursió arribi a node.child_nodes. Si només iteres scene.root_node.child_nodes (sense recursivitat), perds tots els descendents. |
| Malla absent dels resultats recollits | Comproveu que el format de fitxer conserva la jerarquia. OBJ aplaça tota la geometria en un únic nivell de node. Utilitzeu glTF o COLLADA per a jerarquies profundes. |
node.entity retorna només la primera entitat | Utilitzeu node.entities (la llista completa) quan un node conté múltiples entitats. node.entity és una abreviatura de node.entities[0] quan entities no és buit. |
collect_meshes retorna 0 resultats per a un STL carregat | Els fitxers STL solen produir un únic node pla amb una entitat de malla directament sota root_node. Comproveu root_node.child_nodes[0].entity directament. |
| Els noms dels nodes són cadenes buides | Alguns formats (STL binari, alguns fitxers OBJ) no emmagatzemen els noms d’objecte. Els nodes tindran buides name cadenes; utilitzeu l’índex per a la identificació en el seu lloc. |
Preguntes freqüents
Quina profunditat pot tenir un gràfic d’escena?
No hi ha cap límit estricte. El límit de recursió per defecte de Python (1000 fotogrames) s’aplica a les funcions de recorregut recursiu. Per a jerarquies molt profundes, converteix la recursió en una pila explícita:
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))Puc modificar l’arbre mentre el recorro?
No afegiu ni elimineu nodes de child_nodes mentre l’itereu. Recopileu els nodes a modificar en una primera passada, i després apliqueu els canvis en una segona passada.
Com trobo un node específic per nom?
Utilitzeu node.get_child(name) per trobar un fill directe pel nom. Per a una cerca profunda, recorreu l’arbre amb un filtre de nom:
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")Retorna node.entity sempre retorna un Mesh?
No. Un node pot contenir qualsevol tipus d’entitat: Mesh, Camera, Light, o entitats personalitzades. Comproveu sempre amb isinstance(node.entity, Mesh) abans d’utilitzar propietats específiques de la malla.
Com obtinc la posició en espai mundial d’un node?
Llegeix node.global_transform.translation. Aquesta és la posició avaluada en l’espai mundial, tenint en compte totes les transformacions dels avantpassats. És de només lectura; modifica node.transform.translation per reposicionar el node.
Puc comptar el total de polígons en una escena sense escriure un recorregut?
No directament a través de l’API; no hi ha cap scene.total_polygon_count propietat. Utilitza collect_meshes i suma mesh.polygon_count entre els resultats, tal com es mostra al Pas 6.