Alternative in 2.80 to create meshes from Python using the tessfaces API?

In https://wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/Mesh_API it is noted that tesselated faces have been removed in 2.80 and indeed no traces seem left in the API. However, it was a usable way to get large triangle and quad meshes into Blender from Python with reasonable efficiency, provided you put your data in the right array layout. E.g. using a few numpy arrays with the correct layout was all it took and it would create meshes much quicker than any of the importers would.

Is there an API available in 2.80 that offers something similar? I.e. a way to create meshes from a set of numpy arrays? I know of the SoC effort to improve performance of certain importers (PLY, STL, … I believe), but that doesn’t help in cases where the data is not in those format and the user wants to use scripting to generate/import meshes.

3 Likes

Hmm, I guess using mesh.loops.foreach_set("vertex_index", ...) and friends is a somewhat workable solution for now.

For reference, here’s an example script:

# Example of creating a polygonal mesh in Python from numpy arrays
# Note: this is Python 3.x code
#
# $ blender -P create_mesh.py
#
# See this link for more information on this part of the API:
# https://docs.blender.org/api/blender2.8/bpy.types.Mesh.html
#
# Paul Melis ([email protected]), SURFsara, 24-05-2019
import bpy
import numpy

# Note: we DELETE all objects in the scene and only then create the new mesh!
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# Vertices and edges (straightforward)

vertices = numpy.array([
    0, 0, 0,
    2, 0, 0,
    2, 2, 0.2,
    0, 2, 0.2,
    1, 3, 1,
    1, -1, -1,
    0, -2, -1,
    2, -2, -1
], dtype=numpy.float32)

# Setting edges is optional, as they get created automatically for
# any provided polygons. However, if you need edges that exist separately
# from polygons then use this array.
# XXX these edges only seem to show up after going in-and-out of edit mode.
edges = numpy.array([
    5, 6,
    6, 7,
    5, 7
], dtype=numpy.int32)

num_vertices = vertices.shape[0] // 3
num_edges = edges.shape[0] // 2

# Polygons are defined in loops. Here, we define one quad and two triangles

vertex_index = numpy.array([
    0, 1, 2, 3,
    4, 3, 2,
    0, 5, 1
], dtype=numpy.int32)

# For each polygon the start of its vertex indices in the vertex_index array
loop_start = numpy.array([
    0, 4, 7
], dtype=numpy.int32)

# Length of each polygon in number of vertices
loop_total = numpy.array([
    4, 3, 3
], dtype=numpy.int32)

num_vertex_indices = vertex_index.shape[0]
num_loops = loop_start.shape[0]

# Texture coordinates per vertex *per polygon loop*.
uv_coordinates = numpy.array([
    0, 0,
    1, 0,
    1, 1, 
    0, 1,
    
    0.5, 1,
    0, 0,
    1, 0,
    
    0, 1,
    0.5, 0,
    1, 1
], dtype=numpy.float32)

# Vertex color per vertex *per polygon loop*
vertex_colors = numpy.array([
    1, 0, 0,
    1, 0, 0,
    1, 0, 0,
    1, 0, 0,
    
    0, 1, 0,
    0, 1, 0,
    0, 1, 0,
    
    1, 0, 0,
    0, 1, 0, 
    0, 0, 1
], dtype=numpy.float32)

assert uv_coordinates.shape[0] == 2*vertex_index.shape[0]
assert vertex_colors.shape[0] == 3*vertex_index.shape[0]

# Create mesh object based on the arrays above

mesh = bpy.data.meshes.new(name='created mesh')

mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", vertices)

mesh.edges.add(num_edges)
mesh.edges.foreach_set("vertices", edges)

mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", vertex_index)

mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)

# Create UV coordinate layer and set values
uv_layer = mesh.uv_layers.new()
for i, uv in enumerate(uv_layer.data):
    uv.uv = uv_coordinates[2*i:2*i+2]
    
# Create vertex color layer and set values
vcol_lay = mesh.vertex_colors.new()
for i, col in enumerate(vcol_lay.data):
    col.color[0] = vertex_colors[3*i+0]
    col.color[1] = vertex_colors[3*i+1]
    col.color[2] = vertex_colors[3*i+2]
    col.color[3] = 1.0                     # Alpha?
    
# We're done setting up the mesh values, update mesh object and 
# let Blender do some checks on it
mesh.update()
mesh.validate()

# Create Object whose Object Data is our new mesh
obj = bpy.data.objects.new('created object', mesh)

# Add *Object* to the scene, not the mesh
scene = bpy.context.scene
scene.collection.objects.link(obj)

# Select the new object and make it active
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
5 Likes

Hey, nice info on the speed side. Did you verify it works as fast as the tesselated option?

Speed is fine it seems. Creating a mesh of 500k random triangles from pre-filled arrays takes around 0.6s on my system, 1m triangles takes 1.2s

Seems the UV and texcoords arrays can be set in similar fashion using foreach_set(<name>, ...), so for large meshes can be even faster

Hey, I ran in to this while trying to port an addon to 2.80. I’m don’t understand this thoroughly enough to understand how it translates to my scenario:

self.mesh.loop_triangles.add(len(self.faces))
self.mesh.loop_triangles.foreach_set("vertices_raw", unpack_face_list(self.faces))

Or is there even a direct translation for this type of thing?

Thanks

Hi Paul, what are the “attr” names for the UV foreach_set(). I think I’ve got my flattened arrays setup fine, just don’t know the syntax I’m looking for

Whoopsie!..the reason I couldn’t figure it out is I hadn’t toggled editmode so my uv_data was None. Spoiler alert, the attr is “uv” for anyone reading

Is it actually faster than from_pydata?

Yes, foreach_set is much faster than from_pydata, at the expense of having to specify the geometry data on a somewhat lower level (ie including loops)

Edit: the reason for the higher performance is that the data set with foreach_set already consists of the right set of arrays and can basically be used as is. While the Python data passed to from_pydata needs to be converted to the equivalent set of arrays which takes a bit of time. Also, the Python representation takes up much more memory.