Wie man einen 3D‑Szenengraphen in Python durchläuft

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

Importieren Sie die Klassen, die Sie benötigen:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

Alle ö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

ProblemLö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ühzeitigStellen 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 ErgebnissenStellen 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ückVerwenden 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 STLSTL-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.
Knoten­namen sind leere ZeichenkettenEinige 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.

 Deutsch