Jak przeglądać graf sceny 3D w Python
Graf scen w Aspose.3D FOSS jest drzewem Node obiektów z korzeniem w scene.root_node. Każdy plik 3D, niezależnie od tego, czy został wczytany z OBJ, glTF, STL, COLLADA czy 3MF, tworzy tę samą strukturę drzewa. Znajomość sposobu jego przeglądania pozwala na inspekcję geometrii, liczenie wielokątów, filtrowanie obiektów według typu oraz przetwarzanie konkretnych części złożonej sceny.
Przewodnik krok po kroku
Krok 1: Instalacja i import
Zainstaluj Aspose.3D FOSS z PyPI:
pip install aspose-3d-fossZaimportuj potrzebne klasy:
from aspose.threed import Scene
from aspose.threed.entities import MeshWszystkie publiczne klasy znajdują się w aspose.threed lub w jej podpakietach (aspose.threed.entities, aspose.threed.utilities).
Krok 2: Wczytaj scenę z pliku
Użyj statycznej Scene.from_file() metody, aby otworzyć dowolny obsługiwany format. Format jest wykrywany automatycznie na podstawie rozszerzenia pliku:
scene = Scene.from_file("model.gltf")Możesz także otworzyć z wyraźnie podanymi opcjami ładowania:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)Po załadowaniu, scene.root_node jest korzeniem drzewa. Wszystkie zaimportowane węzły są dziećmi lub potomkami tego węzła.
Krok 3: Napisz rekurencyjną funkcję przeglądania
Najprostsze przeglądanie odwiedza każdy węzeł w kolejności głębokościowej (depth‑first):
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)Przykładowe wyjście:
[-]
[-] 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>
Uwaga: Węzeł główny ma pustą nazwę (""), więc pierwsza linia pokazuje [-] bez nazwy następującej.
node.child_nodes zwraca dzieci w kolejności wstawiania (kolejności, w której importer lub użytkownik je dodał).
Krok 4: Dostęp do właściwości encji w każdym węźle
Użyj node.entity aby uzyskać pierwszą encję podłączoną do węzła, lub node.entities aby iterować po wszystkich. Sprawdź typ przed dostępem do właściwości specyficznych dla formatu:
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, oraz node.transform.scaling podają transformację w przestrzeni lokalnej. node.global_transform podaje wyliczony wynik w przestrzeni światowej (z global_transform.scale dla skali w przestrzeni światowej).
Krok 5: Filtrowanie węzłów według typu encji
Aby działać tylko na określonych typach encji, dodaj isinstance ochrona wewnątrz przeglądania:
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}")Ten wzorzec jest przydatny przy operacjach masowych: stosowaniu transformacji do każdego węzła siatki, zamianie materiałów lub eksportowaniu poddrzew.
Krok 6: Zbierz wszystkie siatki i wypisz liczbę wierzchołków
Zagreguj statystyki siatek w jednym przebiegu:
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")Typowe problemy
| Problem | Rozwiązanie |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Ochrona z if node.entity is not None lub isinstance(node.entity, Mesh) przed dostępem do właściwości siatki. Węzły bez encji zwracają None. |
| Przeglądanie zatrzymuje się wcześnie | Upewnij się, że rekurencja dociera do node.child_nodes. Jeśli iterujesz tylko scene.root_node.child_nodes (nie rekurencyjnie), tracisz wszystkich potomków. |
| Brak siatki w zebranych wynikach | Sprawdź, czy format pliku zachowuje hierarchię. OBJ spłaszcza całą geometrię do jednego poziomu węzła. Użyj glTF lub COLLADA dla głębokich hierarchii. |
node.entity zwraca tylko pierwszą encję. | Użyj node.entities (pełna lista) gdy węzeł zawiera wiele encji. node.entity jest skrótem dla node.entities[0] gdy entities nie jest pusty. |
collect_meshes zwraca 0 wyników dla załadowanego STL | Pliki STL zazwyczaj tworzą pojedynczy płaski węzeł z jedną encją siatki bezpośrednio pod root_node. Sprawdź root_node.child_nodes[0].entity bezpośrednio. |
| Nazwy węzłów są pustymi ciągami znaków | Niektóre formaty (binary STL, niektóre pliki OBJ) nie przechowują nazw obiektów. Węzły będą mieć puste name ciągi znaków; użyj indeksu do identyfikacji zamiast tego. |
Najczęściej zadawane pytania
Jak głęboki może być graf sceny?
Nie ma sztywnego limitu. Domyślny limit rekurencji Python (1000 klatek) obowiązuje w funkcjach rekurencyjnego przeglądania. Dla bardzo głębokich hierarchii, zamień rekurencję na explicite stos:
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))Czy mogę modyfikować drzewo podczas jego przeglądania?
Nie dodawaj ani nie usuwaj węzłów z child_nodes podczas iteracji. Zbierz węzły do modyfikacji w pierwszym przebiegu, a następnie zastosuj zmiany w drugim przebiegu.
Jak znaleźć konkretny węzeł po nazwie?
Użyj node.get_child(name) aby znaleźć bezpośrednie dziecko po nazwie. Do głębokiego wyszukiwania, przeglądaj drzewo z filtrem nazw:
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")Czy node.entity zawsze zwraca Mesh?
Nie. Węzeł może zawierać dowolny typ encji: Mesh, Camera, Light, lub własne encje. Zawsze sprawdzaj za pomocą isinstance(node.entity, Mesh) przed użyciem właściwości specyficznych dla siatki.
Jak uzyskać pozycję w przestrzeni światowej węzła?
Odczyt node.global_transform.translation. To jest wyliczona pozycja w przestrzeni świata, uwzględniająca wszystkie transformacje przodków. Jest tylko do odczytu; zmodyfikuj node.transform.translation aby przemieścić węzeł.
Czy mogę policzyć całkowitą liczbę wielokątów w scenie bez pisania przeglądu?
Nie bezpośrednio przez API; nie ma scene.total_polygon_count właściwości. Użyj collect_meshes i sumuj mesh.polygon_count wśród wyników, jak pokazano w Kroku 6.