this is my first time coding for Blender, I haven't gotten around to organizing the code yet. I'm self taught so my code's not the cleanest.

I'm attempting to create a 'dorito' deformer using this code, by selecting a vertex and running this script, it would create two empties that would be used for a warp modifier that would be added to the selected object. I am using bmesh to access the selected vertex's coordinates so I could add the empties to that location, storing it into the "vertex" variable.

I am storing the return value of the empties to an empty list, but I don't know how to use that to be able to add more empties to a second or third warp modifier making the script actually useful.

Any input is appreciated!

PS: I am scripting this for 2.92. Just in case there are relevant changes in the API.

import bpy
import bmesh

if bpy.ops.object.mode_set.poll():

object_data = bpy.context.object.data
meObject = bpy.context.active_object
bm = bmesh.new()

bpy.context.view_layer.objects.active = meObject

if bpy.ops.object.mode_set.poll():

#gets vertex position to add empties
vertex = (0,0,0)
for v in bm.verts:
    if v.select:
        vertex = tuple(v.co)

#changes mode
if bpy.ops.object.mode_set.poll():

#empties list to store empties
empties = []

#adds first empty for warp deformer,  and appends to list
empties.append(bpy.ops.object.empty_add(type='PLAIN_AXES', align='WORLD', location=(vertex), scale=(2, 2, 2)))
#names empty added
bpy.context.active_object.name = 'Parent'

#sets parent name
bpy.context.object.parent = meObject

#adds second empty for warp deformer
empties.append(bpy.ops.object.empty_add(type='PLAIN_AXES', align='WORLD', location=(vertex), scale=(1, 1, 1)))
#names empty added
bpy.context.active_object.name = 'Deformer'
#changes empty used
bpy.context.object.empty_display_type = 'CUBE'
bpy.context.object.empty_display_size = 0.75
bpy.context.object.parent = bpy.data.objects["Parent"]

bpy.context.view_layer.objects.active = meObject

#adds warp modifier
bpy.context.object.modifiers["Warp"].object_from = bpy.data.objects["Parent"]
bpy.context.object.modifiers["Warp"].object_to = bpy.data.objects["Deformer"]

if bpy.ops.object.mode_set.poll():


Using API methods.

A common misapprehension in blender is that create / add "anything" operators return the newly created thing, instead the newly created thing is found from context. Eg for add object primitives the new object is context.object directly after the call (if run in OBJECT mode) A newly added modifier sits last in the objects modifiers collection.

That said, it is a lot, lot, lot easier (IMO) to totally ditch the operators & instead use related API methods which return a reference.

Below is an operator-less re-rig of question code to run entirely in EDIT mode

  • Select a vertex

  • Adds a warp modifier, once again using the related API method new_mod = Object.modifiers.new(...), so we have its reference. The new modifiers show in edit mode is set, so we can see the result.

  • Adds two empties, the first is at the location of the vert, the second is offset from the first by the value set for offset These are assigned as the "from" and "to" objects of the modifier.

  • The empties are given a name to reflect object warping, which modifier, from or to. Other things to consider would be putting the empties into their own collection.

  • Looped over the "object_from", "object_to" properties of the modifier. setattr(warp, k, ob) is equivalent to warp.object_from = ob when k == "object_from".

  • The offset is in global coordinates. This is converted to local since the empties are being parented to the warping object. Think this answer helps explain the maths used Which matrix to use to transform children local coordinates to global?

Test code.

import bpy
import bmesh
from mathutils import Vector
from bpy import context

offset = Vector((0, 0, 1))

col = context.collection
ob = context.object
mw = ob.matrix_world
mwi = mw.inverted()
me = ob.data

bm = bmesh.from_edit_mesh(me)

vert = bm.select_history.active
# if not a vert then throw error
assert(isinstance(vert, bmesh.types.BMVert))

warp = ob.modifiers.new("Warp", type='WARP')
warp.show_in_editmode = True
for k in ("object_from", "object_to"):
    mt = bpy.data.objects.new(f"{warp.name}_{k}", None)
    if k.endswith("from"): # 
        mt.location = vert.co
        mt.location = vert.co + mwi @ offset

    setattr(warp, k, mt)
    mt.parent = ob
  • $\begingroup$ Thank you very much for your reply! I'm going to test it out and ask you some things about it, I hope that's alright! $\endgroup$ – kidpunkrock Mar 22 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.