Scénový graf
A graf scény je základní datový model Aspose.3D FOSS pro Python. Každý 3D soubor, ať už načtený z disku nebo vytvořený v paměti, je reprezentován jako strom Node objektů s kořenem v Scene.root_node. Každý uzel může obsahovat poduzly a jeden nebo více Entity objektů (meshe, kamery, světla). Porozumění grafu scény vám poskytuje přímý přístup k geometrii, materiálům a prostorovým transformacím každého objektu ve scéně.
Instalace a nastavení
Nainstalujte knihovnu z PyPI:
pip install aspose-3d-fossNení vyžadováno žádné nativní rozšíření, kompilátory ani další systémové balíčky. Pro úplné pokyny k instalaci viz Instalační příručka.
Přehled: Koncepty scénového grafu
Scénový graf v Aspose.3D FOSS používá jednoduchou hierarchii obsahování:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Objekt | Role |
|---|---|
Scene | Kontejner nejvyšší úrovně. Obsahuje root_node, asset_info, animation_clips, a sub_scenes. |
Node | Pojmenovaný uzel stromu. Má rodiče, nula nebo více potomků, nula nebo více entit a lokální Transform. |
Entity | Geometrii nebo objekt scény připojený k uzlu. Běžné typy entit: Mesh, Camera, Light. |
Transform | Lokální pozice, rotace a měřítko uzlu. Výsledek ve světovém prostoru se čte z global_transform. |
Krok za krokem: Programové vytvoření scénového grafu
Krok 1: Vytvořte scénu
Nový Scene vždy začíná prázdným 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 je vstupním bodem pro vše: načítání souborů, ukládání souborů, vytváření animačních klipů a přístup k stromu uzlů.
Krok 2: Vytvořte podřízené uzly
Použijte create_child_node(name) pro přidání pojmenovaných uzlů do stromu:
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)) # 1Alternativně vytvořte samostatný Node a připojte jej explicitně:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)Oba přístupy produkují stejný výsledek. create_child_node je stručnější pro inline konstrukci.
Krok 3: Vytvořte entitu Mesh a připojte ji
A Mesh ukládá data vrcholů (control_points) a topologii ploch (polygons). Vytvořte jeden, přidejte geometrii a poté jej připojte k uzlu:
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 homogenní souřadnici. Použijte w=1 pro běžné pozice bodů.
create_polygon(*indices) přijímá indexy vrcholů a zaregistruje jednu plochu v seznamu polygonů. Předávejte tři indexy pro trojúhelník, čtyři pro čtyřúhelník.
Krok 4: Nastavte transformace uzlů
Každý uzel má Transform který řídí jeho pozici, orientaci a velikost v lokálním prostoru:
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)Transformace jsou kumulativní: pozice uzlu v globálním prostoru je složením jeho vlastní transformace se všemi transformacemi předků. Přečtěte vyhodnocený výsledek v globálním prostoru z node.global_transform (neměnný, jen pro čtení).
Krok 5: Rekurzivně procházejte graf scény
Procházejte celý strom rekurzivním procházením přes 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)Příklad výstupu pro scénu vytvořenou výše (název kořenového uzlu je prázdný řetězec):
[None]
parent [None]
child [Mesh]Pro scény s více entitami na uzel iterujte node.entities místo 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: Uložte scénu
Předat cestu k souboru scene.save(). Formát je odvozen z přípony souboru:
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") # STLPro formátově specifické volby předávejte objekt s možnostmi uložení jako druhý argument:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Tipy a osvědčené postupy
- Pojmenujte každý uzel. Dávat uzlům smysluplné názvy usnadňuje ladění průchodů a zajišťuje, že názvy jsou zachovány v exportovaném souboru.
- Jeden mesh na uzel. Udržování entit 1:1 s uzly zjednodušuje transformace a dotazy na kolize.
- Použijte
create_child_nodemísto ručního připojení. Automaticky nastaví odkaz na rodiče a je méně náchylné k chybám. - Přečtěte
global_transformpo vytvoření hierarchie. Výsledek ve světovém prostoru je stabilní až po nastavení všech transformací předků. - Neměňte strom během průchodu. Přidávání nebo odstraňování podřízených uzlů během iterace
child_nodespovede k nepředvídatelnému chování. Nejprve shromážděte uzly, pak je upravte. - Řídicí body používají
Vector4, neVector3. Vždy předávejtew=1pro běžné pozice vrcholů;w=0reprezentuje směrový vektor (ne bod). mesh.control_pointsvrací kopii. Tencontrol_pointsvlastnost vracílist(self._control_points)— přidávání do vráceného seznamu nemění mesh. Vždy přidávejte domesh._control_pointspřímo při programovém vytváření geometrie. Jedná se o známé omezení knihovny; veřejné API pro mutaci zatím neexistuje.
Běžné problémy
| Problém | Řešení |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Ochrana pomocí if node.entity is not None před přístupem k vlastnostem entity. Uzel bez entit má entity = None. |
Mesh se zobrazuje v počátku navzdory nastavení translation | transform.translation aplikuje lokální posun. Pokud má nadřazený uzel sám o sobě netransformaci identity, může se světová pozice lišit. Zkontrolujte global_transform. |
Podřízené uzly chybí po scene.save() / přenačtení | Některé formáty (OBJ) zploští hierarchii. Použijte glTF nebo COLLADA k zachování celé stromové struktury uzlů. |
polygon_count je 0 po mesh.create_polygon(...) | Ověřte, že indexy vrcholů předané do create_polygon jsou v rozsahu (0 do len(control_points) - 1). |
Node.get_child(name) vrací None | Název rozlišuje velká a malá písmena. Potvrďte přesný řetězec názvu použitý při vytváření. |
| Procházení navštěvuje uzly v neočekávaném pořadí | child_nodes vrací potomky v pořadí vložení (pořadí add_child_node / create_child_node byl nazván). |