Graf sceny
scene graph jest podstawowym modelem danych Aspose.3D FOSS for Python. Każdy plik 3D, niezależnie od tego, czy został wczytany z dysku, czy utworzony w pamięci, jest reprezentowany jako drzewo obiektów Node z korzeniem w Scene.root_node. Każdy węzeł może zawierać węzły potomne oraz jeden lub więcej obiektów Entity (siatki, kamery, światła). Zrozumienie scene graph daje bezpośredni dostęp do geometrii, materiałów i transformacji przestrzennych każdego obiektu w scenie.
Instalacja i konfiguracja
Zainstaluj bibliotekę z PyPI:
pip install aspose-3d-fossNie są wymagane żadne natywne rozszerzenia, kompilatory ani dodatkowe pakiety systemowe. Pełne instrukcje instalacji znajdziesz w Przewodnik instalacji.
Przegląd: Koncepcje grafu sceny
Graf scen w Aspose.3D FOSS opiera się na prostej hierarchii zawartości:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Object | Role |
|---|---|
Scene | Kontener najwyższego poziomu. Zawiera root_node, asset_info, animation_clips i sub_scenes. |
Node | Nazwany węzeł drzewa. Ma rodzica, zero lub więcej dzieci, zero lub więcej encji oraz lokalny Transform. |
Entity | Geometria lub obiekt sceny dołączony do węzła. Typowe typy encji: Mesh, Camera, Light. |
Transform | Lokalna pozycja, rotacja i skalowanie w przestrzeni węzła. Wynik w przestrzeni światowej odczytywany jest z global_transform. |
Krok po kroku: Budowanie grafu sceny programowo
Krok 1: Utwórz scenę
Nowy Scene zawsze zaczyna się od pustego root_node:
from aspose.threed import Scene
scene = Scene()
print(scene.root_node.name) # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes)) # 0Scene jest punktem wejścia dla wszystkiego: ładowanie plików, zapisywanie plików, tworzenie klipów animacji i dostęp do drzewa węzłów.
Krok 2: Utwórz węzły potomne
Użyj create_child_node(name), aby dodać nazwane węzły do drzewa:
from aspose.threed import Scene
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
print(parent.name) # "parent"
print(child.parent_node.name) # "parent"
print(len(scene.root_node.child_nodes)) # 1Alternatywnie, utwórz samodzielny Node i dołącz go wyraźnie:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Oba podejścia dają ten sam wynik. create_child_node jest bardziej zwięzły przy konstrukcji inline.
Krok 3: Utwórz encję siatki i dołącz ją
Mesh przechowuje dane wierzchołków (control_points) i topologię ścian (polygons). Utwórz go, dodaj geometrię, a następnie podłącz go do węzła:
from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)
child.add_entity(mesh)
print(f"Mesh name: {mesh.name}")
print(f"Vertex count: {len(mesh.control_points)}")
print(f"Polygon count: {mesh.polygon_count}")Vector4(x, y, z, w) reprezentuje współrzędną jednorodną. Użyj w=1 dla zwykłych położeń punktów.
create_polygon(*indices) akceptuje indeksy wierzchołków i rejestruje jedną ścianę w liście wielokątów. Przekaż trzy indeksy dla trójkąta, cztery dla czworokąta.
Krok 4: Ustaw transformacje węzłów
Każdy węzeł ma Transform, który kontroluje jego pozycję, orientację i rozmiar w przestrzeni lokalnej:
from aspose.threed.utilities import Vector3, Quaternion
##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)
##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)
##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)Transformacje są kumulatywne: pozycja w przestrzeni świata węzła potomnego jest kompozycją jego własnej transformacji ze wszystkimi transformacjami przodków. Odczytaj oceniony wynik w przestrzeni świata z node.global_transform (niemutowalny, tylko do odczytu).
Krok 5: Przeglądaj graf sceny rekurencyjnie
Przejdź całe drzewo, rekurencyjnie przechodząc przez node.child_nodes:
def traverse(node, depth=0):
indent = " " * depth
entity_type = type(node.entity).__name__ if node.entity else "None"
print(f"{indent}{node.name} [{entity_type}]")
for child in node.child_nodes:
traverse(child, depth + 1)
traverse(scene.root_node)Przykładowe wyjście dla sceny zbudowanej powyżej (nazwa węzła głównego jest pustym ciągiem):
[None]
parent [None]
child [Mesh]Dla scen z wieloma encjami na węzeł, iteruj node.entities zamiast node.entity:
def traverse_full(node, depth=0):
indent = " " * depth
entity_names = [type(e).__name__ for e in node.entities] or ["None"]
print(f"{indent}{node.name} [{', '.join(entity_names)}]")
for child in node.child_nodes:
traverse_full(child, depth + 1)
traverse_full(scene.root_node)Krok 6: Zapisz scenę
Przekaż ścieżkę pliku do scene.save(). Format jest wywnioskowany z rozszerzenia pliku:
scene.save("scene.gltf") # JSON glTF 2.0
scene.save("scene.glb") # Binary GLB container
scene.save("scene.obj") # Wavefront OBJ
scene.save("scene.stl") # STLAby przekazać opcje specyficzne dla formatu, przekaż obiekt save-options jako drugi argument:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Wskazówki i najlepsze praktyki
- Nazwij każdy węzeł. Nadawanie węzłom znaczących nazw znacznie ułatwia debugowanie przeglądania i zapewnia, że nazwy są zachowywane w wyeksportowanym pliku.
- Jedna siatka na węzeł. Utrzymywanie encji 1:1 z węzłami upraszcza przekształcenia i zapytania kolizyjne.
- Używaj
create_child_nodezamiast ręcznego dołączania. Ustawia referencję rodzica automatycznie i jest mniej podatny na błędy. - Odczytuj
global_transformpo zbudowaniu hierarchii. Wynik w przestrzeni świata jest stabilny dopiero po ustawieniu wszystkich transformacji przodków. - Nie modyfikuj drzewa podczas przeglądania. Dodawanie lub usuwanie węzłów potomnych podczas iteracji
child_nodesspowoduje nieprzewidywalne zachowanie. Najpierw zbierz węzły, potem modyfikuj. - Punkty kontrolne używają
Vector4, nieVector3. Zawsze przekazujw=1dla zwykłych pozycji wierzchołków;w=0reprezentuje wektor kierunku (nie punkt). mesh.control_pointszwraca kopię. Właściwośćcontrol_pointszwracalist(self._control_points)— dodawanie elementów do zwróconej listy nie modyfikuje siatki. Zawsze dodawaj domesh._control_pointsbezpośrednio podczas programowego budowania geometrii. To znane ograniczenie biblioteki; publiczne API mutacji nie istnieje jeszcze.
Częste problemy
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Zabezpiecz za pomocą if node.entity is not None przed dostępem do właściwości encji. Węzeł bez encji ma entity = None. |
Mesh appears at the origin despite setting translation | transform.translation stosuje lokalne przesunięcie. Jeśli węzeł nadrzędny ma transformację inną niż tożsamość, pozycja w świecie może się różnić. Sprawdź global_transform. |
Child nodes missing after scene.save() / reload | Niektóre formaty (OBJ) spłaszczają hierarchię. Użyj glTF lub COLLADA, aby zachować pełne drzewo węzłów. |
polygon_count is 0 after mesh.create_polygon(...) | polygon_count wynosi 0 po mesh.create_polygon(...). Sprawdź, czy indeksy wierzchołków przekazywane do create_polygon mieszczą się w zakresie (0 do len(control_points) - 1). |
Node.get_child(name) returns None | Nazwa jest rozróżniana pod względem wielkości liter. Potwierdź dokładny ciąg nazwy użyty w czasie tworzenia. |
| Traversal visits nodes in unexpected order | child_nodes zwraca dzieci w kolejności wstawiania (kolejność, w której wywołano add_child_node / create_child_node). |