Moo-Ack! Productions
  • About
    • About Site
    • TheDuckCow in the Media
    • Contact and Support
  • Animation
    • Minecraft
    • Stopmotion
    • Originals
  • Tutorials
    • Tutorials

      Minute Minecraft Animation Tutorials

      September 14, 2020

      Tutorials

      Easy user error reporting for Blender Addons

      March 28, 2020

      Tutorials

      Update Addons with both Blender 2.8 and 2.7…

      January 24, 2019

      Tutorials

      Blender 3D Tutorials

      May 5, 2018

      Tutorials

      Creating Overrides with Linked Libraries

      March 17, 2018

  • Contests
  • Development
    • Blender 3D
    • MCprep Addon
    • Mobile Apps
    • OpenCV
  • Gallery
    • MCStudio Renders
    • Personalized 3D Cards
    • Fresh off the Farm
    • Hyperlapses & Timelapses
  • Free Download

Blender Operator Translations

by Patrick W. Crawford December 23, 2016

Improve your Blender python coding by translating blender operator calls into native API calls. When coding in blender, there are many ways to accomplish the same task. However, some methods are more efficient and faster than others. This is a reference page for “de-op’ing” your code to make it faster and more efficient

Want to learn how to use Python in Blender?

Blender Operators

In general, when a user presses a button in blender, it invokes an operator. This is a special blender python class which runs code from the interface. From the blender info window you can see the order of operators used, allowing you to make macro-like scripts as you could in microsoft office or other programs. While this can be used to quickly write scripts, it can leads to troubles.

The problem with operators

Blender operators are meant to be used by someone pressing a button, thus running some code and finishing with an execution status. Ideally, they are not used in the middle of other code for a variety of reasons. A few of these concerns include:

  • They cannot return values that are usable beyond how they completed (e.g. ‘FINISHED’ or “CANCELLED’)
    • For example, bpy.ops.duplicate() will not return the newly created object which you might want. Instead, you have to obtain it by bpy.context.scene.active_object
  • Running an operator can modify current context in a non-desired way
    • If you wanted to duplicate an object in the middle of a script, using the operator will clear the user’s current selection and set the new object as selected and active. If you did not want your code to do this, you would then have to start by getting the initial selection and then re-applying this selection at the end of the script.
  • They force you to change state
    • Inherently, operators are context-aware and you cannot run EDIT_MESH operators in OBJECT mode by design. If you want your own code to edit mesh values from object mode, you would then have to sprinkle  bpy.ops.object.mode_set() operator calls into your scripts to change between mode=’OBJECT’ and mode=’EDIT’ based on which operators you are trying to run. You even end up “sandwiching” mode_set() to enter a specific mode for one operator call and then immediately switch back. In essence, you then run three operator calls when you wanted to only run one.
  • They run slower than native blender python API calls*
    • Directly true due to the extra interface related code, such as  changing selections in some cases and other side operations that you as a developer aren’t necessarily looking to do. This is unnecessary processing that slows down your code.
    • Indirectly true for even more reasons, including but not limited to the bullet points above. Having to counteract unwanted selection changes and mode sets all add processing time.
    • * See the notes at the bottom of the page, as sometimes operator code can run faster than low level API calls.

Instead, you could use direct python API calls to do what you want – and only what you want. Use this page as a reference for translating operator code into lower-level python API calls. The guide will expand over time, and is sorted roughly by most common use.

Operator translations

Select All/None - bpy.ops.object.select_all

While you can use bpy.ops.object.select_all(action) with action = “TOGGLE”, “SELECT”, or “DESELECT”,  to perform the same action and ensure even hidden objects and objects on other layers have their selection state updated, you could instead use

# select none
for ob in context.scene.objects: ob.select=False

# select all if visible
for ob in context.scene.objects:
    if ob.hide == False:  ob.select=False

Speed note: The API calls appear to run barely faster, but overall execution time is fast and not different enough between operator and python calls.

Move or translate - bpy.ops.transform.translate

To move an object, definitely make use you use object.location instead of the operator.

ob.location = (1,2,3) # assign new location
ob.location[2] += 1 # move the object up 1 unit in the z-direction
reference_location = ob.location.copy() # Be sure to copy location if you want to save the values

Delete object - bpy.ops.object.delete

The below will let you delete an object without knowing or modify its selection and layer, and will not affect any other objects in the scene.

ob = bpy.data.objects["some-object"] # object to delete
bpy.context.scene.objects.unlink(ob) # actively remove it from the current scene, careful if in other scenes
ob.user_clear() # clears all users, e.g. from groups scenes
bpy.data.objects.remove(ob)

# alternatively, if you just want to remove it from the active scene
context.scene.objects.unlink(ob)

If you used the operator instead, you would need to first deselect all objects and make the one object desired for deleting active, then most likely go back and re-apply the initial selection for consistency.

Speed note: See the notes below, in short the operators calls can be significantly slower than the equivalent API call.

Using simple cubes, operator versus API call (time is in seconds)

  • Deleting 3 cubes: Operator (0.00036) is slower than API call (0.00008) by a factor of 4.40
  • Deleting 51 cubes: Operator (0.00538) is slower than API call (0.00144) by a factor of 3.71
  • Deleting 343 cubes: Operator (0.0920) is slower than API call (0.0207) by a factor of 4.45
  • Deleting 2402 cubes: Operator (14.519) is slower than API call (2.687) by a factor of 5.40

Running the same tests against a more data-heavy Suzanne model (subsurf level 1 applied, UV map and vertex group, 2,012 verts/1,968 poly, no linked data),

  • Deleting 3 objects: Operator (0.00151) is slower than API call (0.00129) by a factor of 1.17
  • Deleting 51 objects: Operator (0.1434) is significantly slower than API call (0.0016) by a factor of 91.4
  • Deleting 343 objects: Operator (7.014) is significantly slower than API call (0.035) by a factor of 199
  • Deleting 2402 objects: Operator (332.28) is significantly slower than API call (3.03) by a factor of 109

Duplicating objects - bpy.ops.object.duplicate_move & bpy.ops.object.duplicate

Duplicating using the above operator changes the active selection, active object, and does not conveniently return to you the newly created object (though you could grab it via context.object immediately after the operator runs). It can also be tricky as this operator will also duplicate even hidden objects, so running  bpy.ops.object.select_all(action=’DESELECT’) prior to duplication may not accomplish what you are hoping. Instead, use the following:

ob = bpy.data.objects["some-object"] # object to duplicate
# first, create the datablock
new_ob = bpy.data.objects.new( ob.name, ob.data.copy())
context.scene.objects.link(new_ob) # adds the object to the active scene
new_ob.location = (0,0,0) # set new location, or leave it as-is to keep the same location as the original object. Note, the original selection is unchanged even if the original object wasn't selected or active.

If you wanted to make a linked duplicate, note the minor change of the second line

ob = bpy.data.objects["some-object"] # object to duplicate
new_ob = bpy.data.objects.new( ob.name, ob.data) # this will make a linked copy of ob.data
context.scene.objects.link(new_ob) # adds the object to the active scene
new_ob.location = (0,0,0) # relocate

Speed note: It is my observation that the operator can run faster when duplicating a larger quantity of objects, while duplicating a small number of objects at once is quickest via python API calls.

Simple cube duplication test (times are in seconds):

  • Duplicating 3 cubes: Operator (0.00059) is slower than API call (0.00026) by a factor of 2.3
  • Duplicating 51 cubes: Operator (0.00484) is about equal but slower than API call (0.00462) by a factor of 1.05
  • Duplicating 343 cubes: Operator (0.0732) is faster than API call (0.0913) by a factor of 1.24
  • Duplicating 2402 cubes: Operator (5.092) is faster than API call (8.307) by a factor of 1.63

Results when the same test is run on a more complex object (Suzanne model subsurf level 1 applied, with UV map and vertex group, 2,012 verts/1,968 poly):

  • Duplicating 3 objects: Operator (0.00182) is slower than API call (0.00129) by a factor of 1.41
  • Duplicating 51 objects: Operator (0.0181) is slower than API call (0.0158) by a factor of 1.15
  • Duplicating 343 objects: Operator (0.198) is about equal but slower than API call (0.186) by a factor of 1.07
  • Duplicating 2402 objects: Operator (7.08) is faster than API call (10.98) by a factor of 1.55

Additional notes

The copying method shown above does not copy everything about the source object. For examples, modifiers and vertex groups (and some other attributes) will not be carried over. You can, however, add them back selectively this way if you choose:

# ob is the original object, new_ob is the new using the above code 
for mod_src in ob.modifiers:
    # create one modifier of the same type on the new object
    dest = new_ob.modifiers.new(mod_src.name, mod_src.type)
    
    # Copy all attributes (settings, drop downs, etc) from the source modifier
    # skipping anything that is read only since those cannot be changed anyways.
    properties = [p.identifier for p in mod_src.bl_rna.properties
                  if not p.is_readonly]
    for prop in properties:
        setattr(dest, prop, getattr(mod_src, prop))

Group objects - bpy.ops.group.create

Replace bpy.ops.group.create(name="GroupName") with the code below to more directly create or modify groups with selected (or any) objects.

group = bpy.data.groups.new("GroupName") # note we get a direct ref to new group
for ob in context.selected_objects: # or whichever list of objects desired
    group.objects.link(ob) # now linked to the group, green outline in 3d view

One notable benefit of the above is we get the direct group reference. Using the operator, you would instead need to compare the list of groups before and after creating the new one to be sure you have the right reference. While you could supply name=”GroupName” into the operator call, blender could end up creating a group named “GroupName.001” instead if the first group already existed. Thus, it would be a mistake to try and directly reference the newly made group by name unless you checked for the name explicitly ahead of time that it was safe and unused.

Remove All Group Objects - bpy.ops.group.objects_remove_all

This can be replaced by a simple for-loop as below. One clear benefit of using python API calls in this case is that it is not always clear what the active group may be in the same way the active object may be, so you can be more explicit on which group it operates.

group = bpy.data.groups["group-name"]

# for loop is equivalent to bpy.ops.group.objects_remove_all()
for ob in group.objects:
    group.objects.unlink(ob) # remove from group

If you’re looking to completely remove a group, blender does not provide a single operator to completely remove a group from a blender file – so the best way is via python.

group = bpy.data.groups["group-name"]

# We still want to remove all objects first
for ob in group.objects:
    group.objects.unlink(ob) # remove from group

# then, remove the group itself - no equivalent operator
# group.user_clear() # in some cases, this may be needed
bpy.data.groups.remove(group)

Add Group Instance - bpy.ops.object.group_instance_add

Adding a group instance from Shift-A > Group is equivalent to the steps below. It is more stable and can directly give you the name of the resulting added object.

def addGroupInstance(group, location):
    scene = bpy.context.scene
    ob = bpy.data.objects.new(group.name, None)
    ob.dupli_type = 'GROUP'
    ob.dupli_group = group
    # if only group name known and not direct ID:
    # ob.dupli_group = bpy.data.groups.get(group.name)
    ob.location = location # or for example, ob.location = scene.cursor_location
    scene.objects.link(ob) # this actually adds to the active scene
    # ob.select = True # if desirable, see above if you want to deselect other items or not
    return ob # the reference empty created

# use the function
group = bpy.data.groups["group-name"]
groupInstance = addGroupInstance(group.name,(0,0,0))

New image - bpy.ops.image.new

When creating a new image internally in blender using the operator, you don’t immediately get the reference to the image just created. To find it, you would have to compare the list of images before and after the operator. Rather than that, you can use the constructor that comes with data.images:

# Operator method to create a gray, half-transparent 512x512 image
images_before = list(bpy.data.images) # copy the list ahead to get
bpy.ops.image.new( name="newImage", height=512, width=512, color=(0.5,0.5,0.5,0.5), alpha=True, generated_type="BLANK", float=False )
images_after = list(bpy.data.images)
differences = set(images_after).difference(images_before) # do difference of image list before/after
new_image = list(differences)[0] # extract the one new image created

# Low level api method to create a gray, half-transparent 512x512 image
new_img = bpy.data.images.new( name="newImage", height=512, width=512, alpha=True )
# we already have the reference, but need to set initial color.
# For that, the following will work.
# Note that new_img.pixels is a long, 1D list in the format of [# channels e.g. rgba]*[# total pixel count e.g. 512*512]
new_img.pixels = [0.5,0.5,0.5,0.5]*(len(new_img.pixels)/4)

Now for the timing comparison. These tests were each run 100x with each set done in a refreshed blender instance, variances within sets were all statistically non-significant. Timings are in seconds

NxN image Operator Black api Black Operator Color api Color
256px 0.000390 0.000243 0.000367 0.006842
2048px 0.007365 0.007104 0.007478 0.557998
8192px 0.171569 0.177132 0.174332 10.258755

Generally speaking, creating a single image is quite quick and likely isn’t the critical path for any operator unless it’s trying to generate a large number of images. Regardless, it appears the low level api and operator both have very similar timings for small and large images alike. The operator has a leg up when it comes to setting initial colors; the lower level api takes quite a hit especially at higher resolutions for that second line operation generating all those color values.

New VSE image sequence - bpy.ops.sequencer.image_strip_add

It is quite fast and easy to create a new image sequence in the VSE, which contains a series of sequential image files (frame_001.png, frame_002.png, etc). Use of this operator will fail if you don’t have the right context available, such as you might in a command line environment. Instead, use lower-level API calls to add the sequence – and the tricky part, you need to individually add each element of the image sequence after the fact.

# Get your sequence of image files, note here the list of files here is of a dictionary
files = [ {"name":f} for f in os.path.listdir(FILEPATH) if f.endswith(".png") ]

# This won't work from a different context e.g. on command line, but would work from within a script or other operator with valid context
# This is the operator to remove the use of
bpy.ops.sequencer.image_strip_add(
            directory=img_folder,
            files=files, # this requires each element to have the form of {"name":filepath}
            show_multiview=False,
            frame_start=1, frame_end=len(files),
            channel=1)

Now take a look at the lower-level implementation without operators:

# Get your sequence of image files
files = [ f for f in os.path.listdir(FILEPATH) if f.endswith(".png") ]

# This works in all cases, and is in the spirit of lower level, non operator code.
# Create the sequence object, which will be just 1 frame in length
seq = bpy.context.scene.sequence_editor.sequences.new_image(
            name="Frame Sequence",
            filepath=os.path.join(FILEPATH,files[0]), # a full filepath for the first image
            channel=1,
            frame_start=1)
# now add each additional frame
for f in files[1:]:
    seq.elements.append(f)

I like this translation as it helps showcase the underlining data structure of a sequence object: a primary filepath, plus a list of elements referring to other files in the same folder. Timing comparisons to come.

More examples of operator translations are coming soon. Give a shout if there are any other operators you want to include or have recommended translations to include!

Want to learn more?

Some notes

Please note that from a technical standpoint, there is nothing wrong with using operator calls in your scripts or even as lines of code within your own operators. However, in many cases – particularly when there are few objects involved – using lower-level python API calls instead of the equivalent operators can reduce processing time. But, in some cases operators can execute faster than the API calls. One noteworthy example of when this can be true is duplicating a lot of objects at once. For instance, note the following trend on a simple cube-duplication test (times are in seconds):

  • Duplicating 3 objects: Operator (0.00182) is slower than API call (0.00129) by a factor of 1.41
  • Duplicating 51 objects: Operator (0.0181) is slower than API call (0.0158) by a factor of 1.15
  • Duplicating 343 objects: Operator (0.198) is about equal but slower than API call (0.186) by a factor of 1.07
  • Duplicating 2402 objects: Operator (7.08) is faster than API call (10.98) by a factor of 1.55

So a conclusion could be that API calls are more efficient for duplicating small numbers of objects and less so for larger numbers of objects. This is not always true, as for example deleting objects can be significantly faster by using API calls:

  • Deleting 3 objects: Operator (0.00151) is slower than API call (0.00129) by a factor of 1.17
  • Deleting 51 objects: Operator (0.1434) is significantly slower than API call (0.0016) by a factor of 91.4
  • Deleting 343 objects: Operator (7.014) is significantly slower than API call (0.035) by a factor of 199
  • Deleting 2402 objects: Operator (332.28) is significantly slower than API call (3.03) by a factor of 109

In that last extreme example, the python API calls took 3 seconds while the operator call took over 5 minutes. For deleting objects, the API call seems to always be faster and can save a lot of time when deleting a large number of objects. The end point is: Be sure to use your judgement and run test timings. If speed is a primary concern, test scenarios out using the time library:

import time
t0 = time.time()
bpy.ops.object.select_all(action='SELECT') # run some code
t1 = time.time()
print("The time to run that code was: ",t1-t0)

 

Share FacebookTwitterPinterestEmail

More in this area

  • Software Development
    • Blender Addons & Development
      • Blender Operator Translations
      • MCprep | Blender Minecraft addon
        • MCprep changelogs and releases
        • MCprep Minecraft World Imports
        • MCprep Skin Swapping
        • MCprep Spawner
        • MCprep World Tools
        • Mineways and jmc2obj Exporters
        • Realtime MCprep Addon Stats
        • Reporting MCprep Errors
    • Mobile App Development
    • OpenCV Image Processing
      • Basic image processing – thresholds and filters
      • Cancer Tumor Processing with OpenCV
      • Tracking Bats and Multi-Object Segmentation in OpenCV
      • Tracking Shape Recognition with OpenCV

Recent Posts

  • Paper games win hearts – GDKO 2024

  • Escher Port: Impossible Staircase game

  • Winner: MCprep Version Artwork Contest

  • MCprep Version Artwork Contest

  • DuckCowDrive – Driving game prototype ready!

Moo-Ack! Productions


A producer of digital media and software, and a proud subscriber to the Blender Cloud. Want to support us? Consider subscribing!

Additional links & Resources

  • MCprep Hall of Fame
  • Blender Addons & Development
  • Blender Operator Translations
  • Website blog
  • Media Mentions

Patrick W. Crawford

Patrick W. Crawford

I am the creator behind Moo-Ack! Productions - a computer engineer, digital artist, and professional technology consultant | Linkedin profile

  • Facebook
  • Twitter
  • Instagram
  • Linkedin
  • Youtube

@2024 - Moo-Ack! Productions, all rights reserved
Home | Privacy Policy | Contact Us


Back To Top
  • About
    • About Site
    • TheDuckCow in the Media
    • Contact and Support
  • Animation
    • Minecraft
    • Stopmotion
    • Originals
  • Tutorials
    • Tutorials

      Minute Minecraft Animation Tutorials

      September 14, 2020

      Tutorials

      Easy user error reporting for Blender Addons

      March 28, 2020

      Tutorials

      Update Addons with both Blender 2.8 and 2.7…

      January 24, 2019

      Tutorials

      Blender 3D Tutorials

      May 5, 2018

      Tutorials

      Creating Overrides with Linked Libraries

      March 17, 2018

  • Contests
  • Development
    • Blender 3D
    • MCprep Addon
    • Mobile Apps
    • OpenCV
  • Gallery
    • MCStudio Renders
    • Personalized 3D Cards
    • Fresh off the Farm
    • Hyperlapses & Timelapses
  • Free Download