Dov Grobgeld
Dov Grobgeld

Reputation: 4983

Creating a blender mesh directly from numpy data?

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

Upvotes: 5

Views: 4958

Answers (2)

Eyad Ahmed
Eyad Ahmed

Reputation: 112

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

Upvotes: 2

imnewhere
imnewhere

Reputation: 106

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

Upvotes: 5

Related Questions