Как создать 3D‑сетку с помощью Aspose.3D в Python
Aspose.3D FOSS for Python позволяет создавать 3D‑геометрию полностью в коде: не требуется внешнее средство моделирования. Вы создаёте Mesh, заполняете его позициями вершин (control_points) и определениями граней (polygons), при необходимости добавляете атрибуты вершин, такие как нормали, затем сохраняете сцену в любой поддерживаемый формат.
Пошаговое руководство
Шаг 1: Установить пакет
Установите Aspose.3D FOSS из PyPI. Никакие нативные расширения или набор средств компилятора не требуются.
pip install aspose-3d-fossПроверьте установку:
from aspose.threed import Scene
print("Aspose.3D FOSS ready")Поддерживаемые версии Python: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.
Шаг 2: Создать сцену и узел
Каждая сетка должна находиться внутри графа сцены. Создайте Scene и добавьте именованный Node для размещения сетки:
from aspose.threed import Scene
scene = Scene()
node = scene.root_node.create_child_node("triangle")Имя узла сохраняется в экспортированном файле и полезно для отладки и последующего получения через node.get_child("triangle").
Шаг 3: Создать объект Mesh
Создайте экземпляр Mesh с необязательным описательным именем:
from aspose.threed.entities import Mesh
mesh = Mesh("triangle")Сетка изначально пуста: нет вершин, нет полигонов. Вы заполняете её следующими шагами.
Шаг 4: Добавить контрольные точки (вершины)
Контрольные точки — это позиции вершин. Каждая вершина хранится как Vector4(x, y, z, w), где w=1 указывает на точку в 3‑мерном пространстве:
from aspose.threed.utilities import Vector4
##Vertex 0: origin
# Note: control_points returns a copy of the internal vertex list.
# Appending to the returned copy discards the vertex silently.
# Use _control_points to mutate the backing list directly.
# This is a known library limitation — a public add_control_point() API is not yet available.
mesh._control_points.append(Vector4(0.0, 0.0, 0.0, 1.0))
##Vertex 1: 1 unit along X
mesh._control_points.append(Vector4(1.0, 0.0, 0.0, 1.0))
##Vertex 2: apex
mesh._control_points.append(Vector4(0.5, 1.0, 0.0, 1.0))
print(f"Vertices added: {len(mesh.control_points)}")Important: mesh.control_points возвращает копию внутреннего списка вершин (геттер выполняет list(self._control_points)). Вызов mesh.control_points.append(v) добавляет в копию, а не в сетку, поэтому вершина тихо отбрасывается. Всегда используйте mesh._control_points.append(v) для добавления вершин. Доступ к приватному состоянию через _control_points является известным обходным решением; интерфейс может измениться в будущей версии библиотеки.
Шаг 5: Создать полигональные грани
Определите топологию грани, используя индексы вершин. Передайте индексы вершин в create_polygon(). Три индекса образуют треугольник; четыре — четырёхугольник:
##Triangle: connect vertices 0 → 1 → 2
mesh.create_polygon(0, 1, 2)
print(f"Polygon count: {mesh.polygon_count}")Для квадратичной сетки вы бы передали четыре индекса: mesh.create_polygon(0, 1, 2, 3).
Индексы должны быть допустимыми позициями в control_points (нумерация с нуля, в пределах диапазона). Порядок обхода должен быть против часовой стрелки для наружных нормалей.
Шаг 6: Добавить нормали вершин
Нормали вершин хранятся как VertexElement, прикреплённый к сетке. Используйте mesh.create_element() с VertexElementType.NORMAL, MappingMode.CONTROL_POINT и ReferenceMode.DIRECT:
from aspose.threed.entities import VertexElementType, MappingMode, ReferenceMode, VertexElementNormal
from aspose.threed.utilities import Vector4, FVector4
##Create the normal element (returns VertexElementNormal, a VertexElementFVector subclass)
normals: VertexElementNormal = mesh.create_element(
VertexElementType.NORMAL,
MappingMode.CONTROL_POINT,
ReferenceMode.DIRECT
)
##One normal per vertex: all pointing out of the XY plane (0, 0, 1)
normals.set_data([
FVector4(0, 0, 1, 0), # vertex 0
FVector4(0, 0, 1, 0), # vertex 1
FVector4(0, 0, 1, 0), # vertex 2
])
print("Normal layer attached.")MappingMode.CONTROL_POINT означает одну нормаль на вершину. ReferenceMode.DIRECT означает, что данные нормалей читаются в том же порядке, что и контрольные точки (без дополнительного буфера индексов).
Нормальные векторы используют FVector4(x, y, z, w) с w=0 для указания направления, а не позиции. FVector4 — это вектор одинарной точности с плавающей запятой; данные атрибутов вершин в подклассах VertexElementFVector используют этот тип.
Шаг 7: Присоединить сетку к узлу и сохранить
Добавьте сетку к узлу, затем сохраните сцену:
node.add_entity(mesh)
scene.save("triangle.gltf")
print("Saved triangle.gltf")Полный рабочий скрипт (все шаги объединены):
from aspose.threed import Scene
from aspose.threed.entities import Mesh, VertexElementType, MappingMode, ReferenceMode, VertexElementNormal
from aspose.threed.utilities import Vector3, Vector4, FVector4
scene = Scene()
node = scene.root_node.create_child_node("triangle")
mesh = Mesh("triangle")
##Add 3 vertices (x, y, z, w)
# Use _control_points to mutate the backing list directly (control_points returns a copy)
mesh._control_points.append(Vector4(0.0, 0.0, 0.0, 1.0))
mesh._control_points.append(Vector4(1.0, 0.0, 0.0, 1.0))
mesh._control_points.append(Vector4(0.5, 1.0, 0.0, 1.0))
##Create a triangle polygon
mesh.create_polygon(0, 1, 2)
##Add normals (create_element returns VertexElementNormal, a VertexElementFVector subclass)
normals: VertexElementNormal = mesh.create_element(VertexElementType.NORMAL, MappingMode.CONTROL_POINT, ReferenceMode.DIRECT)
normals.set_data([
FVector4(0, 0, 1, 0),
FVector4(0, 0, 1, 0),
FVector4(0, 0, 1, 0),
])
node.add_entity(mesh)
scene.save("triangle.gltf")Распространённые проблемы
| Проблема | Решение |
|---|---|
IndexError в create_polygon | Убедитесь, что все индексы находятся в пределах range(len(mesh.control_points)). Индексы начинаются с 0. |
| Экспорт сетки с нулевым количеством вершин | mesh.control_points.append(...) тихо отбрасывает вершины, потому что свойство возвращает копию. Вместо этого используйте mesh._control_points.append(...). |
| Количество нормалей не совпадает с количеством вершин | При использовании MappingMode.CONTROL_POINT + ReferenceMode.DIRECT, normals.data должен содержать ровно len(control_points) элементов. |
| Сетка отсутствует в сохранённом файле | Убедитесь, что node.add_entity(mesh) был вызван до scene.save(). Сетка, не привязанная к узлу, не экспортируется. |
| Неправильный порядок обхода (граница кажется невидимой) | Порядок вершин против часовой стрелки создаёт наружную нормаль. Инвертируйте порядок индексов в create_polygon, чтобы изменить его. |
polygon_count возвращает 0 | polygon_count читает тот же список, что и polygons. Если create_polygon не был вызван, список пуст. |
| Нормали выглядят некорректно в просмотрщике | Убедитесь, что все векторы нормалей имеют единичную длину. Вычислите их с помощью n / abs(n) или передайте предварительно нормализованные значения. |
Часто задаваемые вопросы
В чём разница между Vector3 и Vector4 для контрольных точек?
control_points хранит объекты Vector4. Компонент w является однородной координатой: используйте w=1 для позиций вершин и w=0 для векторных направлений, таких как нормали. Vector3 используется для преобразований (перемещение, масштаб), но не для хранения геометрии.
Могу ли я построить сетку из четырёхугольников вместо треугольников?
Да. Вызовите mesh.create_polygon(0, 1, 2, 3) с четырьмя индексами, чтобы определить четырёхугольник. Некоторые целевые форматы сохранения (STL, 3MF) требуют треугольников и автоматически триангулируют четырёхугольники. glTF и COLLADA сохраняют четырёхугольники.
Как добавить UV‑координаты?
Используйте mesh.create_element_uv(TextureMapping.DIFFUSE, MappingMode.POLYGON_VERTEX) для создания VertexElementUV для диффузного канала, затем заполните его список data записями Vector4. UV‑координаты используют x и y; z и w обычно равны 0. Первый аргумент должен быть константой TextureMapping (например, TextureMapping.DIFFUSE), указывающей, к какому слоту текстуры относится слой UV.
Требуются ли нормали у меша для корректного экспорта?
Нет. Нормали являются необязательными. Если их опустить, большинство просмотрщиков вычисляют нормали граней по порядку обхода полигонов. Добавление явных нормалей вершин приводит к более плавному затенению.
Можно ли добавить несколько мешей в один узел?
Да. Вызывайте node.add_entity(mesh) несколько раз. Каждый вызов добавляет новую сущность в node.entities. Некоторые форматы могут объединять несколько сущностей в одну при экспорте.
Как триангулировать сетку со смешанными типами полигонов?
Вызовите mesh.triangulate(), чтобы преобразовать все четырёхугольники и N-гонов в треугольники на месте. Это полезно перед сохранением в форматы, которые поддерживают только треугольники.