Hvordan traversere en 3D-scengraf i Python
Scenegrafen i Aspose.3D FOSS er et tre av Node objekter med roten i scene.root_node. Hver 3D‑fil, enten den er lastet fra OBJ, glTF, STL, COLLADA eller 3MF, produserer den samme trestrukturen. Å vite hvordan man traverserer den lar deg inspisere geometri, telle polygoner, filtrere objekter etter type, og behandle spesifikke deler av en kompleks scene.
Steg-for-steg guide
Steg 1: Installer og importer
Installer Aspose.3D FOSS fra PyPI:
pip install aspose-3d-fossImporter klassene du trenger:
from aspose.threed import Scene
from aspose.threed.entities import MeshAlle offentlige klasser finnes under aspose.threed eller dets underpakker (aspose.threed.entities, aspose.threed.utilities).
Steg 2: Last inn en scene fra en fil
Bruk den statiske Scene.from_file() metoden for å åpne ethvert støttet format. Formatet oppdages automatisk fra filendelsen:
scene = Scene.from_file("model.gltf")Du kan også åpne med eksplisitte lastingsalternativer:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Etter lasting, scene.root_node er roten i treet. Alle importerte noder er barn eller etterkommere av denne noden.
Steg 3: Skriv en rekursiv traverseringsfunksjon
Den enkleste traverseringen besøker hver node i dybde‑først rekkefølge:
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)Eksempel på output:
[-]
[-] 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>
Merk: Rotnoden har et tomt navn (""), så den første linjen viser [-] uten noe navn etter.
node.child_nodes returnerer barn i innsettingsrekkefølge (rekkefølgen som importøren eller brukeren la dem til i).
Steg 4: Få tilgang til entitetsegenskaper på hver node
Bruk node.entity for å hente den første enheten som er festet til en node, eller node.entities for å iterere gjennom alle. Sjekk typen før du får tilgang til formatspesifikke 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, og node.transform.scaling gir den lokale romtransformasjonen. node.global_transform gir det evaluerte verdensrom-resultatet (med global_transform.scale for world-space scale).
Steg 5: Filtrer noder etter entitetstype
For å operere kun på spesifikke entitetstyper, legg til en isinstance guard inne 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}")Dette mønsteret er nyttig for masseoperasjoner: anvende en transformasjon på hver mesh-node, erstatte materialer, eller eksportere undertrær.
Steg 6: Samle alle mesh-er og skriv ut antall vertexer
Aggreger mesh-statistikk i ett enkelt 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")Vanlige problemer
| Problem | Løsning |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Guard med if node.entity is not None eller isinstance(node.entity, Mesh) før du får tilgang til mesh-egenskaper. Noder uten entiteter returnerer None. |
| Traversering stopper tidlig | Sørg for at rekursjonen når inn i node.child_nodes. Hvis du kun itererer scene.root_node.child_nodes (ikke rekursivt), går du glipp av alle etterkommere. |
| Mesh mangler i innsamlede resultater | Sjekk at filformatet bevarer hierarkiet. OBJ flater ut all geometri til ett enkelt nodenivå. Bruk glTF eller COLLADA for dype hierarkier. |
node.entity returnerer kun den første entiteten | Bruk node.entities (den fullstendige listen) når en node har flere enheter. node.entity er en forkortelse for node.entities[0] når entities er ikke tom. |
collect_meshes returnerer 0 resultater for en lastet STL | STL-filer produserer vanligvis en enkelt flat node med én mesh‑entitet direkte under root_node. Sjekk root_node.child_nodes[0].entity direkte. |
| Node-navn er tomme strenger | Noen formater (binary STL, noen OBJ-filer) lagrer ikke objektnavn. Noder vil ha tomme name strenger; bruk indeksen for identifikasjon i stedet. |
Ofte stilte spørsmål
Hvor dypt kan en scenegraph være?
Det finnes ingen fast grense. Pythons standard rekursjonsgrense (1000 rammer) gjelder for rekursive traverseringsfunksjoner. For svært dype hierarkier, konverter rekursjonen til en eksplisitt 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 jeg endre treet mens jeg traverserer det?
Ikke legg til eller fjern noder fra child_nodes mens du itererer over den. Samle nodene som skal endres i et første pass, og anvend endringene i et andre pass.
Hvordan finner jeg en spesifikk node etter navn?
Bruk node.get_child(name) for å finne et direkte barn etter navn. For et dypt søk, traverser treet med et navnefilter:
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")Returnerer node.entity alltid returnerer en Mesh?
Nei. En node kan inneholde hvilken som helst entitetstype: Mesh, Camera, Light, eller egendefinerte enheter. Sjekk alltid med isinstance(node.entity, Mesh) før du bruker mesh-spesifikke egenskaper.
Hvordan får jeg verdensrom-posisjonen til en node?
Les node.global_transform.translation. Dette er den evaluerte posisjonen i verdensrommet, med hensyn til alle foreldertransformasjoner. Den er skrivebeskyttet; endre node.transform.translation for å reposisjonere noden.
Kan jeg telle total antall polygoner i en scene uten å skrive en traversering?
Ikke direkte via API-et; det finnes ingen scene.total_polygon_count egenskap. Bruk collect_meshes og summer mesh.polygon_count på tvers av resultatene, som vist i trinn 6.