Hoe een 3D‑scènegraph te traverseren in Python
De scenegraph in Aspose.3D FOSS is een boom van Node objecten met wortel bij scene.root_node. Elk 3D‑bestand, of het nu geladen wordt vanuit OBJ, glTF, STL, COLLADA of 3MF, produceert dezelfde boomstructuur. Weten hoe je deze kunt doorlopen stelt je in staat om geometrie te inspecteren, polygonen te tellen, objecten op type te filteren en specifieke delen van een complexe scène te verwerken.
Stapsgewijze handleiding
Stap 1: Installeren en importeren
Installeer Aspose.3D FOSS vanaf PyPI:
pip install aspose-3d-fossImporteer de klassen die je nodig hebt:
from aspose.threed import Scene
from aspose.threed.entities import MeshAlle publieke klassen bevinden zich onder aspose.threed of zijn sub‑packages (aspose.threed.entities, aspose.threed.utilities).
Stap 2: Laad een scene vanuit een bestand
Gebruik de statische Scene.from_file() methode om elk ondersteund formaat te openen. Het formaat wordt automatisch gedetecteerd aan de hand van de bestandsextensie:
scene = Scene.from_file("model.gltf")Je kunt ook openen met expliciete laadopties:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Na het laden, scene.root_node is de wortel van de boom. Alle geïmporteerde knooppunten zijn kinderen of afstammelingen van dit knooppunt.
Stap 3: Schrijf een recursieve traversatiefunctie
De eenvoudigste traversie bezoekt elk knooppunt in diepte‑eerste volgorde:
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)Voorbeeldoutput:
[-]
[-] 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=“Kopieer 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>
Opmerking: Het wortelknooppunt heeft een lege naam (""), dus de eerste regel toont [-] zonder daaropvolgende naam.
node.child_nodes geeft kinderen terug in de invoervolgorde (de volgorde waarin de importeur of de gebruiker ze heeft toegevoegd).
Stap 4: Toegang tot entiteitseigenschappen op elk knooppunt
Gebruik node.entity om de eerste entiteit die aan een knooppunt is gekoppeld op te halen, of node.entities om ze allemaal te itereren. Controleer het type voordat je format‑specifieke eigenschappen benadert:
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, en node.transform.scaling geven de lokale‑ruimte transformatie. node.global_transform geeft het geëvalueerde wereld‑ruimte resultaat (met global_transform.scale voor wereldruimteschaal).
Stap 5: Filter knooppunten op entiteitstype
Om alleen op specifieke entiteitstypen te werken, voeg een isinstance guard binnen de traversie:
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}")Dit patroon is nuttig voor bulkbewerkingen: een transformatie toepassen op elk mesh‑knooppunt, materialen vervangen, of sub‑bomen exporteren.
Stap 6: Verzamel alle meshes en print vertex‑aantallen
Aggregeer mesh‑statistieken in één enkele pass:
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")Veelvoorkomende problemen
| Probleem | Oplossing |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guard met if node.entity is not None of isinstance(node.entity, Mesh) voordat mesh-eigenschappen worden benaderd. Nodes zonder entiteiten retourneren None. |
| Traversie stopt vroeg | Zorg ervoor dat de recursie doordringt tot node.child_nodes. Als je alleen iterereert scene.root_node.child_nodes (niet recursief), mis je alle afstammelingen. |
| Mesh ontbreekt in verzamelde resultaten | Controleer of het bestandsformaat de hiërarchie behoudt. OBJ vlakt alle geometrie af tot één knooppuntniveau. Gebruik glTF of COLLADA voor diepe hiërarchieën. |
node.entity retourneert alleen de eerste entiteit | Gebruik node.entities (de volledige lijst) wanneer een node meerdere entiteiten draagt. node.entity is een afkorting voor node.entities[0] wanneer entities is niet leeg. |
collect_meshes geeft 0 resultaten terug voor een geladen STL | STL‑bestanden produceren doorgaans één platte node met één mesh‑entiteit direct eronder root_node. Controleer root_node.child_nodes[0].entity direct. |
| Node-namen zijn lege strings | Sommige formaten (binaire STL, sommige OBJ‑bestanden) slaan geen objectnamen op. Nodes zullen lege name strings; gebruik in plaats daarvan de index voor identificatie. |
Veelgestelde vragen
Hoe diep kan een scene‑graph zijn?
Er is geen harde limiet. De standaard recursielimiet van Python (1000 frames) geldt voor recursieve traversiefuncties. Voor zeer diepe hiërarchieën, zet de recursie om naar een expliciete stack:
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))Kan ik de boom wijzigen tijdens het traverseren?
Voeg geen nodes toe of verwijder ze uit child_nodes tijdens het itereren ervan. Verzamel de nodes die moeten worden aangepast in een eerste pass, en pas de wijzigingen vervolgens toe in een tweede pass.
Hoe vind ik een specifiek knooppunt op naam?
Gebruik node.get_child(name) om een direct kind op naam te vinden. Voor een diepe zoekopdracht, doorloop de boom met een naamfilter:
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")Geeft node.entity altijd een Mesh terug?
Nee. Een node kan elk entiteitstype bevatten: Mesh, Camera, Light, of aangepaste entiteiten. Controleer altijd met isinstance(node.entity, Mesh) voordat u mesh‑specifieke eigenschappen gebruikt.
Hoe krijg ik de positie in wereldruimte van een knooppunt?
Lezen node.global_transform.translation. Dit is de geëvalueerde positie in de wereldruimte, rekening houdend met alle vooroudertransformaties. Het is alleen-lezen; wijzig node.transform.translation om de node te verplaatsen.
Kan ik het totale aantal polygonen in een scene tellen zonder een traversal te schrijven?
Niet rechtstreeks via de API; er is geen scene.total_polygon_count eigenschap. Gebruik collect_meshes en som mesh.polygon_count over de resultaten, zoals weergegeven in Stap 6.