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-foss

Non 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
    └── ...
OggettoRuolo
SceneContenitore di livello superiore. Contiene root_node, asset_info, animation_clips, e sub_scenes.
NodeNodo dell’albero con nome. Ha un genitore, zero o più figli, zero o più entità e un Transform.
EntityGeometria o oggetto di scena collegato a un nodo. Tipi di entità comuni: Mesh, Camera, Light.
TransformPosizione, 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))  # 0

Scene è 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))     # 1

In 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")    # STL

Per 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_node invece dell’attacco manuale. Imposta automaticamente il riferimento al genitore ed è meno soggetto a errori.
  • Leggi global_transform dopo 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_nodes produrrà un comportamento imprevedibile. Raccogli i nodi prima, poi modifica.
  • I punti di controllo usano Vector4, non Vector3. Passa sempre w=1 per posizioni di vertice ordinarie; w=0 rappresenta un vettore di direzione (non un punto).
  • mesh.control_points restituisce una copia. Il control_points la proprietà restituisce list(self._control_points) — aggiungere alla lista restituita non modifica la mesh. Aggiungi sempre a mesh._control_points direttamente quando si costruisce la geometria programmaticamente. Questa è una limitazione nota della libreria; un’API pubblica di mutazione non esiste ancora.

Problemi comuni

ProblemaRisoluzione
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 translationtransform.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() / ricaricaAlcuni 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 NoneIl nome è sensibile al maiuscolo/minuscolo. Conferma la stringa del nome esatta usata al momento della creazione.
Traversal visita i nodi in un ordine inaspettatochild_nodes restituisce i figli in ordine di inserimento (l’ordine add_child_node / create_child_node è stato chiamato).
 Italiano