Sådan traverserer du en 3D‑scengraf i Python

Sådan traverserer du en 3D‑scengraf i Python

Scenegrafen i Aspose.3D FOSS er et træ af Node objekter rodfæstet ved scene.root_node. Hver 3D-fil, uanset om den er indlæst fra OBJ, glTF, STL, COLLADA eller 3MF, producerer den samme træstruktur. At vide, hvordan man traverserer den, giver dig mulighed for at inspicere geometri, tælle polygoner, filtrere objekter efter type og behandle specifikke dele af en kompleks scene.

Trin-for-trin guide

Trin 1: Installér og importér

Installér Aspose.3D FOSS fra PyPI:

pip install aspose-3d-foss

Importér de klasser, du har brug for:

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

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


Trin 2: Indlæs en scene fra en fil

Brug den statiske Scene.from_file() metode til at åbne ethvert understøttet format. Formatet opdages automatisk ud fra filendelsen:

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

Du kan også åbne med eksplicitte indlæsningsindstillinger:

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 indlæsning, scene.root_node er roden af træet. Alle importerede noder er børn eller efterkommere af denne node.


Trin 3: Skriv en rekursiv traverseringsfunktion

Den simpleste traversering besøger hver node i dybde‑først rækkefø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)

Eksempeloutput:

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

Bemærk: Rodnoden har et tomt navn (""), så den første linje viser [-] uden efterfølgende navn.

node.child_nodes returnerer børn i indsættelsesrækkefølge (den rækkefølge, som importøren eller brugeren tilføjede dem i).


Trin 4: Få adgang til entitets‑egenskaber på hver node

Brug node.entity for at hente den første entitet knyttet til en node, eller node.entities for at iterere over dem alle. Tjek typen, før du får adgang til format-specifikke egenskaber:

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 giver den lokale rumtransform. node.global_transform giver det evaluerede verdensrumresultat (med global_transform.scale for verdensrumsskala).


Trin 5: Filtrer noder efter entitetstype

For kun at arbejde på specifikke entitetstyper, tilføj en isinstance vagt inden for gennemløbet:

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ønster er nyttigt til masseoperationer: anvende en transformation på hver mesh-node, erstatte materialer eller eksportere undertræer.


Trin 6: Indsaml alle mesh’er og udskriv antal vertexer

Aggreger mesh-statistik i et enkelt gennemløb:

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

Almindelige problemer

ProblemLøsning
AttributeError: 'NoneType' object has no attribute 'polygons'Vagt med if node.entity is not None eller isinstance(node.entity, Mesh) før du får adgang til mesh-egenskaber. Noder uden entiteter returnerer None.
Gennemløb stopper tidligtSørg for at rekursionen når ind i node.child_nodes. Hvis du kun itererer scene.root_node.child_nodes (ikke rekursivt), går du glip af alle efterkommere.
Mesh mangler i indsamlede resultaterKontroller at filformatet bevarer hierarkiet. OBJ flader al geometri ud til et enkelt nodeniveau. Brug glTF eller COLLADA til dybe hierarkier.
node.entity returnerer kun den første entitetBrug node.entities (den fulde liste) når en node bærer flere enheder. node.entity er en forkortelse for node.entities[0] når entities er ikke-tom.
collect_meshes returnerer 0 resultater for en indlæst STLSTL-filer producerer typisk en enkelt flad node med én mesh-enhed direkte under root_node. Tjek root_node.child_nodes[0].entity direkte.
Node-navne er tomme strengeNogle formater (binary STL, nogle OBJ-filer) gemmer ikke objektnavne. Noder vil have tomme name strenge; brug i stedet indekset til identifikation.

Ofte stillede spørgsmål

Hvor dyb kan en scenegraph være?

Der er ingen fast grænse. Pythons standardrekursionsgrænse (1000 rammer) gælder for rekursive gennemløbsfunktioner. For meget dybe hierarkier, konverter rekursionen til en eksplicit stak:

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 ændre træet, mens jeg gennemløber det?

Tilføj eller fjern ikke noder fra child_nodes mens du itererer over den. Saml noderne, der skal modificeres, i et første gennemløb, og anvend derefter ændringer i et andet gennemløb.

Hvordan finder jeg en specifik node efter navn?

Brug node.get_child(name) til at finde et direkte barn efter navn. For en dyb søgning, gennemløb træet 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")

Gør node.entity altid returnere en Mesh?

Nej. En node kan indeholde enhver entitetstype: Mesh, Camera, Light, eller brugerdefinerede entiteter. Tjek altid med isinstance(node.entity, Mesh) før du bruger mesh-specifikke egenskaber.

Hvordan får jeg verdensrumpositionen for en node?

Læs node.global_transform.translation. Dette er den evaluerede position i verdensrummet, der tager højde for alle forældretransformeringer. Den er skrivebeskyttet; modificer node.transform.translation for at flytte noden.

Kan jeg tælle det samlede antal polygoner i en scene uden at skrive en traversal?

Ikke direkte gennem API’et; der er ingen scene.total_polygon_count egenskab. Brug collect_meshes og sum mesh.polygon_count på tværs af resultaterne, som vist i trin 6.

 Dansk