Wie man einen 3D‑Szenengraphen in Python durchläuft
Der Szenengraph in Aspose.3D FOSS ist ein Baum von Node Objekten, die bei scene.root_node. Jede 3D-Datei, egal ob aus OBJ, glTF, STL, COLLADA oder 3MF geladen, erzeugt dieselbe Baumstruktur. Zu wissen, wie man sie traversiert, ermöglicht das Inspizieren von Geometrie, das Zählen von Polygonen, das Filtern von Objekten nach Typ und das Verarbeiten bestimmter Teile einer komplexen Szene.
Schritt-für-Schritt-Anleitung
Schritt 1: Installieren und Importieren
Installieren Sie Aspose.3D FOSS von PyPI:
pip install aspose-3d-fossImportieren Sie die Klassen, die Sie benötigen:
from aspose.threed import Scene
from aspose.threed.entities import MeshAlle öffentlichen Klassen befinden sich unter aspose.threed oder in seinen Unterpaketen (aspose.threed.entities, aspose.threed.utilities).
Schritt 2: Laden einer Szene aus einer Datei
Verwenden Sie die statische Scene.from_file() Methode, um ein beliebiges unterstütztes Format zu öffnen. Das Format wird automatisch anhand der Dateierweiterung erkannt:
scene = Scene.from_file("model.gltf")Sie können auch mit expliziten Ladeoptionen öffnen:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Nach dem Laden, scene.root_node ist die Wurzel des Baumes. Alle importierten Knoten sind Kinder oder Nachkommen dieses Knotens.
Schritt 3: Schreiben einer rekursiven Traversierungsfunktion
Die einfachste Traversierung besucht jeden Knoten in Tiefen‑Erst‑Reihenfolge:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def traverse(node, depth=0):
prefix = " " * depth
entity_name = type(node.entity).__name__ if node.entity else "-"
print(f"{prefix}[{entity_name}] {node.name}")
for child in node.child_nodes:
traverse(child, depth + 1)
scene = Scene.from_file("model.gltf")
traverse(scene.root_node)Beispielausgabe:
[-]
[-] Armature
[Mesh] Body
[Mesh] Eyes
[-] Ground
[Mesh] Plane<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Code kopieren”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
Hinweis: Der Wurzelknoten hat einen leeren Namen (""), sodass die erste Zeile angezeigt wird [-] ohne nachfolgenden Namen.
node.child_nodes gibt Kinder in Einfügereihenfolge zurück (die Reihenfolge, in der der Importer oder der Benutzer sie hinzugefügt hat).
Schritt 4: Zugriff auf Entitätseigenschaften jedes Knotens
Verwenden Sie node.entity um die erste an einen Knoten angehängte Entität zu erhalten, oder node.entities um alle zu iterieren. Prüfen Sie den Typ, bevor Sie format-spezifische Eigenschaften zugreifen:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def print_entity_details(node, depth=0):
indent = " " * depth
for entity in node.entities:
if isinstance(entity, Mesh):
mesh = entity
print(f"{indent}Mesh '{node.name}':")
print(f"{indent} vertices : {len(mesh.control_points)}")
print(f"{indent} polygons : {mesh.polygon_count}")
print(f"{indent} cast_shadows : {mesh.cast_shadows}")
print(f"{indent} receive_shadows: {mesh.receive_shadows}")
for child in node.child_nodes:
print_entity_details(child, depth + 1)
scene = Scene.from_file("model.gltf")
print_entity_details(scene.root_node)node.transform.translation, node.transform.rotation, und node.transform.scaling geben die lokale Raum-Transformation. node.global_transform gibt das ausgewertete Welt-Raum-Ergebnis zurück (mit global_transform.scale für die Skalierung im Welt-Raum).
Schritt 5: Knoten nach Entitätstyp filtern
Um nur mit bestimmten Entitätstypen zu arbeiten, fügen Sie ein isinstance guard innerhalb der Traversierung:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def find_mesh_nodes(node, results=None):
if results is None:
results = []
for entity in node.entities:
if isinstance(entity, Mesh):
results.append(node)
break # One match per node is enough
for child in node.child_nodes:
find_mesh_nodes(child, results)
return results
scene = Scene.from_file("model.gltf")
mesh_nodes = find_mesh_nodes(scene.root_node)
print(f"Found {len(mesh_nodes)} mesh node(s)")
for n in mesh_nodes:
print(f" {n.name}")Dieses Muster ist nützlich für Massenoperationen: eine Transformation auf jeden Mesh‑Knoten anwenden, Materialien ersetzen oder Teilbäume exportieren.
Schritt 6: Alle Meshes sammeln und Vertex‑Anzahlen ausgeben
Mesh‑Statistiken in einem Durchlauf aggregieren:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def collect_meshes(node, results=None):
if results is None:
results = []
if isinstance(node.entity, Mesh):
results.append((node.name, node.entity))
for child in node.child_nodes:
collect_meshes(child, results)
return results
scene = Scene.from_file("model.gltf")
meshes = collect_meshes(scene.root_node)
print(f"Total meshes: {len(meshes)}")
total_verts = 0
total_polys = 0
for name, mesh in meshes:
verts = len(mesh.control_points)
polys = mesh.polygon_count
total_verts += verts
total_polys += polys
print(f" {name}: {verts} vertices, {polys} polygons")
print(f"Scene totals: {total_verts} vertices, {total_polys} polygons")Häufige Probleme
| Problem | Lösung |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guard mit if node.entity is not None oder isinstance(node.entity, Mesh) bevor auf Mesh-Eigenschaften zugegriffen wird. Knoten ohne Entitäten geben zurück None. |
| Traversierung stoppt frühzeitig | Stellen Sie sicher, dass die Rekursion in node.child_nodes. Wenn Sie nur iterieren scene.root_node.child_nodes (nicht rekursiv), verpassen Sie alle Nachkommen. |
| Mesh fehlt in den gesammelten Ergebnissen | Stellen Sie sicher, dass das Dateiformat die Hierarchie beibehält. OBJ flacht alle Geometrie zu einer einzigen Knotenebene ab. Verwenden Sie glTF oder COLLADA für tiefe Hierarchien. |
node.entity gibt nur die erste Entität zurück | Verwenden node.entities (die vollständige Liste), wenn ein Knoten mehrere Entitäten enthält. node.entity ist eine Kurzschreibweise für node.entities[0] wenn entities nicht leer ist. |
collect_meshes liefert 0 Ergebnisse für ein geladenes STL | STL-Dateien erzeugen typischerweise einen einzelnen flachen Knoten mit einer Mesh-Entität direkt darunter root_node. Prüfen root_node.child_nodes[0].entity direkt. |
| Knotennamen sind leere Zeichenketten | Einige Formate (binäres STL, einige OBJ‑Dateien) speichern keine Objektnamen. Knoten werden leere name Zeichenketten haben; verwenden Sie stattdessen den Index zur Identifizierung. |
Häufig gestellte Fragen
Wie tief kann ein Szenengraph sein?
Es gibt keine feste Obergrenze. Die Standard‑Rekursionsgrenze von Python (1000 Frames) gilt für rekursive Traversierungsfunktionen. Bei sehr tiefen Hierarchien sollte die Rekursion in einen expliziten Stack umgewandelt werden:
from collections import deque
from aspose.threed import Scene
scene = Scene.from_file("deep.gltf")
queue = deque([(scene.root_node, 0)])
while queue:
node, depth = queue.popleft()
print(" " * depth + node.name)
for child in node.child_nodes:
queue.append((child, depth + 1))Kann ich den Baum während der Traversierung modifizieren?
Fügen Sie keine Knoten hinzu oder entfernen Sie Knoten aus child_nodes während Sie sie iterieren. Sammeln Sie die zu ändernden Knoten in einem ersten Durchlauf und wenden Sie die Änderungen im zweiten Durchlauf an.
Wie finde ich einen bestimmten Knoten anhand seines Namens?
Verwenden Sie node.get_child(name) um ein direktes Kind nach Namen zu finden. Für eine tiefere Suche traversieren Sie den Baum mit einem Namensfilter:
def find_by_name(root, name):
if root.name == name:
return root
for child in root.child_nodes:
result = find_by_name(child, name)
if result:
return result
return None
target = find_by_name(scene.root_node, "Wheel_FL")Gibt node.entity immer ein Mesh zurück?
Nein. Ein Knoten kann jeden Entitätstyp enthalten: Mesh, Camera, Light, oder benutzerdefinierte Entitäten. Prüfen Sie immer mit isinstance(node.entity, Mesh) bevor mesh-spezifische Eigenschaften verwendet werden.
Wie erhalte ich die Position eines Knotens im Weltkoordinatensystem?
Lesen node.global_transform.translation. Dies ist die ausgewertete Position im Weltraum, wobei alle übergeordneten Transformationen berücksichtigt werden. Sie ist schreibgeschützt; ändern node.transform.translation um den Knoten neu zu positionieren.
Kann ich die Gesamtzahl der Polygone in einer Szene zählen, ohne eine Traversierung zu schreiben?
Nicht direkt über die API; es gibt kein scene.total_polygon_count Eigenschaft. Verwenden Sie collect_meshes und summieren Sie mesh.polygon_count über die Ergebnisse hinweg, wie in Schritt 6 gezeigt.