Граф на сцената
A граф на сцената е фундаменталният модел на данни на Aspose.3D FOSS за Python. Всеки 3D файл, независимо дали е зареден от диск или създаден в паметта, се представя като дърво от Node обекти, коренящи се в Scene.root_node. Всеки възел може да съдържа дъщерни възли и един или повече Entity обекти (мрежи, камери, светлини). Разбирането на графа на сцената ви дава директен достъп до геометрията, материалите и пространствените трансформации на всеки обект в сцената.
Инсталиране и настройка
Инсталирайте библиотеката от PyPI:
pip install aspose-3d-fossНе се изискват нативни разширения, компилатори или допълнителни системни пакети. За пълни инструкции за инсталиране, вижте Ръководство за инсталиране.
Преглед: Концепции за сценичния граф
Сценичният граф в Aspose.3D FOSS следва проста йерархия на съдържание:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Обект | Роля |
|---|---|
Scene | Контейнер от най-високо ниво. Съдържа root_node, asset_info, animation_clips, и sub_scenes. |
Node | Именуван възел в дървото. Има родител, нула или повече деца, нула или повече обекти и локален Transform. |
Entity | Геометрия или обект от сцената, прикрепен към възел. Чести типове обекти: Mesh, Camera, Light. |
Transform | Локална позиция, ротация и мащаб за възел. Резултатът в световното пространство се чете от global_transform. |
Стъпка по стъпка: Програмирано изграждане на сценичен граф
Стъпка 1: Създаване на сцена
Нов Scene винаги започва с празен 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 е входната точка за всичко: зареждане на файлове, запазване на файлове, създаване на анимационни клипове и достъп до дървото от възли.
Стъпка 2: Създаване на дъщерни възли
Използвайте create_child_node(name) за да добавите именувани възли към дървото:
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)) # 1Алтернативно, създайте самостоятелен Node и го прикачете изрично:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)И двата подхода дават един и същи резултат. create_child_node е по-кратко за вградено конструиране.
Стъпка 3: Създаване на Mesh Entity и прикрепяне към него
A Mesh съхранява данни за върховете (control_points) и топология на лицата (polygons). Създайте такъв, добавете геометрия и след това го прикрепете към възел:
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) представлява хомогенна координата. Използвайте w=1 за обикновени позиции на точки.
create_polygon(*indices) приема индекси на върхове и регистрира едно лице в списъка с полигони. Предайте три индекса за триъгълник, четири за четириъгълник.
Стъпка 4: Задаване на трансформациите на възела
Всеки възел има a Transform което контролира позицията, ориентацията и размера му в локалното пространство:
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)Трансформациите са кумулативни: позицията в световното пространство на дъщерен възел е съставена от собствената му трансформация и всички трансформации на предците. Прочетете изчисления резултат в световното пространство от node.global_transform (непроменяем, само за четене).
Стъпка 5: Рекурсивно обхождане на графа на сцената
Прегледайте цялото дърво, като рекурсивно преминавате през 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)Примерен изход за сцената, построена по-горе (името на кореновия възел е празен низ):
[None]
parent [None]
child [Mesh]За сцени с множество единици на възел, итерирайте node.entities вместо 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)Стъпка 6: Записване на сцената
Подайте път към файл на scene.save(). Форматът се определя от разширението на файла:
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") # STLЗа опции, специфични за формата, подайте обект с опции за запис като втори аргумент:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Съвети и най‑добри практики
- Именувайте всеки възел. Даването на смислени имена на възлите прави отстраняването на грешки при обходите много по-лесно и осигурява запазването на имената в експортирания файл.
- Един mesh на възел. Поддържането на същностите 1:1 с възлите опростява трансформациите и заявките за сблъсъци.
- Използвайте
create_child_nodeвместо ръчно прикачване. Той задава референцията към родителя автоматично и е по-малко податлив на грешки. - Прочетете
global_transformслед изграждане на йерархията. Резултатът в world-space е стабилен само след като са зададени всички ancestor transforms. - Не променяйте дървото по време на обхождане. Добавяне или премахване на дъщерни възли по време на итериране
child_nodesще доведе до непредвидимо поведение. Съберете възлите първо, след това променете ги. - Контролните точки използват
Vector4, а неVector3. Винаги предавайтеw=1за обикновени позиции на върховете;w=0представлява вектор на посоката (не точка). mesh.control_pointsвръща копие. Товаcontrol_pointsсвойството връщаlist(self._control_points)— добавянето към върнатия списък не променя мрежата. Винаги добавяйте къмmesh._control_pointsдиректно, когато създавате геометрия програмно. Това е известна ограниченост на библиотеката; публичен API за мутация все още не съществува.
Чести проблеми
| Проблем | Резолюция |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Защита с if node.entity is not None преди достъп до свойствата на обекта. Възел без обекти има entity = None. |
Мрежата се появява в началото, въпреки задаването translation | transform.translation прилага локално отместване. Ако родителският възел сам има трансформация, различна от идентичната, световната позиция може да се различава. Проверете global_transform. |
Дъщерни възли липсват след scene.save() / презареждане | Някои формати (OBJ) изравняват йерархията. Използвайте glTF или COLLADA, за да запазите пълното дърво от възли. |
polygon_count е 0 след mesh.create_polygon(...) | Проверете, че индексите на върховете, предадени на create_polygon са в диапазона (0 до len(control_points) - 1). |
Node.get_child(name) връща None | Името е чувствително към регистъра. Потвърдете точния низ на името, използван при създаването. |
| Обхождането посещава възлите в неочакван ред | child_nodes връща децата в реда на вмъкване (редът add_child_node / create_child_node беше наречен). |