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-fossNu 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
└── ...| Object | Role |
|---|---|
Scene | Container de nivel superior. Conține root_node, asset_info, animation_clips și sub_scenes. |
Node | Nod de arbore denumit. Are un părinte, zero sau mai mulți copii, zero sau mai multe entități și un Transform local. |
Entity | Geometrie sau obiect de scenă atașat unui nod. Tipuri comune de entități: Mesh, Camera, Light. |
Transform | Poziț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)) # 0Scene 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)) # 1Alternativ, 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") # STLPentru 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_transformdupă 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_nodesva produce un comportament imprevizibil. Colectează nodurile mai întâi, apoi modifică-le. - Punctele de control folosesc
Vector4, nuVector3. Transmite întotdeaunaw=1pentru pozițiile obișnuite ale vârfurilor;w=0reprezintă un vector de direcție (nu un punct). mesh.control_pointsreturnează o copie. Proprietateacontrol_pointsreturneazălist(self._control_points)— adăugarea la lista returnată nu modifică mesh-ul. Adaugă întotdeauna direct lamesh._control_pointscând construiești geometria programatic. Aceasta este o limitare cunoscută a bibliotecii; încă nu există un API public de mutație.
Probleme comune
| Issue | Resolution |
|---|---|
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 translation | transform.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() / reload | Unele 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 None | Numele este sensibil la majuscule/minuscule. Confirmați șirul exact al numelui utilizat la momentul creării. |
| Traversal visits nodes in unexpected order | child_nodes returnează copiii în ordinea inserării (ordinea în care a fost apelat add_child_node / create_child_node). |