Wie man einen 3D‑Szenengraphen in Python traversiert
Der Szenengraph in Aspose.3D FOSS ist ein Baum von Node‑Objekten, dessen Wurzel scene.root_node ist. Jede 3D‑Datei, egal ob aus OBJ, glTF, STL, COLLADA oder 3MF geladen, erzeugt dieselbe Baumstruktur. Wenn man weiß, wie man ihn traversiert, kann man Geometrie inspizieren, Polygone zählen, Objekte nach Typ filtern und bestimmte Teile einer komplexen Szene verarbeiten.
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 dessen 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 ist scene.root_node die Wurzel des Baums. 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)Bitte geben Sie den zu übersetzenden Text an.
[-]
[-] 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 (
""), daher zeigt die erste Zeile[-]ohne nachfolgenden Namen.
node.child_nodes gibt Kinder in Einfügereihenfolge zurück (die Reihenfolge, in der der Importeur 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 durchlaufen. Überprüfen Sie den Typ, bevor Sie formatbezogene Eigenschaften abrufen:
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 liefern die lokale Raum-Transformation. node.global_transform liefert das ausgewertete Welt-Raum-Ergebnis (mit global_transform.scale für die Welt-Raum-Skalierung).
Schritt 5: Knoten nach Entitätstyp filtern
Um nur auf bestimmte Entitätstypen zu operieren, fügen Sie innerhalb der Traversierung einen isinstance‑Guard hinzu:
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: Anwenden einer Transformation auf jeden Mesh‑Knoten, Ersetzen von Materialien oder Exportieren von Unterbäumen.
Schritt 6: Alle Meshes sammeln und Scheitelpunktzahlen ausgeben
Aggregiere Mesh-Statistiken in einem Durchlauf:
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
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Schützen Sie mit if node.entity is not None oder isinstance(node.entity, Mesh), bevor Sie auf Mesh‑Eigenschaften zugreifen. Knoten ohne Entitäten geben None zurück. |
| Traversal stops early | Stellen Sie sicher, dass die Rekursion bis zu node.child_nodes reicht. Wenn Sie nur scene.root_node.child_nodes (nicht rekursiv) iterieren, verpassen Sie alle Nachkommen. |
| Mesh missing from collected results | Überprüfen Sie, ob 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 returns only the first entity | Verwenden Sie node.entities (die vollständige Liste), wenn ein Knoten mehrere Entitäten trägt. node.entity ist eine Kurzform für node.entities[0], wenn entities nicht leer ist. |
collect_meshes returns 0 results for a loaded STL | STL‑Dateien erzeugen typischerweise einen einzelnen flachen Knoten mit einer Mesh‑Entität direkt unter root_node. Überprüfen Sie root_node.child_nodes[0].entity direkt. |
| Node names are empty strings | Einige Formate (binäres STL, einige OBJ‑Dateien) speichern keine Objektnamen. Knoten haben leere name‑Zeichenketten; verwenden Sie stattdessen den Index zur Identifizierung. |
Häufig gestellte Fragen
Wie tief kann ein Szenengraph sein?
Es gibt keine feste Obergrenze. Das standardmäßige Rekursionslimit 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 beim Durchlaufen keine Knoten zu child_nodes hinzu oder entfernen Sie welche. 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 des 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. Überprüfen Sie immer mit isinstance(node.entity, Mesh), bevor Sie mesh‑spezifische Eigenschaften verwenden.
Wie erhalte ich die Welt-Raum-Position eines Knotens?
Lesen Sie node.global_transform.translation. Dies ist die ausgewertete Position im Weltraum, die alle übergeordneten Transformationen berücksichtigt. Sie ist schreibgeschützt; ändern Sie 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 keine scene.total_polygon_count‑Eigenschaft. Verwenden Sie collect_meshes und summieren Sie mesh.polygon_count über die Ergebnisse, wie in Schritt 6 gezeigt.