Como percorrer um grafo de cena 3D em Python

Como percorrer um grafo de cena 3D em Python

O grafo de cena em Aspose.3D FOSS é uma árvore de Node objetos enraizados em scene.root_node.Todo arquivo 3D, seja carregado a partir de OBJ, glTF, STL, COLLADA ou 3MF, produz a mesma estrutura de árvore. Saber como percorrê-la permite inspecionar a geometria, contar polígonos, filtrar objetos por tipo e processar partes específicas de uma cena complexa.

Guia passo a passo

Etapa 1: Instalar e Importar

Instale Aspose.3D FOSS a partir do PyPI:

pip install aspose-3d-foss

Importe as classes que você precisa:

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

Todas as classes públicas estão em aspose.threed ou em seus subpacotes (aspose.threed.entities, aspose.threed.utilities).


Etapa 2: Carregar uma Cena a partir de um Arquivo

Use o método estático Scene.from_file() para abrir qualquer formato suportado. O formato é detectado automaticamente a partir da extensão do arquivo:

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

Você também pode abrir com opções de carregamento explícitas:

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

options = ObjLoadOptions()
options.enable_materials = True

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

Após o carregamento, scene.root_node é a raiz da árvore. Todos os nós importados são filhos ou descendentes deste nó.


Etapa 3: Escrever uma Função de Traversão Recursiva

A travessia mais simples visita cada nó em ordem de profundidade primeiro:

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)

Saída de exemplo:

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

Observação: O nó raiz tem um nome vazio (""), então a primeira linha mostra [-] sem nome a seguir.

node.child_nodes retorna os filhos na ordem de inserção (a ordem em que o importador ou o usuário os adicionou).


Etapa 4: Acessar Propriedades da Entidade em Cada Nó

Use node.entity para obter a primeira entidade anexada a um nó, ou node.entities para iterar todas elas. Verifique o tipo antes de acessar propriedades específicas do formato:

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, e node.transform.scaling fornecem a transformação em espaço local. node.global_transform fornece o resultado avaliado em espaço mundial (com global_transform.scale para escala em espaço mundial).


Etapa 5: Filtrar nós por tipo de entidade

Para operar apenas em tipos específicos de entidade, adicione um isinstance guarda dentro da travessia:

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

Esse padrão é útil para operações em lote: aplicar uma transformação a cada nó de malha, substituir materiais ou exportar subárvores.


Etapa 6: Coletar todas as malhas e imprimir contagens de vértices

Agregue estatísticas de malha em uma única passagem:

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

Problemas Comuns

ProblemaResolução
AttributeError: 'NoneType' object has no attribute 'polygons'Guarda com if node.entity is not None ou isinstance(node.entity, Mesh) antes de acessar as propriedades da malha. Nós sem entidades retornam None.
A travessia para cedoGaranta que a recursão alcance node.child_nodes. Se você iterar apenas scene.root_node.child_nodes (não recursivamente), você perde todos os descendentes.
Malha ausente dos resultados coletadosVerifique se o formato de arquivo preserva a hierarquia. OBJ achata toda a geometria em um único nível de nó. Use glTF ou COLLADA para hierarquias profundas.
node.entity retorna apenas a primeira entidadeUse node.entities (a lista completa) quando um nó contém múltiplas entidades. node.entity é uma abreviação de node.entities[0] quando entities não está vazio.
collect_meshes retorna 0 resultados para um STL carregadoArquivos STL normalmente produzem um único nó plano com uma entidade de malha diretamente abaixo root_node. Verifique root_node.child_nodes[0].entity diretamente.
Os nomes dos nós são strings vaziasAlguns formatos (binary STL, alguns arquivos OBJ) não armazenam nomes de objetos. Os nós terão strings vazias name strings; use o índice para identificação em vez disso.

Perguntas Frequentes

Quão profundo pode ser um grafo de cena?

Não há limite rígido. O limite de recursão padrão de Python (1000 quadros) se aplica às funções de travessia recursiva. Para hierarquias muito profundas, converta a recursão para uma pilha 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))

Posso modificar a árvore enquanto a percorro?

Não adicione ou remova nós de child_nodes enquanto o itera. Colete os nós a serem modificados em uma primeira passagem, depois aplique as alterações em uma segunda passagem.

Como encontro um nó específico pelo nome?

Use node.get_child(name) para encontrar um filho direto pelo nome. Para uma busca profunda, percorra a árvore com um filtro de nome:

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 um Mesh?

Não. Um nó pode conter qualquer tipo de entidade: Mesh, Camera, Light, ou entidades personalizadas. Sempre verifique com isinstance(node.entity, Mesh) antes de usar propriedades específicas da malha.

Como obtenho a posição no espaço mundial de um nó?

Ler node.global_transform.translation. Esta é a posição avaliada no espaço mundial, levando em conta todas as transformações dos ancestrais. É somente leitura; modifique node.transform.translation para reposicionar o nó.

Posso contar o total de polígonos em uma cena sem escrever um traversal?

Não diretamente através da API; não há scene.total_polygon_count propriedade. Use collect_meshes e some mesh.polygon_count nos resultados, como mostrado na Etapa 6.

 Português