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

Geen 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
    └── ...
ObjectRole
SceneTopniveau container. Bevat root_node, asset_info, animation_clips en sub_scenes.
NodeBenoemde boomknooppunt. Heeft een ouder, nul of meer kinderen, nul of meer entiteiten, en een lokale Transform.
EntityGeometrie of scène‑object gekoppeld aan een knooppunt. Veelvoorkomende entiteitstypen: Mesh, Camera, Light.
TransformLokale‑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))  # 0

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

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

Voor 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_node in plaats van handmatige koppeling. Het stelt de ouderreferentie automatisch in en is minder foutgevoelig.
  • Lees global_transform na 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_nodes zal onvoorspelbaar gedrag veroorzaken. Verzamel eerst de nodes, wijzig daarna.
  • Controlepunten gebruiken Vector4, niet Vector3. Geef altijd w=1 door voor gewone vertexposities; w=0 vertegenwoordigt een richtingsvector (geen punt).
  • mesh.control_points retourneert een kopie. De control_points‑eigenschap retourneert list(self._control_points) — toevoegen aan de geretourneerde lijst wijzigt de mesh niet. Voeg altijd direct toe aan mesh._control_points bij het programmatisch opbouwen van geometrie. Dit is een bekende bibliotheekbeperking; een publieke mutatie‑API bestaat nog niet.

Veelvoorkomende problemen

IssueResolution
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 translationtransform.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() / reloadSommige 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 NoneDe naam is hoofdlettergevoelig. Bevestig de exacte naamtekenreeks die bij het aanmaken werd gebruikt.
Traversal visits nodes in unexpected orderchild_nodes geeft kinderen terug in de volgorde van invoeging (de volgorde waarin add_child_node / create_child_node werd aangeroepen).
 Nederlands