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-fossNenhuma 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
└── ...| Objeto | Função |
|---|---|
Scene | Contêiner de nível superior. Contém root_node, asset_info, animation_clips e sub_scenes. |
Node | Nó de árvore nomeado. Possui um pai, zero ou mais filhos, zero ou mais entidades e um Transform local. |
Entity | Geometria ou objeto de cena anexado a um nó. Tipos de entidade comuns: Mesh, Camera, Light. |
Transform | Posiçã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)) # 0Scene é 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)) # 1Alternativamente, 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") # STLPara 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_nodeem vez de anexação manual. Ele define a referência ao pai automaticamente e é menos propenso a erros. - Leia
global_transformapó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_nodesproduzirá comportamento imprevisível. Colete os nós primeiro, depois modifique. - Pontos de controle usam
Vector4, nãoVector3. Sempre passew=1para posições de vértice comuns;w=0representa um vetor de direção (não um ponto). mesh.control_pointsretorna uma cópia. A propriedadecontrol_pointsdevolvelist(self._control_points)— acrescentar à lista retornada não modifica o mesh. Sempre acrescente amesh._control_pointsdiretamente 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
| Issue | Resolution |
|---|---|
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 translation | transform.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() / reload | Alguns 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 None | O nome diferencia maiúsculas de minúsculas. Confirme a string de nome exata usada no momento da criação. |
| Traversal visits nodes in unexpected order | child_nodes retorna os filhos na ordem de inserção (a ordem em que add_child_node / create_child_node foi chamado). |