Scene Graph
A grafo della scena è il modello di dati fondamentale di Aspose.3D FOSS per Python. Ogni file 3D, sia caricato dal disco sia costruito in memoria, è rappresentato come un albero di Node oggetti radicati in Scene.root_node. Ogni nodo può contenere nodi figlio e uno o più Entity oggetti (mesh, telecamere, luci). Comprendere il grafo della scena ti dà accesso diretto alla geometria, ai materiali e alle trasformazioni spaziali di ogni oggetto in una scena.
Installazione e configurazione
Installa la libreria da PyPI:
pip install aspose-3d-fossNon sono richieste estensioni native, compilatori o pacchetti di sistema aggiuntivi. Per istruzioni complete di installazione, consulta il Guida all’installazione.
Panoramica: Concetti di Grafo di Scena
Il grafo di scena in Aspose.3D FOSS segue una gerarchia di contenimento semplice:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Oggetto | Ruolo |
|---|---|
Scene | Contenitore di livello superiore. Contiene root_node, asset_info, animation_clips, e sub_scenes. |
Node | Nodo dell’albero con nome. Ha un genitore, zero o più figli, zero o più entità e un Transform. |
Entity | Geometria o oggetto di scena collegato a un nodo. Tipi di entità comuni: Mesh, Camera, Light. |
Transform | Posizione, rotazione e scala nello spazio locale per un nodo. Il risultato nello spazio globale si legge da global_transform. |
Passo dopo passo: Costruire un grafo di scena programmaticamente
Passo 1: Crea una scena
Un nuovo Scene inizia sempre con un vuoto root_node:
from aspose.threed import Scene
scene = Scene()
print(scene.root_node.name) # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes)) # 0Scene è il punto di ingresso per tutto: caricamento dei file, salvataggio dei file, creazione di clip di animazione e accesso all’albero dei nodi.
Passo 2: Crea nodi figli
Usa create_child_node(name) per aggiungere nodi nominati all’albero:
from aspose.threed import Scene
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
print(parent.name) # "parent"
print(child.parent_node.name) # "parent"
print(len(scene.root_node.child_nodes)) # 1In alternativa, crea un standalone Node e collegalo esplicitamente:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Entrambi gli approcci producono lo stesso risultato. create_child_node è più conciso per la costruzione inline.
Passo 3: Crea un’entità Mesh e collegala
A Mesh memorizza i dati dei vertici (control_points) e la topologia delle facce (polygons). Crea uno, aggiungi la geometria, poi collegalo a un nodo:
from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)
child.add_entity(mesh)
print(f"Mesh name: {mesh.name}")
print(f"Vertex count: {len(mesh.control_points)}")
print(f"Polygon count: {mesh.polygon_count}")Vector4(x, y, z, w) rappresenta una coordinata omogenea. Usa w=1 per le posizioni dei punti regolari.
create_polygon(*indices) accetta indici dei vertici e registra una faccia nella lista dei poligoni. Fornisci tre indici per un triangolo, quattro per un quadrilatero.
Passo 4: Imposta le trasformazioni dei nodi
Ogni nodo ha un Transform che controlla la sua posizione, orientamento e dimensione nello spazio locale:
from aspose.threed.utilities import Vector3, Quaternion
##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)
##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)
##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)Le trasformazioni sono cumulative: la posizione in spazio mondiale di un nodo figlio è la composizione della sua trasformazione con tutte le trasformazioni dei suoi antenati. Leggi il risultato valutato in spazio mondiale da node.global_transform (immutabile, sola lettura).
Passo 5: Attraversa ricorsivamente il grafo della scena
Percorri l’intero albero ricorsivamente attraverso node.child_nodes:
def traverse(node, depth=0):
indent = " " * depth
entity_type = type(node.entity).__name__ if node.entity else "None"
print(f"{indent}{node.name} [{entity_type}]")
for child in node.child_nodes:
traverse(child, depth + 1)
traverse(scene.root_node)Esempio di output per la scena costruita sopra (il nome del nodo radice è una stringa vuota):
[None]
parent [None]
child [Mesh]Per le scene con più entità per nodo, iterare node.entities invece di node.entity:
def traverse_full(node, depth=0):
indent = " " * depth
entity_names = [type(e).__name__ for e in node.entities] or ["None"]
print(f"{indent}{node.name} [{', '.join(entity_names)}]")
for child in node.child_nodes:
traverse_full(child, depth + 1)
traverse_full(scene.root_node)Passo 6: Salva la scena
Passa un percorso di file a scene.save(). Il formato è dedotto dall’estensione del file:
scene.save("scene.gltf") # JSON glTF 2.0
scene.save("scene.glb") # Binary GLB container
scene.save("scene.obj") # Wavefront OBJ
scene.save("scene.stl") # STLPer opzioni specifiche del formato, passa un oggetto save-options come secondo argomento:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Suggerimenti e migliori pratiche
- Nomina ogni nodo. Assegnare ai nodi nomi significativi rende il debug dei percorsi molto più semplice e garantisce che i nomi siano conservati nel file esportato.
- Una mesh per nodo. Mantenere le entità 1:1 con i nodi semplifica le trasformazioni e le query di collisione.
- Usa
create_child_nodeinvece dell’attacco manuale. Imposta automaticamente il riferimento al genitore ed è meno soggetto a errori. - Leggi
global_transformdopo aver costruito la gerarchia. Il risultato nello spazio mondiale è stabile solo dopo che tutte le trasformazioni degli antenati sono impostate. - Non modificare l’albero durante il percorso. Aggiungere o rimuovere nodi figli durante l’iterazione
child_nodesprodurrà un comportamento imprevedibile. Raccogli i nodi prima, poi modifica. - I punti di controllo usano
Vector4, nonVector3. Passa semprew=1per posizioni di vertice ordinarie;w=0rappresenta un vettore di direzione (non un punto). mesh.control_pointsrestituisce una copia. Ilcontrol_pointsla proprietà restituiscelist(self._control_points)— aggiungere alla lista restituita non modifica la mesh. Aggiungi sempre amesh._control_pointsdirettamente quando si costruisce la geometria programmaticamente. Questa è una limitazione nota della libreria; un’API pubblica di mutazione non esiste ancora.
Problemi comuni
| Problema | Risoluzione |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Proteggi con if node.entity is not None prima di accedere alle proprietà dell’entità. Un nodo senza entità ha entity = None. |
La mesh appare all’origine nonostante l’impostazione translation | transform.translation applica un offset locale. Se il nodo genitore stesso ha una trasformazione non identità, la posizione globale può differire. Controlla global_transform. |
Nodi figlio mancanti dopo scene.save() / ricarica | Alcuni formati (OBJ) appiattiscono la gerarchia. Usa glTF o COLLADA per preservare l’intero albero dei nodi. |
polygon_count è 0 dopo mesh.create_polygon(...) | Verifica che gli indici dei vertici passati a create_polygon siano entro l’intervallo (0 a len(control_points) - 1). |
Node.get_child(name) restituisce None | Il nome è sensibile al maiuscolo/minuscolo. Conferma la stringa del nome esatta usata al momento della creazione. |
| Traversal visita i nodi in un ordine inaspettato | child_nodes restituisce i figli in ordine di inserimento (l’ordine add_child_node / create_child_node è stato chiamato). |