# How to quickly assign an individual material to color thousands of cubes at the same time with python API in Blender 2.8?

Let us just say you have a 100x100 cubes 2d grid and each cube will be assigned its own individual material. How can you do this? My code below worked initially when I created all the cubes as individual objects but it took a very very long time to create that many cubes. I was then told I can speed up this process by just copying over the mesh from the first cube to help create the next cube. This speeded up the process but when I tried to run my coloring functions all the cubes now shares the same color material. I assume this is because the new cubes inherited it's material from the creation of the first cube. I think its has something to do with the last else statement below but I am not sure.

How can I keep the speed gain from copying cube mesh to create cubes without inheriting the initial material color from the first cube as well? I asked this question in the Blender official chat a whiles ago and was told to use MaterialSlot(bpy_struct) and I have no idea how it works.

Full relevant code

class Create(Animation):

def __init__(self, collection_name, H, W, y_axis):

self.collection_name = collection_name
self.H = H
self.W = W
self.master_collection = bpy.context.scene.collection
self.myCol = bpy.data.collections.new(self.collection_name) # Automatically create the correct layer

def create_block(self, x_axis, y_axis, z_axis):
"""
1) Takes the Height and Width and create a 3D wall in Blender
2) Center it in the middle, assign it to the given collection
3) Translate corresponding pixel values to the correct mat color

Args:
H -- The height(int)
W -- The width(int)
collection -- The collection this block belongs too(str)
"""

if self.collection_name == "input_image": # try mycol flag

self.x = x_axis
self.y = y_axis
self.z = z_axis

elif self.collection_name == "filters":

self.x = x_axis + 69
self.y = y_axis
self.z = z_axis + 69

pixel_idx = 0 # corresponding pixel value index from the tensor

bpy.ops.mesh.primitive_cube_add(size=2, view_align=False, enter_editmode=False, location=(self.x, self.y, self.z)) #create initial cube

cube = bpy.context.scene.objects[0] # remove the duplicate cube from initbpy.ops

for height in range(self.H): # z-axis

for width in range(self.W): # # x-axis

new_cube = bpy.data.objects.new(self.collection_name + "cube", cube.data) #cube.data = mesh struct

new_cube.location = (self.x, self.y, self.z)

self.x += 3

if self.collection_name == "filters":
self.x = x_axis + 69
elif self.collection_name == "input_image":
self.x = x_axis

self.z += 3

bpy.ops.object.delete()

def fill_pixel_value(self, tensor_img):

self.image_flatten = tensor_img

self.obs = bpy.context.scene.objects

for i, o in enumerate(self.obs):

name = o.name #grab the cube's name

mat = bpy.data.materials.get("Material")

if mat is None:

mat = bpy.data.materials.new( name + "_Material" )

#Change the mat color to the corresponding image_flatten tensor values
# This used to work but now the first cube I made has the dominate material.

mat.diffuse_color = ( self.image_flatten[i], self.image_flatten[i], self.image_flatten[i], 1 ) #RGBA

# Assign it to object
if o.data.materials:

# assign to 1st material slot

o.data.materials[0] = mat

else:
# no slots

o.data.materials.append(mat)


You are trying to make same linked mesh have different materials on a different object. Using object linked materials can use only the one mesh, and N x N materials and objects.

import bpy
context = bpy.context
scene = context.scene
count = 32

ob = context.object
dx, dy = ob.dimensions.xy

mat = bpy.data.materials.new("Mat")
ob.material_slots[0].material = mat
for i in range(1, count * count):
o = ob.copy()
o.material_slots[0].material = mat.copy()
# set some mat props
o.location = (dx * (i % count), dy * (i // count), 0)


One material could be used here instead by mapping texture coordinates via the object.

Method 2: 1 material, N x N objects and meshes.

Grid, UVMap image texture, edge split, solidify, separate by loose parts

Using the same methods as outlined to you here https://blender.stackexchange.com/a/131765/15543

Material is an image texture mapped to the UV. This way we only have one material. The UV can be mapped to 1 pixel squares.

Result of script. Image is 128x128, grid was split 32 x 32

So each block has a top with 4x4 pixels.

import bpy
context = bpy.context

size=10,
x_subdivisions=33,
y_subdivisions=33,
calc_uvs=True)

ob = context.object
me = ob.data
me.materials.append(bpy.data.materials["Material"])
sfy = ob.modifiers.new(name="Solidify", type='SOLIDIFY')
sfy.thickness = 1 / 3
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.edge_split()
bpy.ops.mesh.separate(type='LOOSE')
bpy.ops.object.mode_set()

bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')


Creating 128 x 128 objects and as many materials too is going to be slooooooooooooooooow.

Can be optimized for speed somewhat. Not sure it's worth it for zero feedback.

Method 3: 1 object, 1 mesh, N x N materials.

Single cube, applied array modifiers, materials assigned to each part

Similarly to this answer https://blender.stackexchange.com/a/61254/15543 can create an array mesh very quickly.

After the array modifiers are applied the faces are still ordered such that each 6 belongs to a cube. A new material is added for each and assigned. The row and column of the parts can be obtained using integer div and modulus and hence can be mapped to pixel colour.

For example sake I've only made a 32 x 32 array. Note the offset is at 1.5, change to 1.0 to have touching cubes.

To make each a single object, separate by loose parts as above. This is the rate-determining-step. As one mesh, individual parts can be animated via hooks, armature, shapekeys.

import bpy
from bpy import context
ob = context.active_object
me = ob.data
count = 32
arrays = {"x": (1.5, 0, 0),
"y": (0, 1.5, 0)}

for axis, displace in arrays.items():
mod = ob.modifiers.new(axis, 'ARRAY')
mod.count = count
mod.relative_offset_displace = displace
bpy.ops.object.modifier_apply(modifier=mod.name)

def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i+n]

material_index = 0
for cube in chunks(me.polygons, 6):
mat = bpy.data.materials.new("Mat%d" % material_index)

mat.diffuse_color[0] = material_index / 100
me.materials.append(mat)
for f in cube:

f.material_index = material_index
material_index += 1


Alternatively could use a UV map here also, for 1 object, 1 mesh (with NxN parts) and 1 material.

• Thank you for all these wonderful pieces of information. I did manage to figure out why its not working and it's exactly like you said. I was trying to apply a material to an object's mesh which was the same cube mesh, instead, I should of created to material under the object itself. Thanks again. – Inkplay_ Mar 22 at 18:49
• Follow up question, I am currently trying to integrate the first method you showed me. I want to loop through all my OBJECTS after I create them. I first want to create an empty material slot but when I tried to use bpy.ops.object.material_slot_add() I get RuntimeError: Operator bpy.ops.object.material_slot_add.poll() failed, context is incorrect? I tried to google a lower level version of it but no dice. – Inkplay_ Mar 23 at 2:16
• i) Set object to active first, ii) override context, or iii) add a 'DATA' slot by appending a material to mesh, then change the slot link to 'OBJECT' and remove material from mesh. Code above copies object with one object linked material slot so you only have to do it once. – batFINGER Mar 23 at 3:18