Граф на сцената

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))  # 0

Scene е входната точка за всичко: зареждане на файлове, запазване на файлове, създаване на анимационни клипове и достъп до дървото от възли.


Стъпка 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.
Мрежата се появява в началото, въпреки задаването translationtransform.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 беше наречен).
 Български