Creating mesh with python - performance issue

I’ve built a script that I need in my tool chain to load in meshes from a text file. It works nicely but it is significantly slower than loading in a blend file or using other default importers. I’ve tried to figure out what is the bottle neck in my approach without any luck. Except I know it’s not reading the text file, it’s somewhere in the mesh creation (snippet below).
Any hints how to improve the performance are greatly welcome.

def read_mesh(  collection,
                item_name, 
                vertices, 
                faces, 
                text_coords, 
                material):
    import bmesh, bpy
    empty_object = bpy.data.objects.new( 'ParentEmpty', None )
    collection.objects.link(empty_object)
    mesh_data = bpy.data.meshes.new(item_name)
    mesh_data.from_pydata(vertices, [], faces)
    mesh_data.update(calc_edges=True)
    scene_object = bpy.data.objects.new(item_name, mesh_data)
    scene_object.parent = empty_object
    collection.objects.link(scene_object)
    scene_object.data.materials.append(material)
    for poly in scene_object.data.polygons:
        poly.use_smooth = True
    bm = bmesh.new()
    bm.from_mesh(mesh_data)
    uv_layer = bm.loops.layers.uv.verify()
    for face in bm.faces:
        for loop in face.loops:
            luv = loop[uv_layer]
            luv.uv = tuple(text_coords)
    bm.to_mesh(mesh_data)       
    return empty_object

To profile different parts of a function use perf_counter from the time module.

from time import perf_counter


def function():
    t = perf_counter()

    # Code to measure between.

    print(round((perf_counter() - t) * 1000, 3), "ms")

Then move the t and print statement around to check different parts of the function.

Hard to tell what exactly is slowing your script down without an example, but as a general rule you should avoid doing loops. Blender can generate meshes incredibly fast with foreach methods since they operate directly on C arrays, or linked lists, which is still faster than through rna/python.

    for poly in scene_object.data.polygons:
        poly.use_smooth = True

can be done like:

    scene_object.data.polygons.foreach_set("use_smooth", [1] * len(scene_object.data.polygons))

Not understanding how you’re doing the uv loop. Looking at the code, seems you’re assigning the same text_coords to every uv coord in mesh loop. Not sure why you’d want that, but in either case better to ditch the bmesh and use foreach method on mesh_data to assign uvs.

1 Like

Thanks a lot!
That foreach tip is great. I’ll give it a go.
I was using the bmesh because I was struggling to assign uv coords otherwise. Need to study a bit more. Any further hint how to do that specifically. That bm.loops.layers.uv.verify() does some magic I’m not quite sure I understand how to do otherwise.
And I can see where I lost you with the uv loop. I accidentally deleted the indexing in the luv.uv = tuple(text_coords) while simplifying the snippet for posting. text_coords is a list of lists and it should be something like this luv.uv = tuple(text_coords[loop.vert.index])

I tried replacing the loop with the foreach but it doesn’t seem to work.
Results in this:

Error: Array length mismatch (got -1, expected more)
TypeError: object of type 'bool' has no len()
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  ...
  File "C:\temp\test.py"", line 106, in my_import
    scene_obj.data.polygons.foreach_set("use_smooth", True)
TypeError: couldn't access the py sequence```

Edit, my bad. Didn’t know how to use that foreach_set properly. Obviously had to provide a list Trues.
.foreach_set("use_smooth",[True]*len_of_poly_list)
Maybe there’s more sophisticated way to give that “true” argument, but this already did a noticeable improvement. Not a leap but still faster. thanks

That’s completely my bad :stuck_out_tongue:

It should have been:

scene_object.data.polygons.foreach_set("use_smooth", [1] * len(scene_object.data.polygons))

The verify method essentially creates a uv layer if it doesn’t exist and returns a layer access object, essentially a dictionary key. Bmesh lightly wraps the underlying C data in nothing more than skeleton bindings (that’s why bmesh.from_edit_mesh() is ridiculously fast, it’s just wrapping the current edit mesh struct), and a custom layer access object is a part of that less-overhead paradigm.

Uv coordinates should be sequenced the same way as mesh loops.
First you need to create a uv map if it doesn’t exist.

uv = obj.data.uv_layers.active
if uv is None:
    uv = obj.data.uv_layers.new(name="UVMap")

I’m assuming text_coords is a 2d sequence. In which case you’re better off putting it into a numpy array and let it flatten the thing before using foreach.

import numpy as np

arr = np.array(text_coords, "f")
uv.data.foreach_set("uv", arr.ravel())
1 Like