Come attraversare un grafo di scena 3D in Python
Il grafo della scena in Aspose.3D FOSS è un albero di Node oggetti radicati in scene.root_node. Ogni file 3D, sia caricato da OBJ, glTF, STL, COLLADA o 3MF, produce la stessa struttura ad albero. Conoscere come attraversarlo ti permette di ispezionare la geometria, contare i poligoni, filtrare gli oggetti per tipo e processare parti specifiche di una scena complessa.
Guida passo-passo
Passo 1: Installa e Importa
Installa Aspose.3D FOSS da PyPI:
pip install aspose-3d-fossImporta le classi di cui hai bisogno:
from aspose.threed import Scene
from aspose.threed.entities import MeshTutte le classi pubbliche risiedono sotto aspose.threed o nei suoi sotto‑pacchetti (aspose.threed.entities, aspose.threed.utilities).
Passo 2: Carica una scena da un file
Usa il metodo statico Scene.from_file() per aprire qualsiasi formato supportato. Il formato viene rilevato automaticamente dall’estensione del file:
scene = Scene.from_file("model.gltf")Puoi anche aprire con opzioni di caricamento esplicite:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Dopo il caricamento, scene.root_node è la radice dell’albero. Tutti i nodi importati sono figli o discendenti di questo nodo.
Passo 3: Scrivi una funzione di attraversamento ricorsivo
L’attraversamento più semplice visita ogni nodo in ordine 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)Esempio di 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>
Nota: Il nodo radice ha un nome vuoto (""), quindi la prima riga mostra [-] senza nome successivo.
node.child_nodes restituisce i figli nell’ordine di inserimento (l’ordine in cui l’importatore o l’utente li ha aggiunti).
Passo 4: Accedi alle proprietà dell’entità su ogni nodo
Usa node.entity per ottenere la prima entità collegata a un nodo, o node.entities per iterare tutti loro. Controlla il tipo prima di accedere alle proprietà specifiche del formato:
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, e node.transform.scaling forniscono la trasformazione nello spazio locale. node.global_transform fornisce il risultato valutato nello spazio globale (with global_transform.scale per la scala nello spazio mondiale).
Passo 5: Filtrare i nodi per tipo di entità
Per operare solo su tipi di entità specifici, aggiungi un isinstance guardia all’interno del 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}")Questo schema è utile per operazioni di massa: applicare una trasformazione a ogni nodo mesh, sostituire i materiali o esportare sotto‑alberi.
Passo 6: Raccogliere tutte le mesh e stampare il conteggio dei vertici
Aggrega le statistiche delle mesh in un unico passaggio:
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")Problemi comuni
| Problema | Risoluzione |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guardia con if node.entity is not None oppure isinstance(node.entity, Mesh) prima di accedere alle proprietà della mesh. I nodi senza entità restituiscono None. |
| Il traversal si interrompe prematuramente | Assicurati che la ricorsione raggiunga node.child_nodes. Se iteri solo scene.root_node.child_nodes (non ricorsivamente), perdi tutti i discendenti. |
| Mesh mancante nei risultati raccolti | Verifica che il formato file preservi la gerarchia. OBJ appiattisce tutta la geometria in un unico livello di nodo. Usa glTF o COLLADA per gerarchie profonde. |
node.entity restituisce solo la prima entità | Usa node.entities (l’elenco completo) quando un nodo contiene più entità. node.entity è una forma abbreviata di node.entities[0] quando entities non è vuoto. |
collect_meshes restituisce 0 risultati per un STL caricato | I file STL tipicamente producono un singolo nodo piatto con una entità mesh direttamente sotto root_node. Controlla root_node.child_nodes[0].entity direttamente. |
| I nomi dei nodi sono stringhe vuote | Alcuni formati (binary STL, alcuni file OBJ) non memorizzano i nomi degli oggetti. I nodi avranno stringhe vuote name stringhe; usa l’indice per l’identificazione invece. |
Domande frequenti
Quanto può essere profondo un grafo di scena?
Non esiste un limite rigido. Il limite di ricorsione predefinito di Python (1000 fotogrammi) si applica alle funzioni di attraversamento ricorsivo. Per gerarchie molto profonde, converti la ricorsione in uno stack esplicito:
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))Posso modificare l’albero durante l’attraversamento?
Non aggiungere o rimuovere nodi da child_nodes mentre lo iteri. Raccogli i nodi da modificare in un primo passaggio, poi applica le modifiche in un secondo passaggio.
Come trovo un nodo specifico per nome?
Usa node.get_child(name) per trovare un figlio diretto per nome. Per una ricerca profonda, attraversa l’albero con un filtro per nome:
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")Restituisce node.entity sempre una Mesh?
No. Un nodo può contenere qualsiasi tipo di entità: Mesh, Camera, Light, o entità personalizzate. Controlla sempre con isinstance(node.entity, Mesh) prima di utilizzare le proprietà specifiche della mesh.
Come ottengo la posizione in spazio mondo di un nodo?
Leggi node.global_transform.translation. Questa è la posizione valutata nello spazio mondiale, tenendo conto di tutte le trasformazioni dei genitori. È di sola lettura; modifica node.transform.translation per riposizionare il nodo.
Posso contare il numero totale di poligoni in una scena senza scrivere un traversal?
Non direttamente tramite l’API; non c’è scene.total_polygon_count proprietà. Usa collect_meshes e somma mesh.polygon_count sui risultati, come mostrato al Passo 6.