Graf de scenă

Un scene graph este modelul de date fundamental al Aspose.3D FOSS pentru Python. Fiecare fișier 3D, fie că este încărcat de pe disc sau construit în memorie, este reprezentat ca un arbore de obiecte Node cu rădăcina la Scene.root_node. Fiecare nod poate conține noduri copil și unul sau mai multe obiecte Entity (meshe, camere, lumini). Înțelegerea scene graph-ului îți oferă acces direct la geometria, materialele și transformările spațiale ale fiecărui obiect dintr-o scenă.

Instalare și configurare

Instalați biblioteca din PyPI:

pip install aspose-3d-foss

Nu sunt necesare extensii native, compilatoare sau pachete suplimentare de sistem. Pentru instrucțiuni complete de instalare, consultați Ghid de instalare.


Prezentare generală: Concepte de graf de scenă

Graful de scenă în Aspose.3D FOSS urmează o ierarhie de conținere simplă:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
ObjectRole
SceneContainer de nivel superior. Conține root_node, asset_info, animation_clips și sub_scenes.
NodeNod de arbore denumit. Are un părinte, zero sau mai mulți copii, zero sau mai multe entități și un Transform local.
EntityGeometrie sau obiect de scenă atașat unui nod. Tipuri comune de entități: Mesh, Camera, Light.
TransformPoziție, rotație și scară în spațiul local pentru un nod. Rezultatul în spațiul mondial este citit din global_transform.

Pas cu pas: Construirea unui graf de scenă programatic

Pasul 1: Crearea unei scene

Un nou Scene începe întotdeauna cu un root_node gol:

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 este punctul de intrare pentru tot: încărcarea fișierelor, salvarea fișierelor, crearea de clipuri de animație și accesarea arborelui de noduri.


Pasul 2: Crearea nodurilor copil

Utilizați create_child_node(name) pentru a adăuga noduri numite în arbore:

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

Alternativ, creaţi un fişier independent Node şi ataşaţi-l explicit:

from aspose.threed import Scene, Node

scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)

Ambele abordări produc același rezultat. create_child_node este mai concis pentru construcția inline.


Pasul 3: Creați o entitate Mesh și atașați‑o

Un Mesh stochează datele de vârf (control_points) și topologia fețelor (polygons). Creează unul, adaugă geometrie, apoi atașează-l la un nod:

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) reprezintă o coordonată omogenă. Folosiți w=1 pentru poziții de puncte obișnuite.

create_polygon(*indices) acceptă indici de vârf și înregistrează o față în lista de poligoane. Furnizaţi trei indici pentru un triunghi, patru pentru un cvadrilater.


Pasul 4: Setarea transformărilor nodului

Fiecare nod are un Transform care controlează poziția, orientarea și dimensiunea în spațiul 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)

Transformările sunt cumulative: poziția world-space a unui nod copil este compunerea transformului său cu toate transformurile strămoșilor. Citiți rezultatul evaluat world-space din node.global_transform (immutable, read-only).


Pasul 5: Parcurgeți graful de scenă recursiv

Parcurgeți întregul arbore recursiv prin 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)

Exemplu de ieșire pentru scena construită mai sus (numele nodului rădăcină este un șir gol):

 [None]
  parent [None]
    child [Mesh]

Pentru scenele cu mai multe entități pe nod, iterați node.entities în loc 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)

Pasul 6: Salvează scena

Transmiteți o cale de fișier către scene.save(). Formatul este dedus din extensia fișierului:

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

Pentru opțiuni specifice formatului, transmiteți un obiect save-options ca al doilea argument:

from aspose.threed.formats import GltfSaveOptions

opts = GltfSaveOptions()
scene.save("scene.gltf", opts)

Sfaturi și cele mai bune practici

  • Numește fiecare nod. Atribuirea de nume semnificative nodurilor face depanarea traversărilor mult mai ușoară și asigură păstrarea numelor în fișierul exportat.
  • Un mesh pe nod. Menținerea entităților 1:1 cu nodurile simplifică transformările și interogările de coliziune.
  • Folosește create_child_node în loc de atașarea manuală. Aceasta setează referința părintelui automat și este mai puțin predispusă la erori.
  • Citește global_transform după construirea ierarhiei. Rezultatul în spațiul mondial este stabil doar după ce toate transformările strămoșilor sunt setate.
  • Nu modifica arborele în timpul traversării. Adăugarea sau eliminarea nodurilor copil în timpul iterării child_nodes va produce un comportament imprevizibil. Colectează nodurile mai întâi, apoi modifică-le.
  • Punctele de control folosesc Vector4, nu Vector3. Transmite întotdeauna w=1 pentru pozițiile obișnuite ale vârfurilor; w=0 reprezintă un vector de direcție (nu un punct).
  • mesh.control_points returnează o copie. Proprietatea control_points returnează list(self._control_points) — adăugarea la lista returnată nu modifică mesh-ul. Adaugă întotdeauna direct la mesh._control_points când construiești geometria programatic. Aceasta este o limitare cunoscută a bibliotecii; încă nu există un API public de mutație.

Probleme comune

IssueResolution
AttributeError: 'NoneType' object has no attribute 'polygons'Protejați cu if node.entity is not None înainte de a accesa proprietățile entității. Un nod fără entități are entity = None.
Mesh appears at the origin despite setting translationtransform.translation aplică un offset local. Dacă nodul părinte are el însuși o transformare care nu este identitate, poziția în lume poate diferi. Verificați global_transform.
Child nodes missing after scene.save() / reloadUnele formate (OBJ) aplatizează ierarhia. Utilizați glTF sau COLLADA pentru a păstra arborele complet de noduri.
polygon_count is 0 after mesh.create_polygon(...)Verificați că indecșii de vârf transmiși către create_polygon sunt în interval (0 până la len(control_points) - 1).
Node.get_child(name) returns NoneNumele este sensibil la majuscule/minuscule. Confirmați șirul exact al numelui utilizat la momentul creării.
Traversal visits nodes in unexpected orderchild_nodes returnează copiii în ordinea inserării (ordinea în care a fost apelat add_child_node / create_child_node).
 Română