Scene-Graph

A Szenengraph ist das grundlegende Datenmodell von Aspose.3D FOSS für Python. Jede 3D-Datei, egal ob von der Festplatte geladen oder im Speicher erstellt, wird als Baum von Node Objekten, die verwurzelt sind in Scene.root_node. Jeder Knoten kann Kindknoten und ein oder mehrere Entity Objekte (Meshes, Kameras, Lichter). Das Verständnis des Szenengraphen gibt Ihnen direkten Zugriff auf die Geometrie, Materialien und räumlichen Transformationen jedes Objekts in einer Szene.

Installation und Einrichtung

Installieren Sie die Bibliothek von PyPI:

pip install aspose-3d-foss

Es werden keine nativen Erweiterungen, Compiler oder zusätzlichen Systempakete benötigt. Für vollständige Installationsanweisungen siehe die Installationsanleitung.


Übersicht: Konzepte des Szenengraphen

Der Szenengraph in Aspose.3D FOSS folgt einer einfachen Containment‑Hierarchie:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
ObjektRolle
SceneContainer auf höchster Ebene. Enthält root_node, asset_info, animation_clips, und sub_scenes.
NodeBenannter Baumknoten. Hat ein Elternteil, null oder mehr Kinder, null oder mehr Entitäten und einen lokalen Transform.
EntityGeometrie oder Szenenobjekt, das an einen Knoten angehängt ist. Häufige Entitätstypen: Mesh, Camera, Light.
TransformLokaler Positions-, Rotations- und Skalierungswert für einen Knoten. Das Ergebnis im Welt-Raum wird gelesen aus global_transform.

Schritt für Schritt: Programmatischer Aufbau eines Szenengraphen

Schritt 1: Eine Szene erstellen

Ein neuer Scene beginnt immer mit einem leeren 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 ist der Einstiegspunkt für alles: Dateien laden, Dateien speichern, Animationsclips erstellen und auf den Knotenbaum zugreifen.


Schritt 2: Kindknoten erstellen

Verwende create_child_node(name) um benannte Knoten zum Baum hinzuzufügen:

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 erstelle ein eigenständiges Node und hänge es explizit an:

from aspose.threed import Scene, Node

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

Beide Ansätze führen zum gleichen Ergebnis. create_child_node ist für die Inline‑Konstruktion prägnanter.


Schritt 3: Eine Mesh‑Entity erstellen und anhängen

A Mesh speichert Scheitelpunktdaten (control_points) und Flächentopologie (polygons). Erstelle ein Objekt, füge Geometrie hinzu und hänge es dann an einen Knoten an:

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) repräsentiert eine homogene Koordinate. Verwende w=1 für reguläre Punktpositionen.

create_polygon(*indices) akzeptiert Scheitelindizes und registriert eine Fläche in der Polygonliste. Übergebe drei Indizes für ein Dreieck, vier für ein Viereck.


Schritt 4: Knoten-Transformationen festlegen

Jeder Knoten hat ein Transform das seine Position, Orientierung und Größe im lokalen Raum steuert:

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)

Transformationen sind kumulativ: Die Weltposition eines Kindknotens ist die Zusammensetzung seiner eigenen Transformation mit allen Transformationsvorgängern. Lies das ausgewertete Weltpositionsergebnis von node.global_transform (unveränderlich, schreibgeschützt).


Schritt 5: Durchlaufe den Szenengraph rekursiv

Durchlaufe den gesamten Baum, indem du rekursiv durch 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)

Beispielausgabe für die oben erstellte Szene (der Name des Wurzelknotens ist ein leerer String):

 [None]
  parent [None]
    child [Mesh]

Für Szenen mit mehreren Entitäten pro Knoten, iteriere node.entities statt 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)

Schritt 6: Szene speichern

Gib einen Dateipfad an scene.save(). Das Format wird aus der Dateierweiterung abgeleitet:

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

Für formatbezogene Optionen übergebe ein Save-Options-Objekt als zweites Argument:

from aspose.threed.formats import GltfSaveOptions

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

Tipps und bewährte Verfahren

  • Benenne jeden Knoten. Knoten sinnvolle Namen zu geben, erleichtert das Debuggen von Traversierungen erheblich und stellt sicher, dass die Namen in der exportierten Datei erhalten bleiben.
  • Ein Mesh pro Knoten. Entitäten 1:1 mit Knoten zu halten, vereinfacht Transformationen und Kollisionsabfragen.
  • Verwenden create_child_node statt manueller Anbindung. Es setzt die Elternreferenz automatisch und ist weniger fehleranfällig.
  • Lesen global_transform nach dem Aufbau der Hierarchie. Das Ergebnis im Weltraum ist nur stabil, sobald alle übergeordneten Transformationen gesetzt sind.
  • Verändern Sie den Baum nicht während der Traversierung. Hinzufügen oder Entfernen von Kindknoten während des Durchlaufens child_nodes führt zu unvorhersehbarem Verhalten. Sammeln Sie zuerst die Knoten, dann ändern Sie sie.
  • Steuerpunkte verwenden Vector4, nicht Vector3. Immer übergeben w=1 für gewöhnliche Scheitelpunktpositionen; w=0 repräsentiert einen Richtungsvektor (kein Punkt).
  • mesh.control_points gibt eine Kopie zurück. Der control_points Eigenschaft gibt zurück list(self._control_points) — das Anhängen an die zurückgegebene Liste verändert das Mesh nicht. Immer anhängen an mesh._control_points direkt, wenn Geometrie programmgesteuert erstellt wird. Dies ist eine bekannte Bibliotheksbeschränkung; eine öffentliche Mutations-API existiert noch nicht.

Häufige Probleme

ProblemLösung
AttributeError: 'NoneType' object has no attribute 'polygons'Schütze mit if node.entity is not None bevor auf Entity‑Eigenschaften zugegriffen wird. Ein Knoten ohne Entities hat entity = None.
Mesh erscheint am Ursprung trotz Einstellung translationtransform.translation wendet einen lokalen Versatz an. Wenn der übergeordnete Knoten selbst eine nicht‑Identitäts‑Transformation hat, kann die Weltposition abweichen. Prüfen global_transform.
Kindknoten fehlen nach scene.save() / neu ladenEinige Formate (OBJ) flachen die Hierarchie ab. Verwenden Sie glTF oder COLLADA, um den vollständigen Knotenbaum beizubehalten.
polygon_count ist 0 nach mesh.create_polygon(...)Vergewissern Sie sich, dass die an übergebenen Vertex‑Indizes create_polygon innerhalb des Bereichs liegen (0 bis len(control_points) - 1).
Node.get_child(name) gibt zurück NoneDer Name ist case-sensitive. Bestätigen Sie die genaue Namenszeichenkette, die zum Erstellungszeitpunkt verwendet wurde.
Die Traversierung besucht Knoten in unerwarteter Reihenfolge.child_nodes gibt Kinder in Einfügereihenfolge zurück (die Reihenfolge add_child_node / create_child_node wurde aufgerufen).
 Deutsch