Как создать 3D‑сетку с помощью Aspose.3D в Python

Как создать 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 возвращает 0polygon_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-гонов в треугольники на месте. Это полезно перед сохранением в форматы, которые поддерживают только треугольники.

 Русский