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-fossAucune 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
└── ...| Objet | Rôle |
|---|---|
Scene | Conteneur de niveau supérieur. Contient root_node, asset_info, animation_clips, et sub_scenes. |
Node | Nœud d’arbre nommé. Possède un parent, zéro ou plusieurs enfants, zéro ou plusieurs entités, et un local Transform. |
Entity | Géométrie ou objet de scène attaché à un nœud. Types d’entités courants : Mesh, Camera, Light. |
Transform | Position, 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)) # 0Scene 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)) # 1Alternativement, 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") # STLPour 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_nodeplutôt que l’attachement manuel. Il définit automatiquement la référence du parent et est moins sujet aux erreurs. - Lire
global_transformaprè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_nodesproduira un comportement imprévisible. Collectez d’abord les nœuds, puis modifiez-les. - Les points de contrôle utilisent
Vector4, pasVector3. Toujours passerw=1pour les positions de sommet ordinaires ;w=0représente un vecteur directionnel (pas un point). mesh.control_pointsrenvoie une copie. Lecontrol_pointsla propriété renvoielist(self._control_points)— ajouter à la liste renvoyée ne modifie pas le maillage. Toujours ajouter àmesh._control_pointsdirectement 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ème | Ré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 translation | transform.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() / recharger | Certains 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 None | Le 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 inattendu | child_nodes renvoie les enfants dans l’ordre d’insertion (l’ordre add_child_node / create_child_node a été appelé). |