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-fossEs 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
└── ...| Objekt | Rolle |
|---|---|
Scene | Container auf höchster Ebene. Enthält root_node, asset_info, animation_clips, und sub_scenes. |
Node | Benannter Baumknoten. Hat ein Elternteil, null oder mehr Kinder, null oder mehr Entitäten und einen lokalen Transform. |
Entity | Geometrie oder Szenenobjekt, das an einen Knoten angehängt ist. Häufige Entitätstypen: Mesh, Camera, Light. |
Transform | Lokaler 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)) # 0Scene 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)) # 1Alternativ 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") # STLFü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_nodestatt manueller Anbindung. Es setzt die Elternreferenz automatisch und ist weniger fehleranfällig. - Lesen
global_transformnach 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_nodesführt zu unvorhersehbarem Verhalten. Sammeln Sie zuerst die Knoten, dann ändern Sie sie. - Steuerpunkte verwenden
Vector4, nichtVector3. Immer übergebenw=1für gewöhnliche Scheitelpunktpositionen;w=0repräsentiert einen Richtungsvektor (kein Punkt). mesh.control_pointsgibt eine Kopie zurück. Dercontrol_pointsEigenschaft gibt zurücklist(self._control_points)— das Anhängen an die zurückgegebene Liste verändert das Mesh nicht. Immer anhängen anmesh._control_pointsdirekt, wenn Geometrie programmgesteuert erstellt wird. Dies ist eine bekannte Bibliotheksbeschränkung; eine öffentliche Mutations-API existiert noch nicht.
Häufige Probleme
| Problem | Lö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 translation | transform.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 laden | Einige 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 None | Der 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). |