Grafo de Cena

Um scene graph é o modelo de dados fundamental do Aspose.3D FOSS para Python. Cada arquivo 3D, seja carregado do disco ou construído na memória, é representado como uma árvore de objetos Node enraizada em Scene.root_node. Cada nó pode conter nós filhos e um ou mais objetos Entity (malhas, câmeras, luzes). Compreender o scene graph lhe dá acesso direto à geometria, materiais e transformações espaciais de cada objeto em uma cena.

Instalação e Configuração

Instale a biblioteca do PyPI:

pip install aspose-3d-foss

Nenhuma extensão nativa, compilador ou pacote de sistema adicional é necessário. Para instruções completas de instalação, veja o Installation Guide.


Visão geral: Conceitos de grafo de cena

O grafo de cena no Aspose.3D FOSS segue uma hierarquia de contenção simples:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
ObjetoFunção
SceneContêiner de nível superior. Contém root_node, asset_info, animation_clips e sub_scenes.
NodeNó de árvore nomeado. Possui um pai, zero ou mais filhos, zero ou mais entidades e um Transform local.
EntityGeometria ou objeto de cena anexado a um nó. Tipos de entidade comuns: Mesh, Camera, Light.
TransformPosição, rotação e escala em espaço local para um nó. O resultado em espaço mundial é lido de global_transform.

Passo a Passo: Construindo um Grafo de Cena Programaticamente

Etapa 1: Criar uma Cena

Um novo Scene sempre começa com um root_node vazio:

from aspose.threed import Scene

scene = Scene()
print(scene.root_node.name)   # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes))  # 0

Scene é o ponto de entrada para tudo: carregamento de arquivos, salvamento de arquivos, criação de clipes de animação e acesso à árvore de nós.


Etapa 2: Criar nós filhos

Use create_child_node(name) para adicionar nós nomeados à árvore:

from aspose.threed import Scene

scene = Scene()

parent = scene.root_node.create_child_node("parent")
child  = parent.create_child_node("child")

print(parent.name)                          # "parent"
print(child.parent_node.name)               # "parent"
print(len(scene.root_node.child_nodes))     # 1

Alternativamente, crie um Node independente e anexe‑o explicitamente:

from aspose.threed import Scene, Node

scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)

Ambas as abordagens produzem o mesmo resultado. create_child_node é mais conciso para construção inline.


Etapa 3: Criar uma Entidade Mesh e Anexá‑la

Um Mesh armazena dados de vértices (control_points) e topologia de faces (polygons). Crie um, adicione geometria e, então, anexe‑o a um nó:

from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4

scene = Scene()
parent = scene.root_node.create_child_node("parent")
child  = parent.create_child_node("child")

##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)

child.add_entity(mesh)

print(f"Mesh name:      {mesh.name}")
print(f"Vertex count:   {len(mesh.control_points)}")
print(f"Polygon count:  {mesh.polygon_count}")

Vector4(x, y, z, w) representa uma coordenada homogênea. Use w=1 para posições de ponto regulares.

create_polygon(*indices) aceita índices de vértices e registra uma face na lista de polígonos. Passe três índices para um triângulo, quatro para um quad.


Etapa 4: Definir Transformações de Nó

Cada nó tem um Transform que controla sua posição, orientação e tamanho no espaço local:

from aspose.threed.utilities import Vector3, Quaternion

##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)

##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)

##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)

As transformações são cumulativas: a posição em espaço mundial do nó filho é a composição da sua própria transformação com todas as transformações ancestrais. Leia o resultado avaliado em espaço mundial de node.global_transform (imutável, somente leitura).


Etapa 5: Percorrer o Grafo de Cena Recursivamente

Percorra toda a árvore recursivamente através de node.child_nodes:

def traverse(node, depth=0):
    indent = "  " * depth
    entity_type = type(node.entity).__name__ if node.entity else "None"
    print(f"{indent}{node.name} [{entity_type}]")
    for child in node.child_nodes:
        traverse(child, depth + 1)

traverse(scene.root_node)

Exemplo de saída para a cena construída acima (o nome do nó raiz é uma string vazia):

 [None]
  parent [None]
    child [Mesh]

Para cenas com várias entidades por nó, itere node.entities em vez de node.entity:

def traverse_full(node, depth=0):
    indent = "  " * depth
    entity_names = [type(e).__name__ for e in node.entities] or ["None"]
    print(f"{indent}{node.name} [{', '.join(entity_names)}]")
    for child in node.child_nodes:
        traverse_full(child, depth + 1)

traverse_full(scene.root_node)

Etapa 6: Salvar a Cena

Passe um caminho de arquivo para scene.save(). O formato é inferido a partir da extensão do arquivo:

scene.save("scene.gltf")   # JSON glTF 2.0
scene.save("scene.glb")    # Binary GLB container
scene.save("scene.obj")    # Wavefront OBJ
scene.save("scene.stl")    # STL

Para opções específicas de formato, passe um objeto save-options como segundo argumento:

from aspose.threed.formats import GltfSaveOptions

opts = GltfSaveOptions()
scene.save("scene.gltf", opts)

Dicas e Melhores Práticas

  • Nomeie cada nó. Dar nomes significativos aos nós facilita muito a depuração de travessias e garante que os nomes sejam preservados no arquivo exportado.
  • Um mesh por nó. Manter as entidades 1:1 com os nós simplifica transformações e consultas de colisão.
  • Use create_child_node em vez de anexação manual. Ele define a referência ao pai automaticamente e é menos propenso a erros.
  • Leia global_transform após construir a hierarquia. O resultado em espaço mundial só é estável depois que todas as transformações dos ancestrais são definidas.
  • Não altere a árvore durante a travessia. Adicionar ou remover nós filhos enquanto itera child_nodes produzirá comportamento imprevisível. Colete os nós primeiro, depois modifique.
  • Pontos de controle usam Vector4, não Vector3. Sempre passe w=1 para posições de vértice comuns; w=0 representa um vetor de direção (não um ponto).
  • mesh.control_points retorna uma cópia. A propriedade control_points devolve list(self._control_points) — acrescentar à lista retornada não modifica o mesh. Sempre acrescente a mesh._control_points diretamente ao construir a geometria programaticamente. Esta é uma limitação conhecida da biblioteca; ainda não existe uma API pública de mutação.

Problemas Comuns

IssueResolution
AttributeError: 'NoneType' object has no attribute 'polygons'Proteja com if node.entity is not None antes de acessar as propriedades da entidade. Um nó sem entidades tem entity = None.
Mesh appears at the origin despite setting translationtransform.translation aplica um deslocamento local. Se o nó pai em si tem uma transformação diferente da identidade, a posição mundial pode ser diferente. Verifique global_transform.
Child nodes missing after scene.save() / reloadAlguns formatos (OBJ) achatam a hierarquia. Use glTF ou COLLADA para preservar a árvore completa de nós.
polygon_count is 0 after mesh.create_polygon(...)Verifique se os índices de vértice passados para create_polygon estão dentro do intervalo (0 a len(control_points) - 1).
Node.get_child(name) returns NoneO nome diferencia maiúsculas de minúsculas. Confirme a string de nome exata usada no momento da criação.
Traversal visits nodes in unexpected orderchild_nodes retorna os filhos na ordem de inserção (a ordem em que add_child_node / create_child_node foi chamado).
 Português