Com recórrer un gràfic d'escena 3D en Python

Com recórrer un gràfic d'escena 3D en Python

El gràfic de escena a Aspose.3D FOSS és un arbre de Node objectes arrelats a scene.root_node. Cada fitxer 3D, ja sigui carregat des d’OBJ, glTF, STL, COLLADA o 3MF, produeix la mateixa estructura d’arbre. Saber com recórrer-lo et permet inspeccionar la geometria, comptar polígons, filtrar objectes per tipus i processar parts específiques d’una escena complexa.

Guia pas a pas

Pas 1: Instal·lar i Importar

Instal·la Aspose.3D FOSS des de PyPI:

pip install aspose-3d-foss

Importa les classes que necessites:

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

Totes les classes públiques es troben a aspose.threed o els seus subpaquets (aspose.threed.entities, aspose.threed.utilities).


Pas 2: Carregar una Escena des d’un Fitxer

Utilitza el estàtic Scene.from_file() mètode per obrir qualsevol format compatible. El format es detecta automàticament a partir de l’extensió del fitxer:

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

També pots obrir amb opcions de càrrega explícites:

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

options = ObjLoadOptions()
options.enable_materials = True

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

Després de carregar, scene.root_node és l’arrel de l’arbre. Tots els nodes importats són fills o descendents d’aquest node.


Pas 3: Escriure una Funció de Recórrer Recursiva

El recórrer més senzill visita cada node en ordre de profunditat primer:

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)

Sortida d’exemple:

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

Nota: El node arrel té un nom buit (""), de manera que la primera línia mostra [-] sense cap nom a continuació.

node.child_nodes retorna els fills en l’ordre d’inserció (l’ordre en què l’importador o l’usuari els va afegir).


Pas 4: Accedir a les Propietats de l’Entitat a Cada Node

Utilitza node.entity per obtenir la primera entitat adjunta a un node, o node.entities per iterar totes elles. Comproveu el tipus abans d’accedir a les propietats específiques del format:

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, i node.transform.scaling donen la transformació en l’espai local. node.global_transform dóna el resultat avaluat en l’espai mundial (amb global_transform.scale per a l’escala en l’espai mundial).


Pas 5: Filtrar nodes per tipus d’entitat

Per operar només amb tipus d’entitat específics, afegiu un isinstance guarda dins del recorregut:

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

Aquest patró és útil per a operacions massives: aplicar una transformació a cada node de malla, substituir materials o exportar subarbres.


Pas 6: Recollir totes les malles i imprimir el recompte de vèrtexs

Agrega les estadístiques de les malles en una sola passada:

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

Problemes comuns

ProblemaResolució
AttributeError: 'NoneType' object has no attribute 'polygons'Guarda amb if node.entity is not None o isinstance(node.entity, Mesh) abans d’accedir a les propietats de la malla. Els nodes sense entitats retornen None.
El recorregut s’atura aviatAssegura que la recursió arribi a node.child_nodes. Si només iteres scene.root_node.child_nodes (sense recursivitat), perds tots els descendents.
Malla absent dels resultats recollitsComproveu que el format de fitxer conserva la jerarquia. OBJ aplaça tota la geometria en un únic nivell de node. Utilitzeu glTF o COLLADA per a jerarquies profundes.
node.entity retorna només la primera entitatUtilitzeu node.entities (la llista completa) quan un node conté múltiples entitats. node.entity és una abreviatura de node.entities[0] quan entities no és buit.
collect_meshes retorna 0 resultats per a un STL carregatEls fitxers STL solen produir un únic node pla amb una entitat de malla directament sota root_node. Comproveu root_node.child_nodes[0].entity directament.
Els noms dels nodes són cadenes buidesAlguns formats (STL binari, alguns fitxers OBJ) no emmagatzemen els noms d’objecte. Els nodes tindran buides name cadenes; utilitzeu l’índex per a la identificació en el seu lloc.

Preguntes freqüents

Quina profunditat pot tenir un gràfic d’escena?

No hi ha cap límit estricte. El límit de recursió per defecte de Python (1000 fotogrames) s’aplica a les funcions de recorregut recursiu. Per a jerarquies molt profundes, converteix la recursió en una pila explícita:

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

Puc modificar l’arbre mentre el recorro?

No afegiu ni elimineu nodes de child_nodes mentre l’itereu. Recopileu els nodes a modificar en una primera passada, i després apliqueu els canvis en una segona passada.

Com trobo un node específic per nom?

Utilitzeu node.get_child(name) per trobar un fill directe pel nom. Per a una cerca profunda, recorreu l’arbre amb un filtre de nom:

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

Retorna node.entity sempre retorna un Mesh?

No. Un node pot contenir qualsevol tipus d’entitat: Mesh, Camera, Light, o entitats personalitzades. Comproveu sempre amb isinstance(node.entity, Mesh) abans d’utilitzar propietats específiques de la malla.

Com obtinc la posició en espai mundial d’un node?

Llegeix node.global_transform.translation. Aquesta és la posició avaluada en l’espai mundial, tenint en compte totes les transformacions dels avantpassats. És de només lectura; modifica node.transform.translation per reposicionar el node.

Puc comptar el total de polígons en una escena sense escriure un recorregut?

No directament a través de l’API; no hi ha cap scene.total_polygon_count propietat. Utilitza collect_meshes i suma mesh.polygon_count entre els resultats, tal com es mostra al Pas 6.

 Català