Comment parcourir un graphe de scène 3D dans Python
Le graphe de scène dans Aspose.3D FOSS est un arbre de Node objets enracinés à scene.root_node. Chaque fichier 3D, qu’il soit chargé depuis OBJ, glTF, STL, COLLADA ou 3MF, produit la même structure d’arbre. Savoir comment le parcourir vous permet d’inspecter la géométrie, de compter les polygones, de filtrer les objets par type et de traiter des parties spécifiques d’une scène complexe.
Guide étape par étape
Étape 1 : Installer et importer
Installez Aspose.3D FOSS depuis PyPI :
pip install aspose-3d-fossImportez les classes dont vous avez besoin :
from aspose.threed import Scene
from aspose.threed.entities import MeshToutes les classes publiques se trouvent sous aspose.threed ou ses sous‑packages (aspose.threed.entities, aspose.threed.utilities).
Étape 2 : Charger une scène depuis un fichier
Utilisez le static Scene.from_file() méthode pour ouvrir n’importe quel format pris en charge. Le format est détecté automatiquement à partir de l’extension du fichier :
scene = Scene.from_file("model.gltf")Vous pouvez également ouvrir avec des options de chargement explicites :
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Après le chargement, scene.root_node est la racine de l’arbre. Tous les nœuds importés sont des enfants ou des descendants de ce nœud.
Étape 3 : Écrire une fonction de traversée récursive
La traversée la plus simple visite chaque nœud en ordre profondeur d’abord :
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)Exemple de sortie :
[-]
[-] 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>
Remarque : Le nœud racine a un nom vide (""), donc la première ligne affiche [-] sans nom qui suit.
node.child_nodes renvoie les enfants dans l’ordre d’insertion (l’ordre dans lequel l’importateur ou l’utilisateur les a ajoutés).
Étape 4 : Accéder aux propriétés d’entité sur chaque nœud
Utiliser node.entity pour obtenir la première entité attachée à un nœud, ou node.entities pour parcourir toutes. Vérifiez le type avant d’accéder aux propriétés spécifiques au format :
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, et node.transform.scaling donnent la transformation en espace local. node.global_transform donne le résultat évalué en espace mondial (avec global_transform.scale pour l’échelle en espace mondial).
Étape 5 : Filtrer les nœuds par type d’entité
Pour n’opérer que sur des types d’entité spécifiques, ajoutez un isinstance garde dans le parcours :
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}")Ce modèle est utile pour des opérations en masse : appliquer une transformation à chaque nœud de maillage, remplacer des matériaux, ou exporter des sous‑arbres.
Étape 6 : Collecter tous les maillages et afficher le nombre de sommets
Regrouper les statistiques des maillages en un seul passage :
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")Problèmes courants
| Problème | Résolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Garde avec if node.entity is not None ou isinstance(node.entity, Mesh) avant d’accéder aux propriétés du maillage. Les nœuds sans entités renvoient None. |
| Le parcours s’arrête prématurément | Assurez-vous que la récursion pénètre dans node.child_nodes. Si vous itérez uniquement scene.root_node.child_nodes (pas de façon récursive), vous manquez tous les descendants. |
| Maillage manquant dans les résultats collectés | Vérifiez que le format de fichier préserve la hiérarchie. OBJ aplatit toute la géométrie en un seul niveau de nœud. Utilisez glTF ou COLLADA pour les hiérarchies profondes. |
node.entity renvoie uniquement la première entité | Utilisez node.entities (la liste complète) lorsqu’un nœud porte plusieurs entités. node.entity est une abréviation de node.entities[0] lorsque entities n’est pas vide. |
collect_meshes renvoie 0 résultats pour un STL chargé | Les fichiers STL produisent généralement un seul nœud plat avec une entité mesh directement sous root_node. Vérifiez root_node.child_nodes[0].entity directement. |
| Les noms de nœuds sont des chaînes vides | Certains formats (STL binaire, certains fichiers OBJ) ne stockent pas les noms d’objet. Les nœuds auront des chaînes vides name ; utilisez l’index pour l’identification à la place. |
Foire aux questions
Quelle profondeur peut atteindre un graphe de scène ?
Il n’y a pas de limite stricte. La limite de récursion par défaut de Python (1000 appels) s’applique aux fonctions de parcours récursif. Pour des hiérarchies très profondes, convertissez la récursion en une pile explicite :
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))Puis-je modifier l’arbre pendant son parcours ?
N’ajoutez pas et ne supprimez pas de nœuds de child_nodes pendant son itération. Collectez les nœuds à modifier lors d’un premier passage, puis appliquez les changements lors d’un second passage.
Comment trouver un nœud spécifique par son nom ?
Utilisez node.get_child(name) pour trouver un enfant direct par son nom. Pour une recherche approfondie, parcourez l’arbre avec un filtre de nom :
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")Est‑ce que node.entity renvoie toujours un Mesh ?
Non. Un nœud peut contenir n’importe quel type d’entité : Mesh, Camera, Light, ou des entités personnalisées. Vérifiez toujours avec isinstance(node.entity, Mesh) avant d’utiliser les propriétés spécifiques au maillage.
Comment obtenir la position en espace mondial d’un nœud ?
Lire node.global_transform.translation. Il s’agit de la position évaluée dans l’espace mondial, en tenant compte de toutes les transformations des ancêtres. Elle est en lecture seule ; modifiez node.transform.translation pour repositionner le nœud.
Puis-je compter le nombre total de polygones dans une scène sans écrire de traversée ?
Pas directement via l’API ; il n’existe pas de scene.total_polygon_count propriété. Utilisez collect_meshes et additionnez mesh.polygon_count sur l’ensemble des résultats, comme indiqué à l’étape 6.