Jak procházet 3D scénový graf v Python

Jak procházet 3D scénový graf v Python

Scénový graf v Aspose.3D FOSS je strom Node objektů s kořenem v scene.root_node. Každý 3D soubor, ať už načtený z OBJ, glTF, STL, COLLADA nebo 3MF, vytváří stejnou stromovou strukturu. Znalost toho, jak ji procházet, vám umožní prohlížet geometrii, počítat polygonů, filtrovat objekty podle typu a zpracovávat konkrétní části složité scény.

Průvodce krok za krokem

Krok 1: Instalace a import

Nainstalujte Aspose.3D FOSS z PyPI:

pip install aspose-3d-foss

Importujte třídy, které potřebujete:

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

Všechny veřejné třídy se nacházejí pod aspose.threed nebo v jejích podbalíčcích (aspose.threed.entities, aspose.threed.utilities).


Krok 2: Načtení scény ze souboru

Použijte statickou Scene.from_file() metodu k otevření libovolného podporovaného formátu. Formát je detekován automaticky z přípony souboru:

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

Můžete také otevřít s explicitními možnostmi načtení:

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

options = ObjLoadOptions()
options.enable_materials = True

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

Po načtení, scene.root_node je kořenem stromu. Všechny importované uzly jsou potomky nebo následníky tohoto uzlu.


Krok 3: Napište rekurzivní funkci pro procházení

Nejjednodušší procházení navštíví každý uzel v pořadí hloubkového průchodu:

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)

Příklad výstupu:

[-]
  [-] 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=“Zkopírovat kód”

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

Poznámka: Kořenový uzel má prázdný název (""), takže první řádek zobrazuje [-] bez následujícího názvu.

node.child_nodes vrací potomky v pořadí vložení (v pořadí, v jakém je importér nebo uživatel přidal).


Krok 4: Přístup k vlastnostem entit na každém uzlu

Použijte node.entity k získání první entity připojené k uzlu, nebo node.entities pro iteraci všech. Zkontrolujte typ před přístupem k vlastnostem specifickým pro formát:

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, a node.transform.scaling poskytuje transformaci v lokálním prostoru. node.global_transform dává vyhodnocený výsledek ve světovém prostoru (s global_transform.scale pro měřítko ve světovém prostoru).


Krok 5: Filtrovat uzly podle typu entity

Pro operaci pouze s konkrétními typy entit přidejte isinstance ochranný prvek uvnitř průchodu:

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

Tento vzor je užitečný pro hromadné operace: aplikování transformace na každý uzel s mesh, nahrazování materiálů nebo export podstromů.


Krok 6: Shromáždit všechny meshe a vypsat počty vrcholů

Agregovat statistiky mesh v jediném průchodu:

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

Časté problémy

ProblémŘešení
AttributeError: 'NoneType' object has no attribute 'polygons'Ochrana pomocí if node.entity is not None nebo isinstance(node.entity, Mesh) před přístupem k vlastnostem mesh. Uzly bez entit vrací None.
Průchod se zastaví předčasněZajistěte, aby rekurze zasáhla do node.child_nodes. Pokud iterujete pouze scene.root_node.child_nodes (ne rekurzivně), přicházíte o všechny potomky.
Mesh chybí ve shromážděných výsledcíchZkontrolujte, že formát souboru zachovává hierarchii. OBJ zplošťuje veškerou geometrii na úroveň jediného uzlu. Použijte glTF nebo COLLADA pro hluboké hierarchie.
node.entity vrací pouze první entituPoužijte node.entities (úplný seznam) když uzel nese více entit. node.entity je zkratkou pro node.entities[0] když entities není prázdný.
collect_meshes vrací 0 výsledků pro načtený STLSoubory STL obvykle vytvářejí jeden plochý uzel s jednou mesh entitou přímo pod ním root_node. Zkontrolujte root_node.child_nodes[0].entity přímo.
Názvy uzlů jsou prázdné řetězceNěkteré formáty (binární STL, některé soubory OBJ) neukládají názvy objektů. Uzly budou mít prázdné name řetězce; použijte místo toho index pro identifikaci.

Často kladené otázky

Jak hluboký může být scénový graf?

Neexistuje pevný limit. Výchozí limit rekurze Python (1000 snímků) se vztahuje na rekurzivní funkce pro průchod. Pro velmi hluboké hierarchie převěďte rekurzi na explicitní zásobník:

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

Mohu upravovat strom během jeho průchodu?

Nepřidávejte ani neodstraňujte uzly z child_nodes během iterace. Shromážděte uzly k úpravě v prvním průchodu, pak aplikujte změny ve druhém průchodu.

Jak najdu konkrétní uzel podle jména?

Použijte node.get_child(name) k nalezení přímého potomka podle názvu. Pro hluboké hledání projděte strom s filtrem názvu:

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

Vrací node.entity vždy vrací Mesh?

Ne. Uzel může obsahovat libovolný typ entity: Mesh, Camera, Light, nebo vlastní entity. Vždy zkontrolujte pomocí isinstance(node.entity, Mesh) před použitím vlastností specifických pro mesh.

Jak získám pozici uzlu ve světovém prostoru?

Číst node.global_transform.translation. Toto je vyhodnocená pozice ve světovém prostoru, zohledňující všechny nadřazené transformace. Je jen pro čtení; upravte node.transform.translation k přemístění uzlu.

Mohu spočítat celkový počet polygonů ve scéně, aniž bych psal průchod?

Ne přímo přes API; neexistuje scene.total_polygon_count vlastnost. Použijte collect_meshes a sečtěte mesh.polygon_count napříč výsledky, jak je ukázáno v kroku 6.

 Čeština