Graphe de scène

A graphique de scène est le modèle de données fondamental de Aspose.3D FOSS pour Python. Chaque fichier 3D, qu’il soit chargé depuis le disque ou construit en mémoire, est représenté sous forme d’arbre de Node objets enracinés à Scene.root_node. Chaque nœud peut contenir des nœuds enfants et un ou plusieurs Entity objets (maillages, caméras, lumières). Comprendre le graphique de scène vous donne un accès direct à la géométrie, aux matériaux et aux transformations spatiales de chaque objet d’une scène.

Installation et configuration

Installez la bibliothèque depuis PyPI :

pip install aspose-3d-foss

Aucune extension native, compilateur ou paquet système supplémentaire n’est requis. Pour les instructions d’installation complètes, consultez le Guide d’installation.


Vue d’ensemble : Concepts du graphe de scène

Le graphe de scène dans Aspose.3D FOSS suit une hiérarchie de contenance simple :

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
ObjetRôle
SceneConteneur de niveau supérieur. Contient root_node, asset_info, animation_clips, et sub_scenes.
NodeNœud d’arbre nommé. Possède un parent, zéro ou plusieurs enfants, zéro ou plusieurs entités, et un local Transform.
EntityGéométrie ou objet de scène attaché à un nœud. Types d’entités courants : Mesh, Camera, Light.
TransformPosition, rotation et mise à l’échelle en espace local pour un nœud. Le résultat en espace mondial est lu depuis global_transform.

Étape par étape : Construire un graphe de scène par programme

Étape 1 : Créer une scène

Un nouveau Scene commence toujours avec un vide root_node:

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 est le point d’entrée pour tout : chargement de fichiers, sauvegarde de fichiers, création de clips d’animation et accès à l’arbre de nœuds.


Étape 2 : Créer des nœuds enfants

Utilisez create_child_node(name) pour ajouter des nœuds nommés à l’arbre :

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

Alternativement, créez un autonome Node et attachez-le explicitement :

from aspose.threed import Scene, Node

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

Les deux approches produisent le même résultat. create_child_node est plus concis pour la construction en ligne.


Étape 3 : Créer une entité Mesh et l’attacher

A Mesh stocke les données de sommet (control_points) et la topologie des faces (polygons). Créez‑en un, ajoutez la géométrie, puis attachez‑le à un nœud :

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) représente une coordonnée homogène. Utilisez w=1 pour les positions de points ordinaires.

create_polygon(*indices) accepte des indices de sommet et enregistre une face dans la liste des polygones. Fournissez trois indices pour un triangle, quatre pour un quadrilatère.


Étape 4 : définir les transformations des nœuds

Chaque nœud possède un Transform qui contrôle sa position, son orientation et sa taille dans l’espace 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)

Les transformations sont cumulatives : la position d’un nœud enfant en world-space est la composition de sa propre transformation avec toutes les transformations des ancêtres. Lisez le résultat évalué en world-space depuis node.global_transform (immuable, lecture seule).


Étape 5 : parcourir le graphe de scène de manière récursive

Parcourez l’arbre entier en récursant à travers 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)

Exemple de sortie pour la scène construite ci‑dessus (le nom du nœud racine est une chaîne vide) :

 [None]
  parent [None]
    child [Mesh]

Pour les scènes avec plusieurs entités par nœud, itérez node.entities au lieu 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)

Étape 6 : enregistrer la scène

Passez un chemin de fichier à scene.save(). Le format est déduit de l’extension du fichier :

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

Pour les options spécifiques au format, passez un objet d’options d’enregistrement comme deuxième argument :

from aspose.threed.formats import GltfSaveOptions

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

Conseils et bonnes pratiques

  • Nommez chaque nœud. Donner des noms significatifs aux nœuds facilite grandement le débogage des traversées et garantit que les noms sont conservés dans le fichier exporté.
  • Un maillage par nœud. Conserver un rapport 1:1 entre les entités et les nœuds simplifie les transformations et les requêtes de collision.
  • Utiliser create_child_node plutôt que l’attachement manuel. Il définit automatiquement la référence du parent et est moins sujet aux erreurs.
  • Lire global_transform après avoir construit la hiérarchie. Le résultat en espace mondial n’est stable que lorsque toutes les transformations des ancêtres sont définies.
  • Ne modifiez pas l’arbre pendant le parcours. Ajouter ou supprimer des nœuds enfants pendant l’itération child_nodes produira un comportement imprévisible. Collectez d’abord les nœuds, puis modifiez-les.
  • Les points de contrôle utilisent Vector4, pas Vector3. Toujours passer w=1 pour les positions de sommet ordinaires ; w=0 représente un vecteur directionnel (pas un point).
  • mesh.control_points renvoie une copie. Le control_points la propriété renvoie list(self._control_points) — ajouter à la liste renvoyée ne modifie pas le maillage. Toujours ajouter à mesh._control_points directement lors de la construction de la géométrie par programme. C’est une limitation connue de la bibliothèque ; une API de mutation publique n’existe pas encore.

Problèmes courants

ProblèmeRésolution
AttributeError: 'NoneType' object has no attribute 'polygons'Protéger avec if node.entity is not None avant d’accéder aux propriétés de l’entité. Un nœud sans entités a entity = None.
Mesh apparaît à l’origine malgré le paramètre translationtransform.translation applique un décalage local. Si le nœud parent lui‑même possède une transformation non identitaire, la position mondiale peut différer. Vérifiez global_transform.
Nœuds enfants manquants après scene.save() / rechargerCertains formats (OBJ) aplatissent la hiérarchie. Utilisez glTF ou COLLADA pour préserver l’arbre complet des nœuds.
polygon_count est 0 après mesh.create_polygon(...)Vérifiez que les indices de sommet transmis à create_polygon sont dans la plage (0 à len(control_points) - 1).
Node.get_child(name) renvoie NoneLe nom est sensible à la casse. Confirmez la chaîne de nom exacte utilisée lors de la création.
Le parcours visite les nœuds dans un ordre inattenduchild_nodes renvoie les enfants dans l’ordre d’insertion (l’ordre add_child_node / create_child_node a été appelé).
 Français