Hvordan traversere en 3D-scengraf i Python

Hvordan traversere en 3D-scengraf i Python

Scenegrafen i Aspose.3D FOSS er et tre av Node objekter med roten i scene.root_node. Hver 3D‑fil, enten den er lastet fra OBJ, glTF, STL, COLLADA eller 3MF, produserer den samme trestrukturen. Å vite hvordan man traverserer den lar deg inspisere geometri, telle polygoner, filtrere objekter etter type, og behandle spesifikke deler av en kompleks scene.

Steg-for-steg guide

Steg 1: Installer og importer

Installer Aspose.3D FOSS fra PyPI:

pip install aspose-3d-foss

Importer klassene du trenger:

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

Alle offentlige klasser finnes under aspose.threed eller dets underpakker (aspose.threed.entities, aspose.threed.utilities).


Steg 2: Last inn en scene fra en fil

Bruk den statiske Scene.from_file() metoden for å åpne ethvert støttet format. Formatet oppdages automatisk fra filendelsen:

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

Du kan også åpne med eksplisitte lastingsalternativer:

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

options = ObjLoadOptions()
options.enable_materials = True

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

Etter lasting, scene.root_node er roten i treet. Alle importerte noder er barn eller etterkommere av denne noden.


Steg 3: Skriv en rekursiv traverseringsfunksjon

Den enkleste traverseringen besøker hver node i dybde‑først rekkefølge:

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)

Eksempel på output:

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

Merk: Rotnoden har et tomt navn (""), så den første linjen viser [-] uten noe navn etter.

node.child_nodes returnerer barn i innsettingsrekkefølge (rekkefølgen som importøren eller brukeren la dem til i).


Steg 4: Få tilgang til entitetsegenskaper på hver node

Bruk node.entity for å hente den første enheten som er festet til en node, eller node.entities for å iterere gjennom alle. Sjekk typen før du får tilgang til formatspesifikke 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, og node.transform.scaling gir den lokale romtransformasjonen. node.global_transform gir det evaluerte verdensrom-resultatet (med global_transform.scale for world-space scale).


Steg 5: Filtrer noder etter entitetstype

For å operere kun på spesifikke entitetstyper, legg til en isinstance guard inne 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}")

Dette mønsteret er nyttig for masseoperasjoner: anvende en transformasjon på hver mesh-node, erstatte materialer, eller eksportere undertrær.


Steg 6: Samle alle mesh-er og skriv ut antall vertexer

Aggreger mesh-statistikk i ett enkelt 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")

Vanlige problemer

ProblemLøsning
AttributeError: 'NoneType' object has no attribute 'polygons'Guard med if node.entity is not None eller isinstance(node.entity, Mesh) før du får tilgang til mesh-egenskaper. Noder uten entiteter returnerer None.
Traversering stopper tidligSørg for at rekursjonen når inn i node.child_nodes. Hvis du kun itererer scene.root_node.child_nodes (ikke rekursivt), går du glipp av alle etterkommere.
Mesh mangler i innsamlede resultaterSjekk at filformatet bevarer hierarkiet. OBJ flater ut all geometri til ett enkelt nodenivå. Bruk glTF eller COLLADA for dype hierarkier.
node.entity returnerer kun den første entitetenBruk node.entities (den fullstendige listen) når en node har flere enheter. node.entity er en forkortelse for node.entities[0] når entities er ikke tom.
collect_meshes returnerer 0 resultater for en lastet STLSTL-filer produserer vanligvis en enkelt flat node med én mesh‑entitet direkte under root_node. Sjekk root_node.child_nodes[0].entity direkte.
Node-navn er tomme strengerNoen formater (binary STL, noen OBJ-filer) lagrer ikke objektnavn. Noder vil ha tomme name strenger; bruk indeksen for identifikasjon i stedet.

Ofte stilte spørsmål

Hvor dypt kan en scenegraph være?

Det finnes ingen fast grense. Pythons standard rekursjonsgrense (1000 rammer) gjelder for rekursive traverseringsfunksjoner. For svært dype hierarkier, konverter rekursjonen til en eksplisitt 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 jeg endre treet mens jeg traverserer det?

Ikke legg til eller fjern noder fra child_nodes mens du itererer over den. Samle nodene som skal endres i et første pass, og anvend endringene i et andre pass.

Hvordan finner jeg en spesifikk node etter navn?

Bruk node.get_child(name) for å finne et direkte barn etter navn. For et dypt søk, traverser treet med et navnefilter:

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

Returnerer node.entity alltid returnerer en Mesh?

Nei. En node kan inneholde hvilken som helst entitetstype: Mesh, Camera, Light, eller egendefinerte enheter. Sjekk alltid med isinstance(node.entity, Mesh) før du bruker mesh-spesifikke egenskaper.

Hvordan får jeg verdensrom-posisjonen til en node?

Les node.global_transform.translation. Dette er den evaluerte posisjonen i verdensrommet, med hensyn til alle foreldertransformasjoner. Den er skrivebeskyttet; endre node.transform.translation for å reposisjonere noden.

Kan jeg telle total antall polygoner i en scene uten å skrive en traversering?

Ikke direkte via API-et; det finnes ingen scene.total_polygon_count egenskap. Bruk collect_meshes og summer mesh.polygon_count på tvers av resultatene, som vist i trinn 6.

 Norsk