1

In the answer to this question it's said that the high level ops functions have low performance since they do a scene update: Python performance with Blender operators

I'm looking for a low level equivalent to this command, which creates a cube in the scene, with a given size and position:

bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, align="WORLD", location=(0,0,0), scale=(1, 1, 1))
BroDude
  • 15
  • 2
  • That answer was written many years ago (2014) with a much earlier version of Blender. There have been a lot of changes since then to speed up and re-write the underlying code in Blender so it might not be an issue nowadays. – John Eason Nov 20 '23 at 22:14

2 Answers2

1

This is one way to do it without having to depend on bpy.ops.

import bpy

name = "Cube" size = 2 location = (0, 0, 0) color = (0, 0, 1, 1)

Generate the vertices and faces, edges are inferred from the polygons

s = size / 2 vertices = [(s, s, s), (s, s, -s), (s, -s, s), (s, -s, -s), (-s, s, s), (-s, s, -s), (-s, -s, s), (-s, -s, -s)] faces = [(0, 2, 3, 1), (0, 1, 5, 4), (2, 0, 4, 6), (1, 3, 7, 5), (3, 2, 6, 7), (4, 5, 7, 6)]

Create a new mesh and set generated data

mesh = bpy.data.meshes.new(name) mesh.from_pydata(vertices, [], faces)

Create a new material

material = bpy.data.materials.new(f"{name}Mat")

Mark the material to use nodes and set the base color of the Principled shader and the display color

material.use_nodes = True material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = color material.diffuse_color = color

Assign the material to the mesh

mesh.materials.append(material)

Create a new object to hold the mesh and set its location

object = bpy.data.objects.new(name, mesh) object.location = location

Link the object to the active collection to be able to see it in the scene

bpy.context.collection.objects.link(object)

Running this 1000 times using a for loop takes about 0.0702 seconds on my PC. Running the first solution in X Y's answer takes about 0.0941 seconds. Running the second solution in X Y's answer takes about 0.2027 seconds. Running the selected answer here for 1000 cubes takes about 0.0324 seconds. Tests were run in Blender 4.0.


Edit 1: If the mesh can be shared between the objects, the proposed answer can take around 0.0132 seconds to generate 1000 objects that use the same mesh data.


Edit 2: I added a few extra steps to create and assign a material to the generated mesh.

Mr A
  • 1,860
  • 2
  • 11
  • FYI, testing it too, X Y solution (the first one, without update functions) seems to be a bit faster than yours. – lemon Nov 21 '23 at 12:24
  • @lemon Did you undo after running each solution? Anyway, the fastest solution for me was the one linked to. I updated my answer. – Mr A Nov 21 '23 at 12:31
  • I've created a function similar to X Y one (so with a loop on coords), but using your method. this way: https://i.stack.imgur.com/hKhES.png – lemon Nov 21 '23 at 12:33
  • and yes, undo after each. – lemon Nov 21 '23 at 12:33
  • @lemon Do you mind passing me your results after you're done? I will update my post to include them. I don't know why our results do not agree. – Mr A Nov 21 '23 at 12:37
  • here is my file: https://blend-exchange.com/b/Pnd018Lm/ – lemon Nov 21 '23 at 12:40
  • @lemon It seems that you have a modified version of the first solution. Yes, that one runs faster on my PC too. It takes about 0.0456 seconds. – Mr A Nov 21 '23 at 12:54
  • Yes, modified, because the functions named with "update" are not usefull here, just that. – lemon Nov 21 '23 at 13:09
  • @MrA I like your solution, I'm soon going to mark it as the answer. I'd also like to add a simple color to the recently created cubes. How can I do this? I usually do this by using the active object, similarly to this solution: https://blender.stackexchange.com/questions/201874/how-to-add-a-color-to-a-generated-cube-within-a-python-script (lemon's answer)

    I want to have a separate material for each cube.

    – BroDude Nov 21 '23 at 16:01
  • @BroDude Since we're creating the mesh, we can easily create a material and assign it directly. I gave the material a default blue color. Please note that I prepared the script to handle one cube only as the question was for one cube. If you want there to be a grid of cubes with different colors, sizes, and locations, we'll have to indent the code under one or more for loops and decide how to modify the code to satisfy our needs. – Mr A Nov 21 '23 at 17:02
  • @MrA Thanks for relpying! I want to create a bunch of cubes with different colors. My current solution is too slow. I've created a new question regarding this: https://blender.stackexchange.com/questions/305336/coloring-lots-of-cubes – BroDude Nov 21 '23 at 17:05
  • You're welcome! I have to go now, but I've saved it to look at it as soon as I am available. – Mr A Nov 21 '23 at 17:26
0

Fast way to create 1000 same size cubes with different position

# tested in Blender 3.6
import bpy, bmesh, time

_temp = [None, None]

def N(context): pass

def update_disable(): from bpy.ops import _BPyOpsSubModOp _temp[0] = _BPyOpsSubModOp _temp[1] = _BPyOpsSubModOp._view_layer_update _BPyOpsSubModOp._view_layer_update = N

def update_enable(): _temp[0]._view_layer_update = _temp[1]

def add_cubes(coords, size=1): bm = bmesh.new() bmesh.ops.create_cube(bm, size=size)

mesh = bpy.data.meshes.new('Cube')
bm.to_mesh(mesh)
mesh.update()
bm.free()

objects = bpy.data.objects
objs = bpy.context.collection.objects

for co in coords:
    ob = objects.new("Cube", mesh.copy())
    ob.location = co
    objs.link(ob)

bpy.data.meshes.remove(mesh)


t = time.time() update_disable() # Not required if no other ops are running add_cubes([[r, r, r] for r in range (1000)])

run other ops here..

update_enable() bpy.context.view_layer.update() print('run time: ', time.time() - t)

Other way

import bpy, time

t = time.time()

bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, align="WORLD", location=(0,0,0), scale=(1, 1, 1)) cube = bpy.context.object

md = cube.modifiers.new('', 'ARRAY') md.count = 1000 bpy.ops.object.modifier_apply(modifier=md.name)

bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.separate(type="LOOSE") bpy.ops.object.mode_set(mode='OBJECT')

print('run time: ', time.time() - t)

X Y
  • 5,234
  • 1
  • 6
  • 20
  • I've tested it by curiosity. The update functions does not seem to change anything or more, it seems to be faster without them. Could you explain a bit what it is intend to do? Thanks – lemon Nov 21 '23 at 07:31
  • Do You mean the last 2 line: bpy.context.view_layer.update()? or update_disable()? – X Y Nov 21 '23 at 07:34
  • update_disable and _enable, yes – lemon Nov 21 '23 at 07:35
  • 0.11s with the three update functions and 0.06s without them 3, here. – lemon Nov 21 '23 at 07:37
  • Not required if no other ops are running, if there are lot of ops running, this method lot more faster, see the new update. – X Y Nov 21 '23 at 07:47
  • ok, that's for ops. Thanks – lemon Nov 21 '23 at 07:48
  • I've found a solution in the answer to this question: https://stackoverflow.com/questions/69835134/in-python-how-do-i-generate-a-large-amount-of-cube-meshes-fast

    If I understand it correctly, it creates one cube with ops, then copies the object & mesh data with low level functions, then does a single update at the end.

    – BroDude Nov 21 '23 at 11:48
  • Isn't disabling updating this way a bit of a hack? Can this solution break in future Blender versions? – BroDude Nov 21 '23 at 14:51
  • No one knows because there are no developers here. Any content may change in future versions. – X Y Nov 21 '23 at 17:28