Hur man traverserar en 3D-scengraf i Python
Scengrafen i Aspose.3D FOSS är ett träd av Node objekt med rot i scene.root_node. Varje 3D-fil, oavsett om den läses in från OBJ, glTF, STL, COLLADA eller 3MF, producerar samma trädstruktur. Att veta hur man traverserar den låter dig inspektera geometri, räkna polygoner, filtrera objekt efter typ och bearbeta specifika delar av en komplex scen.
Steg-för-steg-guide
Steg 1: Installera och importera
Installera Aspose.3D FOSS från PyPI:
pip install aspose-3d-fossImportera de klasser du behöver:
from aspose.threed import Scene
from aspose.threed.entities import MeshAlla offentliga klasser finns under aspose.threed eller dess underpaket (aspose.threed.entities, aspose.threed.utilities).
Steg 2: Läs in en scen från en fil
Använd den statiska Scene.from_file() metoden för att öppna vilket stödformat som helst. Formatet upptäcks automatiskt från filändelsen:
scene = Scene.from_file("model.gltf")Du kan också öppna med explicita laddningsalternativ:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Efter inläsning, scene.root_node är roten i trädet. Alla importerade noder är barn eller ättlingar till denna nod.
Steg 3: Skriv en rekursiv traverseringsfunktion
Den enklaste traverseringen besöker varje nod i djup‑först‑ordning:
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)Exempelutdata:
[-]
[-] 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>
Obs: Rotnoden har ett tomt namn (""), så den första raden visar [-] utan något namn efter.
node.child_nodes returnerar barn i insättningsordning (den ordning i vilken importören eller användaren lade till dem).
Steg 4: Åtkomst till enhetsegenskaper på varje nod
Använd node.entity för att hämta den första entiteten som är fäst vid en nod, eller node.entities för att iterera alla. Kontrollera typen innan du får åtkomst till format-specifika egenskaper:
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, och node.transform.scaling ger den lokala rymdtransformen. node.global_transform ger det utvärderade världsrumsresultatet (med global_transform.scale för världsrumsskala).
Steg 5: Filtrera noder efter entitetstyp
För att endast arbeta med specifika entitetstyper, lägg till en isinstance guard i traverseringen:
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}")Detta mönster är användbart för massoperationer: applicera en transformation på varje mesh-nod, ersätta material eller exportera underträd.
Steg 6: Samla alla meshar och skriv ut vertexantal
Aggregera mesh-statistik i ett enda 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")Vanliga problem
| Problem | Lösning |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guard med if node.entity is not None eller isinstance(node.entity, Mesh) innan du får åtkomst till mesh‑egenskaper. Noder utan entiteter returnerar None. |
| Traversering stoppar tidigt | Säkerställ att rekursionen når in i node.child_nodes. Om du bara itererar scene.root_node.child_nodes (inte rekursivt), missar du alla ättlingar. |
| Mesh saknas i insamlade resultat | Kontrollera att filformatet bevarar hierarkin. OBJ plattar ut all geometri till en enda nodnivå. Använd glTF eller COLLADA för djupa hierarkier. |
node.entity returnerar endast den första entiteten | Använd node.entities (den fullständiga listan) när en nod bär flera enheter. node.entity är en förkortning för node.entities[0] när entities är icke-tom. |
collect_meshes returnerar 0 resultat för en inläst STL | STL-filer producerar vanligtvis en enda platt nod med en mesh‑entitet direkt under root_node. Kontrollera root_node.child_nodes[0].entity direkt. |
| Nodnamn är tomma strängar | Vissa format (binär STL, vissa OBJ-filer) lagrar inte objektnamn. Noder kommer att ha tomma name strängar; använd indexet för identifiering istället. |
Vanliga frågor
Hur djup kan en scengraf vara?
Det finns ingen hård gräns. Pythons standardrekursionsgräns (1000 ramar) gäller för rekursiva traverseringsfunktioner. För mycket djupa hierarkier, konvertera rekursionen till en explicit 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 jag modifiera trädet medan jag traverserar det?
Lägg inte till eller ta bort noder från child_nodes under iteration. Samla noderna som ska modifieras i ett första pass, och applicera ändringarna i ett andra pass.
Hur hittar jag en specifik nod med namn?
Använd node.get_child(name) för att hitta ett direkt barn efter namn. För en djup sökning, traversera trädet med ett namnfilter:
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")Returnerar node.entity alltid returnera en Mesh?
Nej. En nod kan innehålla vilken entitetstyp som helst: Mesh, Camera, Light, eller anpassade entiteter. Kontrollera alltid med isinstance(node.entity, Mesh) innan du använder mesh‑specifika egenskaper.
Hur får jag världsrumspositionen för en nod?
Läs node.global_transform.translation. Detta är den utvärderade positionen i världsrummet, med hänsyn till alla förfäders transformationer. Den är skrivskyddad; ändra node.transform.translation för att flytta noden.
Kan jag räkna det totala antalet polygoner i en scen utan att skriva en traversering?
Inte direkt via API:et; det finns ingen scene.total_polygon_count egenskap. Använd collect_meshes och summera mesh.polygon_count över resultaten, som visas i steg 6.