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

No 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
    └── ...
ObjecteRol
SceneContenidor de nivell superior. Conté root_node, asset_info, animation_clips, i sub_scenes.
NodeNode d’arbre amb nom. Té un pare, zero o més fills, zero o més entitats i un local Transform.
EntityGeometria o objecte d’escena adjuntat a un node. Tipus d’entitat comuns: Mesh, Camera, Light.
TransformPosició, 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))  # 0

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

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

Per 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_node en lloc d’un adjunt manual. Estableix la referència del pare automàticament i és menys propensa a errors.
  • Llegeix global_transform despré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_nodes produirà un comportament imprevisible. Recolliu els nodes primer, després modifiqueu-los.
  • Els punts de control utilitzen Vector4, no Vector3. Sempre passeu w=1 per a posicions de vèrtex ordinàries; w=0 representa un vector de direcció (no un punt).
  • mesh.control_points retorna una còpia. El control_points la propietat retorna list(self._control_points) — afegir a la llista retornada no modifica la malla. Sempre afegeix a mesh._control_points directament 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ènciaResolució
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 translationtransform.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() / recarregaAlguns 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 NoneEl 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 inesperatchild_nodes retorna fills en l’ordre d’inserció (l’ordre add_child_node / create_child_node es va anomenar).
 Català