如何在 Python 中使用 Aspose.3D 构建 3D 网格

如何在 Python 中使用 Aspose.3D 构建 3D 网格

Aspose.3D FOSS for Python 让您完全在代码中构建 3D 几何体:无需外部建模工具。您创建一个 Mesh,用顶点位置(control_points)和面定义(polygons)填充它,附加可选的顶点属性,如法线,然后将场景保存为任何受支持的格式。

分步指南

步骤 1:安装软件包

从 PyPI 安装 Aspose.3D FOSS。无需本机扩展或编译工具链。

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,并可选择提供描述性名称:

from aspose.threed.entities import Mesh

mesh = Mesh("triangle")

网格最初是空的:没有顶点,没有多边形。您将在以下步骤中填充它。


步骤 4:添加控制点(顶点)

控制点是顶点位置。每个顶点存储为 Vector4(x, y, z, w),其中 w=1 表示三维空间中的一点:

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)}")

重要: mesh.control_points 返回内部顶点列表的 副本(getter 执行 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 中的有效位置(基于 0,且在范围内)。对于外向法线,绕向顺序为逆时针。


步骤 6:添加顶点法线

顶点法线作为 VertexElement 附加到网格上。使用 mesh.create_element()VertexElementType.NORMALMappingMode.CONTROL_POINTReferenceMode.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步:将 Mesh 附加到 Node 并保存

将网格添加到节点,然后保存场景:

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")

常见问题

问题解决方案
IndexErrorcreate_polygon确认所有索引都在 range(len(mesh.control_points)) 范围内。索引从 0 开始。
Mesh exports with zero verticesmesh.control_points.append(...) 静默丢弃顶点,因为该属性返回的是副本。请改用 mesh._control_points.append(...)
Normals count does not match vertex count使用 MappingMode.CONTROL_POINT + ReferenceMode.DIRECT 时,normals.data 必须恰好有 len(control_points) 条目。
Mesh missing from saved file确认已在 scene.save() 之前调用了 node.add_entity(mesh)。未附加到任何节点的网格不会被导出。
Wrong winding order (face appears invisible)逆时针的顶点顺序会产生指向外部的法线。请在 create_polygon 中反转索引顺序以翻转它。
polygon_count returns 0polygon_count 读取的列表与 polygons 相同。如果未调用 create_polygon,该列表为空。
Normals appear incorrect in viewer确保所有法向量都是单位长度。使用 n / abs(n) 计算或传入预归一化的值。

常见问题

控制点中 Vector3Vector4 有何区别?

control_points 存储 Vector4 对象。w 组件是齐次坐标:对顶点位置使用 w=1,对方向向量(如法线)使用 w=0Vector3 用于变换(平移、缩放),但不用于几何存储。

我可以使用四边形而不是三角形来构建网格吗?

是的。使用四个索引调用 mesh.create_polygon(0, 1, 2, 3) 来定义一个四边形。某些保存目标(STL、3MF)需要三角形,并会自动将四边形三角化。glTF 和 COLLADA 会保留四边形。

如何添加 UV 坐标?

使用 mesh.create_element_uv(TextureMapping.DIFFUSE, MappingMode.POLYGON_VERTEX) 为漫反射通道创建一个 VertexElementUV,然后用 Vector4 条目填充其 data 列表。UV 坐标使用 xyzw 通常为 0。第一个参数必须是一个 TextureMapping 常量(例如 TextureMapping.DIFFUSE),用于标识 UV 层所属的纹理槽。

网格需要法线才能正确导出吗?

不。法线是可选的。如果省略,大多数查看器会根据多边形的环绕顺序计算每个面的法线。添加显式的每顶点法线会产生更平滑的着色。

我能在一个节点上添加多个网格吗?

是的。多次调用 node.add_entity(mesh)。每次调用都会向 node.entities 添加一个新实体。某些格式在导出时可能会将多个实体合并为一个。

如何对混合多边形类型的网格进行三角化?

调用 mesh.triangulate() 将所有四边形和 N‑边形就地转换为三角形。这在保存到仅支持三角形的格式之前非常有用。

 中文