Wie man einen 3D‑Szenengraphen in Python traversiert

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

IssueResolution
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 earlyStellen 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 entityVerwenden 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 STLSTL‑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 stringsEinige 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.

 Deutsch