1
$\begingroup$

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))
$\endgroup$
1
  • $\begingroup$ 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. $\endgroup$
    – John Eason
    Nov 20 at 22:14

2 Answers 2

1
$\begingroup$

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.

$\endgroup$
12
  • $\begingroup$ FYI, testing it too, X Y solution (the first one, without update functions) seems to be a bit faster than yours. $\endgroup$
    – lemon
    Nov 21 at 12:24
  • $\begingroup$ @lemon Did you undo after running each solution? Anyway, the fastest solution for me was the one linked to. I updated my answer. $\endgroup$
    – Mr A
    Nov 21 at 12:31
  • $\begingroup$ I've created a function similar to X Y one (so with a loop on coords), but using your method. this way: i.stack.imgur.com/hKhES.png $\endgroup$
    – lemon
    Nov 21 at 12:33
  • $\begingroup$ and yes, undo after each. $\endgroup$
    – lemon
    Nov 21 at 12:33
  • $\begingroup$ @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. $\endgroup$
    – Mr A
    Nov 21 at 12:37
0
$\begingroup$

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)
$\endgroup$
9
  • $\begingroup$ 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 $\endgroup$
    – lemon
    Nov 21 at 7:31
  • $\begingroup$ Do You mean the last 2 line: bpy.context.view_layer.update()? or update_disable()? $\endgroup$
    – X Y
    Nov 21 at 7:34
  • $\begingroup$ update_disable and _enable, yes $\endgroup$
    – lemon
    Nov 21 at 7:35
  • $\begingroup$ 0.11s with the three update functions and 0.06s without them 3, here. $\endgroup$
    – lemon
    Nov 21 at 7:37
  • $\begingroup$ Not required if no other ops are running, if there are lot of ops running, this method lot more faster, see the new update. $\endgroup$
    – X Y
    Nov 21 at 7:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .