Gràfic d'escena
A graf de escena és el model de dades fonamental de Aspose.3D FOSS per a Python. Cada fitxer 3D, tant si es carrega des del disc com si es construeix en memòria, es representa com un arbre de Node objectes arrelats a Scene.root_node. Cada node pot contenir nodes fills i un o més Entity objectes (malles, càmeres, llums). Entendre el graf de escena us dóna accés directe a la geometria, els materials i les transformacions espacials de cada objecte d’una escena.
Instal·lació i configuració
Instal·leu la biblioteca des de PyPI:
pip install aspose-3d-fossNo es requereixen extensions natives, compiladors o paquets de sistema addicionals. Per a instruccions d’instal·lació completes, vegeu el Guia d’instal·lació.
Visió general: Conceptes del gràfic d’escena
El gràfic d’escena a Aspose.3D FOSS segueix una jerarquia de contenció senzilla:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Objecte | Rol |
|---|---|
Scene | Contenidor de nivell superior. Conté root_node, asset_info, animation_clips, i sub_scenes. |
Node | Node d’arbre amb nom. Té un pare, zero o més fills, zero o més entitats i un local Transform. |
Entity | Geometria o objecte d’escena adjuntat a un node. Tipus d’entitat comuns: Mesh, Camera, Light. |
Transform | Posició, rotació i escala en l’espai local per a un node. El resultat en l’espai mundial es llegeix des de global_transform. |
Pas a pas: Construir un gràfic d’escena programàticament
Pas 1: Crear una escena
Un nou Scene sempre comença amb un buit 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 és el punt d’entrada per a tot: carregar fitxers, desar fitxers, crear clips d’animació i accedir a l’arbre de nodes.
Pas 2: Crear nodes fills
Utilitza create_child_node(name) per afegir nodes amb nom a l’arbre:
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)) # 1Alternativament, crea un element independent Node i adjunta’l explícitament:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Ambdues aproximacions produeixen el mateix resultat. create_child_node és més concís per a la construcció en línia.
Pas 3: Crear una entitat Mesh i adjuntar-la
A Mesh emmagatzema dades de vèrtex (control_points) i topologia de cares (polygons). Crea’n un, afegeix geometria i, a continuació, adjunta’l a un node:
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) representa una coordenada homogènia. Utilitza w=1 per a posicions de punts regulars.
create_polygon(*indices) accepta índexs de vèrtex i registra una cara a la llista de polígons. Passa tres índexs per a un triangle, quatre per a un quadrilàter.
Pas 4: Estableix les transformacions del node
Cada node té una Transform que controla la seva posició, orientació i mida en l’espai local:
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)Les transformacions són acumulatives: la posició en l’espai mundial d’un node fill és la composició de la seva pròpia transformació amb totes les transformacions dels avantpassats. Llegeix el resultat avaluat en l’espai mundial des de node.global_transform (immutable, només de lectura).
Pas 5: Recorre el gràfic d’escena recursivament
Recorre tot l’arbre recorrent 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)Exemple de sortida per a l’escena construïda anteriorment (el nom del node arrel és una cadena buida):
[None]
parent [None]
child [Mesh]Per a escenes amb múltiples entitats per node, itera node.entities en lloc de 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)Pas 6: Desa l’escena
Passa una ruta de fitxer a scene.save(). El format s’infereix a partir de l’extensió del fitxer:
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 a opcions específiques del format, passa un objecte d’opcions de desament com a segon argument:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Consells i bones pràctiques
- Anomena cada node. Donar noms significatius als nodes fa que la depuració de les travessies sigui molt més fàcil i assegura que els noms es conserven al fitxer exportat.
- Una malla per node. Mantenir les entitats 1:1 amb els nodes simplifica les transformacions i les consultes de col·lisió.
- Utilitza
create_child_nodeen lloc d’un adjunt manual. Estableix la referència del pare automàticament i és menys propensa a errors. - Llegeix
global_transformdesprés de construir la jerarquia. El resultat en l’espai mundial només és estable un cop s’han establert totes les transformacions dels avantpassats. - No modifiqueu l’arbre durant la travessia. Afegir o eliminar nodes fill mentre s’itera
child_nodesproduirà un comportament imprevisible. Recolliu els nodes primer, després modifiqueu-los. - Els punts de control utilitzen
Vector4, noVector3. Sempre passeuw=1per a posicions de vèrtex ordinàries;w=0representa un vector de direcció (no un punt). mesh.control_pointsretorna una còpia. Elcontrol_pointsla propietat retornalist(self._control_points)— afegir a la llista retornada no modifica la malla. Sempre afegeix amesh._control_pointsdirectament quan es construeix geometria programàticament. Aquesta és una limitació coneguda de la biblioteca; encara no existeix una API pública de mutació.
Problemes comuns
| Incidència | Resolució |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guarda amb if node.entity is not None abans d’accedir a les propietats de l’entitat. Un node sense entitats té entity = None. |
Mesh apareix a l’origen tot i establir translation | transform.translation aplica un desplaçament local. Si el node pare mateix té una transformació que no és la identitat, la posició mundial pot diferir. Comprova global_transform. |
Nodes fills desapareguts després de scene.save() / recarrega | Alguns formats (OBJ) aplanen la jerarquia. Utilitza glTF o COLLADA per preservar l’arbre complet de nodes. |
polygon_count és 0 després mesh.create_polygon(...) | Verifiqueu que els índexs de vèrtex passats a create_polygon estiguin dins del rang (0 fins a len(control_points) - 1). |
Node.get_child(name) retorna None | El nom distingeix majúscules i minúscules. Confirmeu la cadena de nom exacta utilitzada en el moment de la creació. |
| El recorregut visita els nodes en un ordre inesperat | child_nodes retorna fills en l’ordre d’inserció (l’ordre add_child_node / create_child_node es va anomenar). |