5

Is it possible to create a Blender mesh directly from efficient numpy arrays without needing to go through (the slow and space wasting) POD python data types? Here is a script that illustrate what I mean. I need to call .tolist() on my numpy data to make the script work. Is there another more efficient API?

# Create a mesh from numpy arrays. Can you do this without tolist()?
import bpy
import numpy as np

verts = np.array([
  (1, 1, 1),
  (-1, 1, -1),
  (-1, -1, 1),
  (1, -1, -1),
], dtype=np.float32)

faces = np.array([[0, 1, 2], [0, 2, 3], [0, 3, 1], [2, 1, 3]],
                 dtype=int)

mesh = bpy.data.meshes.new('TetraMesh')   
obj = bpy.data.objects.new('Tetra', mesh) 

# Must call tolist() to pass to from_pydata()!
mesh.from_pydata(verts.tolist(),[],faces.tolist())   
mesh.update(calc_edges=True)              # Update mesh with new data
bpy.context.collection.objects.link(obj)  # Link to scene

2
  • 1
    Note that to_list() is no longer necessary as of blender 4.0 (maybe earlier). Commented Oct 11, 2024 at 23:05
  • Oh, my. But passing numpy arrays actually seems to hurt performance of from_pydata() significantly: that is, passing to_list() on both vertices and faces seems to speed up from_pydata() by a factor of more than 2.5 (in blender 4.0.2, and also built-from-source). (I observed this using the above code, modified to create a triangle fan of 2 million vertices in the shape of a circle.) Wonder why that is. Commented Oct 16, 2024 at 1:07

2 Answers 2

5

This one works for me using foreach_set. You don't have to convert the numpy array to a list. I commented every line, it should be clear how it works.

import bpy
import numpy

# the numpy array must be in this form
vertices = numpy.array([
                        1, 1, 1,       # vertex 0
                        -1, 1, -1,     # vertex 1
                        -1, -1, 1,     # vertex 2
                        1, -1, -1      # vertex 3
                        ], dtype=numpy.float32)

# vertices for each polygon
vertex_index = numpy.array([
                            0, 1, 2,   #  first polygon starting at 0
                            0, 2, 3,   # second polygon starting at 3
                            0, 3, 1,   #  third polygon starting at 6
                            2, 1, 3    #  forth polygon starting at 9
                            ], dtype=numpy.int32)


# every polygon start at a specific index in vertex_index array
loop_start = numpy.array([
                          0, # polygon start at 0 --> 0, 1, 2
                          3, # polygon start at 3 --> 0, 2, 3
                          6, # polygon start at 6 --> 0, 3, 1
                          9  # polygon start at 9 --> 2, 1, 3
                          ], dtype=numpy.int32)
                          
# lenght of the loop
num_loops = loop_start.shape[0]


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


# Create mesh object based on the arrays above
mesh = bpy.data.meshes.new(name='created mesh')

# Number of vertices in vertices array (12 // 3)
num_vertices = vertices.shape[0] // 3

# add the amount of vertices, in this case 4.
mesh.vertices.add(num_vertices)

# use the vertices numpy array
mesh.vertices.foreach_set("co", vertices)

# total indexes in vertex_index
num_vertex_indices = vertex_index.shape[0]

# add the amount of the vertex_index array, in this case 12
mesh.loops.add(num_vertex_indices)

# set the vertx_index
mesh.loops.foreach_set("vertex_index", vertex_index)

# add the length of loop_start array
mesh.polygons.add(num_loops)

# generate the polygons
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)

# validate your mesh
mesh.update()
mesh.validate()

# create the object with the mesh just created
obj = bpy.data.objects.new('created object', mesh)

# add the Oject to the scene
scene = bpy.context.scene
scene.collection.objects.link(obj)

If you want to know more there is another more consistent example here

Sign up to request clarification or add additional context in comments.

Comments

2

Blender recently (2021/08/10) fixed this in master branch https://projects.blender.org/blender/blender/commit/7c2c66cdb8db8f38edc57a890c0ec7ea28b9fad3 , so you will no longer have to call to_list().

A current workaround is to view the array with a class with overridden __bool__ method; that way when Blender checks the bool value of the array, it actually does what Blender devs intended to do (check if length of array is > 0).

class ndarray_pydata(np.ndarray):
    def __bool__(self) -> bool:
        return len(self) > 0

edges = edges_np.view(ndarray_pydata)
faces = faces_np.view(ndarray_pydata)
mesh.from_pydata(vertices, edges, faces)

full working example: https://gist.github.com/iyadahmed/7c7c0fae03c40bd87e75dc7059e35377

3 Comments

I fixed the first link. The second one is still broken.
Confirmed: tolist() is no longer necessary as in blender 4.0 (maybe earlier)
But numpy arrays seem to make from_pydata() significantly slower, for some reason; see my comment which I added to the question, above.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.