Граф сцени
scene graph — це фундаментальна модель даних Aspose.3D FOSS for Python. Кожен 3D‑файл, незалежно від того, чи завантажений з диска, чи створений у пам’яті, представлений у вигляді дерева об’єктів Node, коренем якого є Scene.root_node. Кожен вузол може містити дочірні вузли та один або кілька об’єктів Entity (меші, камери, освітлення). Розуміння scene graph дає вам прямий доступ до геометрії, матеріалів та просторових трансформацій кожного об’єкта у сцені.
Встановлення та налаштування
Встановіть бібліотеку з PyPI:
pip install aspose-3d-fossНе потрібні нативні розширення, компілятори або додаткові системні пакети. Для повних інструкцій з встановленням перегляньте Installation Guide.
Огляд: Концепції графа сцени
Граф сцени в 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
└── ...| Object | Role |
|---|---|
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 і приєднати її
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: Встановити перетворення вузлів
Кожен вузол має 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Для параметрів, специфічних для формату, передайте об’єкт save-options як другий аргумент:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)Поради та кращі практики
- Назвіть кожен вузол. Надання вузлам змістовних імен значно спрощує налагодження обходів і забезпечує збереження імен у експортованому файлі.
- Один меш на вузол. Підтримка співвідношення 1:1 між сутностями та вузлами спрощує трансформації та запити зіткнень.
- Використовуйте
create_child_nodeзамість ручного приєднання. Він автоматично встановлює посилання на батька і менш схильний до помилок. - Зчитайте
global_transformпісля побудови ієрархії. Результат у світовому просторі стабільний лише після встановлення всіх трансформацій предків. - Не змінюйте дерево під час обходу. Додавання або видалення дочірніх вузлів під час ітерації
child_nodesпризведе до непередбачуваної поведінки. Спочатку зберіть вузли, потім модифікуйте їх. - Контрольні точки використовуйте
Vector4, а неVector3. Завжди передавайтеw=1для звичайних позицій вершин;w=0представляє вектор напрямку (не точку). mesh.control_pointsповертає копію. Властивістьcontrol_pointsповертаєlist(self._control_points)— додавання елементів до повернутого списку не змінює меш. Завжди додавайте безпосередньо доmesh._control_pointsпід час програмного створення геометрії. Це відома обмеженість бібліотеки; публічного API для мутації ще не існує.
Поширені проблеми
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | Захистіть за допомогою if node.entity is not None перед доступом до властивостей сутності. Вузол без сутностей має entity = None. |
Mesh appears at the origin despite setting translation | transform.translation застосовує локальний зсув. Якщо батьківський вузол сам має трансформацію, відмінну від одиничної, світова позиція може бути іншою. Перевірте global_transform. |
Child nodes missing after scene.save() / reload | Деякі формати (OBJ) сплющують ієрархію. Використовуйте glTF або COLLADA, щоб зберегти повне дерево вузлів. |
polygon_count is 0 after mesh.create_polygon(...) | Переконайтеся, що індекси вершин, передані до create_polygon, знаходяться в діапазоні (0 до len(control_points) - 1). |
Node.get_child(name) returns None | Назва чутлива до регістру. Підтвердіть точний рядок назви, використаний під час створення. |
| Traversal visits nodes in unexpected order | child_nodes повертає дочірні елементи у порядку вставки (у порядку, у якому викликали add_child_node / create_child_node). |