Cum să parcurgi un graf de scenă 3D în Python

Cum să parcurgi un graf de scenă 3D în Python

Graful de scenă în Aspose.3D FOSS este un arbore de Node obiecte cu rădăcina la scene.root_node. Fiecare fișier 3D, indiferent dacă este încărcat din OBJ, glTF, STL, COLLADA sau 3MF, produce aceeași structură de arbore. Cunoașterea modului de a-l parcurge vă permite să inspectați geometria, să numărați poligoanele, să filtrați obiectele după tip și să procesați părți specifice ale unei scene complexe.

Ghid pas cu pas

Pasul 1: Instalați și importați

Instalați Aspose.3D FOSS de pe PyPI:

pip install aspose-3d-foss

Importați clasele de care aveți nevoie:

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

Toate clasele publice se află sub aspose.threed sau sub-pachetele sale (aspose.threed.entities, aspose.threed.utilities).


Pasul 2: Încărcați o scenă dintr-un fișier

Folosiți metoda statică Scene.from_file() pentru a deschide orice format suportat. Formatul este detectat automat din extensia fișierului:

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

De asemenea, puteți deschide cu opțiuni explicite de încărcare:

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

options = ObjLoadOptions()
options.enable_materials = True

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

După încărcare, scene.root_node este rădăcina arborelui. Toate nodurile importate sunt copii sau descendenți ai acestui nod.


Pasul 3: Scrieți o funcție recursivă de traversare

Cea mai simplă traversare vizitează fiecare nod în ordine adâncime-primeiro (depth-first):

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)

Exemplu de ieșire:

[-]
  [-] 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=“Copiază codul”

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

Notă: Nodul rădăcină are un nume gol (""), astfel încât prima linie să afișeze [-] fără niciun nume ulterior.

node.child_nodes returnează copiii în ordinea inserării (ordinea în care importatorul sau utilizatorul i-a adăugat).


Pasul 4: Accesați proprietățile entității pentru fiecare nod

Folosiți node.entity pentru a obține prima entitate atașată unui nod, sau node.entities pentru a itera prin toate. Verificați tipul înainte de a accesa proprietățile specifice formatului:

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 oferă transformarea în spațiul local. node.global_transform returnează rezultatul evaluat în spațiul global (cu global_transform.scale pentru scară în spațiul global).


Pasul 5: Filtrarea nodurilor după tipul entității

Pentru a opera doar pe tipuri specifice de entități, adăugați un isinstance verificare în interiorul traversării:

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

Acest model este util pentru operații în masă: aplicarea unei transformări fiecărui nod de tip mesh, înlocuirea materialelor sau exportarea sub-arborilor.


Pasul 6: Colectați toate mesh-urile și afișați numărul de vârfuri

Agregați statisticile mesh-urilor într-un singur pas:

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

Probleme comune

ProblemăRezolvare
AttributeError: 'NoneType' object has no attribute 'polygons'Verificare cu if node.entity is not None sau isinstance(node.entity, Mesh) înainte de a accesa proprietățile mesh. Nodurile fără entități returnează None.
Traversarea se oprește devremeAsigurați-vă că recursiunea ajunge în node.child_nodes. Dacă iterați doar scene.root_node.child_nodes (nu recursiv), pierdeți toți descendenții.
Mesh lipsă din rezultatele colectateVerificați că formatul de fișier păstrează ierarhia. OBJ aplatizează toată geometria la un singur nivel de nod. Utilizați glTF sau COLLADA pentru ierarhii profunde.
node.entity returnează doar prima entitateUtilizați node.entities (lista completă) când un nod conține multiple entități. node.entity este o prescurtare pentru node.entities[0] când entities nu este goală.
collect_meshes returnează 0 rezultate pentru un STL încărcatFișierele STL produc de obicei un singur nod plat cu o entitate mesh direct sub root_node. Verificați root_node.child_nodes[0].entity direct.
Numele nodurilor sunt șiruri goaleUnele formate (STL binar, unele fișiere OBJ) nu stochează numele obiectelor. Nodurile vor avea șiruri goale name șiruri; folosiți indexul pentru identificare în schimb.

Întrebări frecvente

Cât de adânc poate fi un graf de scenă?

Nu există o limită strictă. Limita implicită de recursivitate a Python (1000 de cadre) se aplică funcțiilor de traversare recursivă. Pentru ierarhii foarte adânci, convertiți recursiunea într-un stivă explicită:

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

Pot modifica arborele în timpul traversării?

Nu adăugați sau eliminați noduri din child_nodes în timpul iterării acestuia. Colectați nodurile de modificat într-un prim pas, apoi aplicați modificările într-un al doilea pas.

Cum găsesc un nod specific după nume?

Utilizați node.get_child(name) pentru a găsi un copil direct după nume. Pentru o căutare profundă, parcurgeți arborele cu un filtru de nume:

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

Întoarce node.entity întotdeauna returnează un Mesh?

Nu. Un nod poate conține orice tip de entitate: Mesh, Camera, Light, sau entități personalizate. Verificați întotdeauna cu isinstance(node.entity, Mesh) înainte de a utiliza proprietăți specifice mesh-ului.

Cum obțin poziția în spațiul mondial a unui nod?

Citește node.global_transform.translation. Aceasta este poziția evaluată în spațiul lumii, ținând cont de toate transformările strămoșilor. Este doar în citire; modifică node.transform.translation pentru a repoziționa nodul.

Pot să număr totalul de poligoane dintr-o scenă fără să scriu o traversare?

Nu direct prin API; nu există scene.total_polygon_count proprietate. Folosește collect_meshes și însumează mesh.polygon_count pe parcursul rezultatelor, așa cum se arată în Pasul 6.

 Română