Scènegraph
Een scene graph is het fundamentele datamodel van Aspose.3D FOSS for Python. Elk 3D‑bestand, of het nu van schijf wordt geladen of in het geheugen wordt geconstrueerd, wordt weergegeven als een boom van Node‑objecten met Scene.root_node als wortel. Elke knoop kan kindknooppunten bevatten en een of meer Entity‑objecten (meshes, cameras, lights). Het begrijpen van de scene graph geeft je directe toegang tot de geometrie, materialen en ruimtelijke transformaties van elk object in een scene.
Installatie en configuratie
Installeer de bibliotheek van PyPI:
pip install aspose-3d-fossGeen native extensies, compilers of extra systeempakketten zijn vereist. Voor volledige installatie‑instructies, zie de Installation Guide.
Overzicht: Scene Graph-concepten
De scene graph in Aspose.3D FOSS volgt een eenvoudige containment‑hiërarchie:
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 | Topniveau container. Bevat root_node, asset_info, animation_clips en sub_scenes. |
Node | Benoemde boomknooppunt. Heeft een ouder, nul of meer kinderen, nul of meer entiteiten, en een lokale Transform. |
Entity | Geometrie of scène‑object gekoppeld aan een knooppunt. Veelvoorkomende entiteitstypen: Mesh, Camera, Light. |
Transform | Lokale‑ruimte positie, rotatie en schaal voor een knooppunt. Het wereld‑ruimte resultaat wordt gelezen uit global_transform. |
Stap voor stap: Een scenegraph via code bouwen
Stap 1: Maak een scène
Een nieuwe Scene begint altijd met een lege 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 is het toegangspunt voor alles: bestanden laden, bestanden opslaan, animatieclips maken en de knoopboom benaderen.
Stap 2: Kindknooppunten maken
Gebruik create_child_node(name) om benoemde knooppunten aan de boom toe te voegen:
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)) # 1Maak eventueel een zelfstandige Node en voeg deze expliciet toe:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Beide benaderingen leveren hetzelfde resultaat op. create_child_node is beknopter voor inline constructie.
Stap 3: Maak een Mesh Entity en koppel het
Een Mesh slaat vertexgegevens (control_points) en vlaktopologie (polygons) op. Maak er één, voeg geometrie toe, en koppel het vervolgens aan een 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) vertegenwoordigt een homogene coördinaat. Gebruik w=1 voor reguliere puntposities.
create_polygon(*indices) accepteert vertex indices en registreert één vlak in de polygon list. Geef drie indices door voor een driehoek, vier voor een quad.
Stap 4: Node‑transformaties instellen
Elke knoop heeft een Transform die zijn positie, oriëntatie en grootte in de lokale ruimte regelt:
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)Transformaties zijn cumulatief: de wereldruimtepunt van een kindknooppunt is de samenstelling van zijn eigen transformatie met alle vooroudertransformaties. Lees het geëvalueerde wereldruimteresultaat van node.global_transform (onveranderlijk, alleen‑lezen).
Stap 5: Doorloop de scènegrafiek recursief
Loop de volledige boom door recursief door node.child_nodes te gaan:
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)Voorbeeldoutput voor de hierboven gebouwde scène (root node-naam is een lege string):
[None]
parent [None]
child [Mesh]Voor scènes met meerdere entiteiten per knooppunt, itereren node.entities in plaats van 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)Stap 6: Sla de scène op
Geef een bestandspad door aan scene.save(). Het formaat wordt afgeleid van de bestandsextensie:
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") # STLVoor formaat‑specifieke opties, geef een save‑options object door als het tweede argument:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Tips en best practices
- Geef elke node een naam. Het geven van betekenisvolle namen aan nodes maakt het debuggen van traversals veel gemakkelijker en zorgt ervoor dat namen behouden blijven in het geëxporteerde bestand.
- Één mesh per node. Entiteiten 1:1 met nodes houden vereenvoudigt transformaties en botsingsquery’s.
- Gebruik
create_child_nodein plaats van handmatige koppeling. Het stelt de ouderreferentie automatisch in en is minder foutgevoelig. - Lees
global_transformna het opbouwen van de hiërarchie. Het resultaat in wereldcoördinaten is alleen stabiel zodra alle vooroudertransformaties zijn ingesteld. - Wijzig de boom niet tijdens traverseren. Het toevoegen of verwijderen van kindnodes tijdens itereren
child_nodeszal onvoorspelbaar gedrag veroorzaken. Verzamel eerst de nodes, wijzig daarna. - Controlepunten gebruiken
Vector4, nietVector3. Geef altijdw=1door voor gewone vertexposities;w=0vertegenwoordigt een richtingsvector (geen punt). mesh.control_pointsretourneert een kopie. Decontrol_points‑eigenschap retourneertlist(self._control_points)— toevoegen aan de geretourneerde lijst wijzigt de mesh niet. Voeg altijd direct toe aanmesh._control_pointsbij het programmatisch opbouwen van geometrie. Dit is een bekende bibliotheekbeperking; een publieke mutatie‑API bestaat nog niet.
Veelvoorkomende problemen
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Bescherm met if node.entity is not None voordat u entiteitseigenschappen benadert. Een knooppunt zonder entiteiten heeft entity = None. |
Mesh appears at the origin despite setting translation | transform.translation past een lokale offset toe. Als het bovenliggende knooppunt zelf een transformatie heeft die geen identiteit is, kan de wereldpositie afwijken. Controleer global_transform. |
Child nodes missing after scene.save() / reload | Sommige formaten (OBJ) vlakken de hiërarchie af. Gebruik glTF of COLLADA om de volledige knooppuntboom te behouden. |
polygon_count is 0 after mesh.create_polygon(...) | Controleer of de vertex‑indices die aan create_polygon worden doorgegeven binnen het bereik liggen (0 tot len(control_points) - 1). |
Node.get_child(name) returns None | De naam is hoofdlettergevoelig. Bevestig de exacte naamtekenreeks die bij het aanmaken werd gebruikt. |
| Traversal visits nodes in unexpected order | child_nodes geeft kinderen terug in de volgorde van invoeging (de volgorde waarin add_child_node / create_child_node werd aangeroepen). |