Hoe een 3D‑scènegraph te traverseren in Python

Hoe een 3D‑scènegraph te traverseren in Python

De scenegraph in Aspose.3D FOSS is een boom van Node objecten met wortel bij scene.root_node. Elk 3D‑bestand, of het nu geladen wordt vanuit OBJ, glTF, STL, COLLADA of 3MF, produceert dezelfde boomstructuur. Weten hoe je deze kunt doorlopen stelt je in staat om geometrie te inspecteren, polygonen te tellen, objecten op type te filteren en specifieke delen van een complexe scène te verwerken.

Stapsgewijze handleiding

Stap 1: Installeren en importeren

Installeer Aspose.3D FOSS vanaf PyPI:

pip install aspose-3d-foss

Importeer de klassen die je nodig hebt:

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

Alle publieke klassen bevinden zich onder aspose.threed of zijn sub‑packages (aspose.threed.entities, aspose.threed.utilities).


Stap 2: Laad een scene vanuit een bestand

Gebruik de statische Scene.from_file() methode om elk ondersteund formaat te openen. Het formaat wordt automatisch gedetecteerd aan de hand van de bestandsextensie:

scene = Scene.from_file("model.gltf")

Je kunt ook openen met expliciete laadopties:

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

options = ObjLoadOptions()
options.enable_materials = True

scene = Scene()
scene.open("model.obj", options)

Na het laden, scene.root_node is de wortel van de boom. Alle geïmporteerde knooppunten zijn kinderen of afstammelingen van dit knooppunt.


Stap 3: Schrijf een recursieve traversatiefunctie

De eenvoudigste traversie bezoekt elk knooppunt in diepte‑eerste volgorde:

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)

Voorbeeldoutput:

[-]
  [-] 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=“Kopieer code”

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

Opmerking: Het wortelknooppunt heeft een lege naam (""), dus de eerste regel toont [-] zonder daaropvolgende naam.

node.child_nodes geeft kinderen terug in de invoervolgorde (de volgorde waarin de importeur of de gebruiker ze heeft toegevoegd).


Stap 4: Toegang tot entiteitseigenschappen op elk knooppunt

Gebruik node.entity om de eerste entiteit die aan een knooppunt is gekoppeld op te halen, of node.entities om ze allemaal te itereren. Controleer het type voordat je format‑specifieke eigenschappen benadert:

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, en node.transform.scaling geven de lokale‑ruimte transformatie. node.global_transform geeft het geëvalueerde wereld‑ruimte resultaat (met global_transform.scale voor wereldruimteschaal).


Stap 5: Filter knooppunten op entiteitstype

Om alleen op specifieke entiteitstypen te werken, voeg een isinstance guard binnen de traversie:

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}")

Dit patroon is nuttig voor bulkbewerkingen: een transformatie toepassen op elk mesh‑knooppunt, materialen vervangen, of sub‑bomen exporteren.


Stap 6: Verzamel alle meshes en print vertex‑aantallen

Aggregeer mesh‑statistieken in één enkele pass:

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

Veelvoorkomende problemen

ProbleemOplossing
AttributeError: 'NoneType' object has no attribute 'polygons'Guard met if node.entity is not None of isinstance(node.entity, Mesh) voordat mesh-eigenschappen worden benaderd. Nodes zonder entiteiten retourneren None.
Traversie stopt vroegZorg ervoor dat de recursie doordringt tot node.child_nodes. Als je alleen iterereert scene.root_node.child_nodes (niet recursief), mis je alle afstammelingen.
Mesh ontbreekt in verzamelde resultatenControleer of het bestandsformaat de hiërarchie behoudt. OBJ vlakt alle geometrie af tot één knooppuntniveau. Gebruik glTF of COLLADA voor diepe hiërarchieën.
node.entity retourneert alleen de eerste entiteitGebruik node.entities (de volledige lijst) wanneer een node meerdere entiteiten draagt. node.entity is een afkorting voor node.entities[0] wanneer entities is niet leeg.
collect_meshes geeft 0 resultaten terug voor een geladen STLSTL‑bestanden produceren doorgaans één platte node met één mesh‑entiteit direct eronder root_node. Controleer root_node.child_nodes[0].entity direct.
Node-namen zijn lege stringsSommige formaten (binaire STL, sommige OBJ‑bestanden) slaan geen objectnamen op. Nodes zullen lege name strings; gebruik in plaats daarvan de index voor identificatie.

Veelgestelde vragen

Hoe diep kan een scene‑graph zijn?

Er is geen harde limiet. De standaard recursielimiet van Python (1000 frames) geldt voor recursieve traversiefuncties. Voor zeer diepe hiërarchieën, zet de recursie om naar een expliciete stack:

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

Kan ik de boom wijzigen tijdens het traverseren?

Voeg geen nodes toe of verwijder ze uit child_nodes tijdens het itereren ervan. Verzamel de nodes die moeten worden aangepast in een eerste pass, en pas de wijzigingen vervolgens toe in een tweede pass.

Hoe vind ik een specifiek knooppunt op naam?

Gebruik node.get_child(name) om een direct kind op naam te vinden. Voor een diepe zoekopdracht, doorloop de boom met een naamfilter:

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

Geeft node.entity altijd een Mesh terug?

Nee. Een node kan elk entiteitstype bevatten: Mesh, Camera, Light, of aangepaste entiteiten. Controleer altijd met isinstance(node.entity, Mesh) voordat u mesh‑specifieke eigenschappen gebruikt.

Hoe krijg ik de positie in wereldruimte van een knooppunt?

Lezen node.global_transform.translation. Dit is de geëvalueerde positie in de wereldruimte, rekening houdend met alle vooroudertransformaties. Het is alleen-lezen; wijzig node.transform.translation om de node te verplaatsen.

Kan ik het totale aantal polygonen in een scene tellen zonder een traversal te schrijven?

Niet rechtstreeks via de API; er is geen scene.total_polygon_count eigenschap. Gebruik collect_meshes en som mesh.polygon_count over de resultaten, zoals weergegeven in Stap 6.

 Nederlands