Jak przeglądać graf sceny 3D w Python

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-foss

Zaimportuj potrzebne klasy:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

Wszystkie 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

ProblemRozwią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śnieUpewnij 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 wynikachSprawdź, 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 STLPliki 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ówNiektó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.

 Polski