How to Build a 3D Mesh with Aspose.3D in Python

How to Build a 3D Mesh with Aspose.3D in Python

Aspose.3D FOSS for Python lets you build 3D geometry entirely in code: no external modelling tool required. You create a Mesh, populate it with vertex positions (control_points) and face definitions (polygons), attach optional vertex attributes such as normals, then save the scene to any supported format.

Step-by-Step Guide

Step 1: Install the Package

Install Aspose.3D FOSS from PyPI. No native extensions or compiler toolchain is required.

pip install aspose-3d-foss

Verify the installation:

from aspose.threed import Scene
print("Aspose.3D FOSS ready")

Supported Python versions: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.


Step 2: Create a Scene and a Node

Every mesh must live inside a scene graph. Create a Scene and add a named Node to hold the mesh:

from aspose.threed import Scene

scene = Scene()
node = scene.root_node.create_child_node("triangle")

The node name is preserved in the exported file and is useful for debugging and later retrieval via node.get_child("triangle").


Step 3: Create a Mesh Object

Instantiate a Mesh with an optional descriptive name:

from aspose.threed.entities import Mesh

mesh = Mesh("triangle")

The mesh is initially empty: no vertices, no polygons. You populate it in the following steps.


Step 4: Add Control Points (Vertices)

Control points are the vertex positions. Each vertex is stored as a Vector4(x, y, z, w) where w=1 indicates a point in 3D space:

from aspose.threed.utilities import Vector4

##Vertex 0: origin
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)}")

control_points is a standard Python list, so append, extend, and slice operations all work.


Step 5: Create Polygon Faces

Define the face topology using vertex indices. Pass vertex indices to create_polygon(). Three indices produce a triangle; four produce a quad:

##Triangle: connect vertices 0 → 1 → 2
mesh.create_polygon(0, 1, 2)

print(f"Polygon count: {mesh.polygon_count}")

For a quad mesh you would pass four indices: mesh.create_polygon(0, 1, 2, 3).

Indices must be valid positions in control_points (0-based, within range). Winding order is counter-clockwise for outward-facing normals.


Step 6: Add Vertex Normals

Vertex normals are stored as a VertexElement attached to the mesh. Use mesh.create_element() with VertexElementType.NORMAL, MappingMode.CONTROL_POINT, and ReferenceMode.DIRECT:

from aspose.threed.entities import VertexElementType, MappingMode, ReferenceMode
from aspose.threed.utilities import Vector4

##Create the normal element
normals = 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.data.extend([
    Vector4(0, 0, 1, 0),   # vertex 0
    Vector4(0, 0, 1, 0),   # vertex 1
    Vector4(0, 0, 1, 0),   # vertex 2
])

print(f"Normal count: {len(normals.data)}")

MappingMode.CONTROL_POINT means one normal per vertex. ReferenceMode.DIRECT means the normal data is read in the same order as the control points (no extra index buffer).

Normal vectors use Vector4(x, y, z, w) with w=0 to indicate a direction rather than a position.


Step 7: Attach the Mesh to the Node and Save

Add the mesh to the node, then save the scene:

node.add_entity(mesh)

scene.save("triangle.gltf")
print("Saved triangle.gltf")

The complete working script (all steps combined):

from aspose.threed import Scene
from aspose.threed.entities import Mesh, VertexElementType, MappingMode, ReferenceMode
from aspose.threed.utilities import Vector3, Vector4

scene = Scene()
node = scene.root_node.create_child_node("triangle")

mesh = Mesh("triangle")

##Add 3 vertices (x, y, z, w)
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
normals = mesh.create_element(VertexElementType.NORMAL, MappingMode.CONTROL_POINT, ReferenceMode.DIRECT)
normals.data.extend([
    Vector4(0, 0, 1, 0),
    Vector4(0, 0, 1, 0),
    Vector4(0, 0, 1, 0),
])

node.add_entity(mesh)
scene.save("triangle.gltf")

Common Issues

IssueResolution
IndexError in create_polygonVerify that all indices are within range(len(mesh.control_points)). Indices are 0-based.
Normals count does not match vertex countWhen using MappingMode.CONTROL_POINT + ReferenceMode.DIRECT, normals.data must have exactly len(control_points) entries.
Mesh missing from saved fileConfirm that node.add_entity(mesh) was called before scene.save(). A mesh not attached to any node is not exported.
Wrong winding order (face appears invisible)Counter-clockwise vertex order produces an outward-facing normal. Reverse the index order in create_polygon to flip it.
polygon_count returns 0polygon_count reads the same list as polygons. If create_polygon was not called, the list is empty.
Normals appear incorrect in viewerEnsure all normal vectors are unit-length. Compute with n / abs(n) or pass pre-normalised values.

Frequently Asked Questions

What is the difference between Vector3 and Vector4 for control points?

control_points stores Vector4 objects. The w component is the homogeneous coordinate: use w=1 for vertex positions and w=0 for direction vectors such as normals. Vector3 is used for transforms (translation, scale) but not for geometry storage.

Can I build a mesh with quads instead of triangles?

Yes. Call mesh.create_polygon(0, 1, 2, 3) with four indices to define a quad. Some save targets (STL, 3MF) require triangles and will triangulate quads automatically. glTF and COLLADA preserve quads.

How do I add UV coordinates?

Use mesh.create_element_uv(TextureMapping.DIFFUSE, MappingMode.POLYGON_VERTEX) to create a VertexElementUV for the diffuse channel, then populate its data list with Vector4 entries. UV coordinates use x and y; z and w are typically 0. The first argument must be a TextureMapping constant (e.g., TextureMapping.DIFFUSE) identifying which texture slot the UV layer belongs to.

Does the mesh need normals to export correctly?

No. Normals are optional. If omitted, most viewers compute per-face normals from the polygon winding order. Adding explicit per-vertex normals produces smoother shading.

Can I add multiple meshes to one node?

Yes. Call node.add_entity(mesh) multiple times. Each call appends a new entity to node.entities. Some formats may flatten multiple entities into one on export.

How do I triangulate a mesh with mixed polygon types?

Call mesh.triangulate() to convert all quads and N-gons to triangles in place. This is useful before saving to formats that only support triangles.