Hur man traverserar en 3D-scengraf i Python

Hur man traverserar en 3D-scengraf i Python

Scengrafen i Aspose.3D FOSS är ett träd av Node objekt med rot i scene.root_node. Varje 3D-fil, oavsett om den läses in från OBJ, glTF, STL, COLLADA eller 3MF, producerar samma trädstruktur. Att veta hur man traverserar den låter dig inspektera geometri, räkna polygoner, filtrera objekt efter typ och bearbeta specifika delar av en komplex scen.

Steg-för-steg-guide

Steg 1: Installera och importera

Installera Aspose.3D FOSS från PyPI:

pip install aspose-3d-foss

Importera de klasser du behöver:

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

Alla offentliga klasser finns under aspose.threed eller dess underpaket (aspose.threed.entities, aspose.threed.utilities).


Steg 2: Läs in en scen från en fil

Använd den statiska Scene.from_file() metoden för att öppna vilket stödformat som helst. Formatet upptäcks automatiskt från filändelsen:

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

Du kan också öppna med explicita laddningsalternativ:

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

options = ObjLoadOptions()
options.enable_materials = True

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

Efter inläsning, scene.root_node är roten i trädet. Alla importerade noder är barn eller ättlingar till denna nod.


Steg 3: Skriv en rekursiv traverseringsfunktion

Den enklaste traverseringen besöker varje nod i djup‑först‑ordning:

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)

Exempelutdata:

[-]
  [-] 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=“Copy 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>

Obs: Rotnoden har ett tomt namn (""), så den första raden visar [-] utan något namn efter.

node.child_nodes returnerar barn i insättningsordning (den ordning i vilken importören eller användaren lade till dem).


Steg 4: Åtkomst till enhetsegenskaper på varje nod

Använd node.entity för att hämta den första entiteten som är fäst vid en nod, eller node.entities för att iterera alla. Kontrollera typen innan du får åtkomst till format-specifika egenskaper:

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, och node.transform.scaling ger den lokala rymdtransformen. node.global_transform ger det utvärderade världsrumsresultatet (med global_transform.scale för världsrumsskala).


Steg 5: Filtrera noder efter entitetstyp

För att endast arbeta med specifika entitetstyper, lägg till en isinstance guard i traverseringen:

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

Detta mönster är användbart för massoperationer: applicera en transformation på varje mesh-nod, ersätta material eller exportera underträd.


Steg 6: Samla alla meshar och skriv ut vertexantal

Aggregera mesh-statistik i ett enda 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")

Vanliga problem

ProblemLösning
AttributeError: 'NoneType' object has no attribute 'polygons'Guard med if node.entity is not None eller isinstance(node.entity, Mesh) innan du får åtkomst till mesh‑egenskaper. Noder utan entiteter returnerar None.
Traversering stoppar tidigtSäkerställ att rekursionen når in i node.child_nodes. Om du bara itererar scene.root_node.child_nodes (inte rekursivt), missar du alla ättlingar.
Mesh saknas i insamlade resultatKontrollera att filformatet bevarar hierarkin. OBJ plattar ut all geometri till en enda nodnivå. Använd glTF eller COLLADA för djupa hierarkier.
node.entity returnerar endast den första entitetenAnvänd node.entities (den fullständiga listan) när en nod bär flera enheter. node.entity är en förkortning för node.entities[0] när entities är icke-tom.
collect_meshes returnerar 0 resultat för en inläst STLSTL-filer producerar vanligtvis en enda platt nod med en mesh‑entitet direkt under root_node. Kontrollera root_node.child_nodes[0].entity direkt.
Nodnamn är tomma strängarVissa format (binär STL, vissa OBJ-filer) lagrar inte objektnamn. Noder kommer att ha tomma name strängar; använd indexet för identifiering istället.

Vanliga frågor

Hur djup kan en scengraf vara?

Det finns ingen hård gräns. Pythons standardrekursionsgräns (1000 ramar) gäller för rekursiva traverseringsfunktioner. För mycket djupa hierarkier, konvertera rekursionen till en explicit 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 jag modifiera trädet medan jag traverserar det?

Lägg inte till eller ta bort noder från child_nodes under iteration. Samla noderna som ska modifieras i ett första pass, och applicera ändringarna i ett andra pass.

Hur hittar jag en specifik nod med namn?

Använd node.get_child(name) för att hitta ett direkt barn efter namn. För en djup sökning, traversera trädet med ett namnfilter:

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

Returnerar node.entity alltid returnera en Mesh?

Nej. En nod kan innehålla vilken entitetstyp som helst: Mesh, Camera, Light, eller anpassade entiteter. Kontrollera alltid med isinstance(node.entity, Mesh) innan du använder mesh‑specifika egenskaper.

Hur får jag världsrumspositionen för en nod?

Läs node.global_transform.translation. Detta är den utvärderade positionen i världsrummet, med hänsyn till alla förfäders transformationer. Den är skrivskyddad; ändra node.transform.translation för att flytta noden.

Kan jag räkna det totala antalet polygoner i en scen utan att skriva en traversering?

Inte direkt via API:et; det finns ingen scene.total_polygon_count egenskap. Använd collect_meshes och summera mesh.polygon_count över resultaten, som visas i steg 6.

 Svenska