# How can I make a custom gizmos to set the origin

I want to make a custom gizmo to set the origin. I mean when we select the 3d model a cube (subdivided cube) like the below image will appear on the 3d model and scale based on 3d model size dimension.

and when we select the vertex it should set the origin based on vertex position.

suppose we have a 3d model. when I select the 3d model gizmos should appear

first I select the point on gizmos

and then I set the origin

and finally, I will delete the custom gizmos because I don't need it

I want to make these steps automatic because I have thousand of 3d models

bpy.ops.object.editmode_toggle()
bpy.ops.view3d.snap_cursor_to_selected()
bpy.ops.object.editmode_toggle()
bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')


• Would you accept a "non gizmo" approach? Sep 2 '20 at 15:12
• @lemon feel free to answer no problem. thank you Sep 2 '20 at 15:48

I propose to do it with a modal operator, not a gizmo:

The principles:

Run the operator (search it using its name F3 and type set origin on boundings). Then the operator creates a sphere to display it as green point as shown above. And it register a draw handler in the 3D view, in order to display the boundings and the small green spheres.

The operator is a modal so it will continue until 'FINISHED' or if 'CANCELED' is returned. In this case, it returns 'CANCELED' when the ESC key is hit. Now for each modal call, it does the following:

• Get the view location
• Get the mouse cursor location
• Get the active object

And from that the bounds of the object are going to be calculated in order to display a black subdivided mesh (some kind of lattice) as well as figuring out which point to highlight in green.

Here we can use the dot product the view location/mouse location with view location/bouding point location and keep the best one. When all that done, validating the point where to set the object origin:

If you left-click (LMB) then the previously identified bounding point will be used to change the origin. It seems to work fine independently of the mode (object, edit, etc.) but I've not tested them all.

import bpy
import bmesh
import gpu
from mathutils import Vector, Matrix
from bpy_extras.view3d_utils import region_2d_to_location_3d

# indices to make cube edges from boundings
bounding_indices = (
(0, 1), (1, 2), (2, 3), (3, 4), #up
(4, 5), (5, 6), (6, 7), (7, 0),
(8, 1), (8, 3), (8, 5), (8, 7),
(9, 10), (10, 11), (11, 12), (12, 13), #slide
(13, 14), (14, 15), (15, 16), (16, 9), #down
(17, 18), (18, 19), (19, 20), (20, 21),
(21, 22),(22, 23), (23, 24), (24, 17),
(25, 18), (25, 20), (25, 22), (25, 24),
(0, 9), (1, 10), (2, 11), (3, 12), # transversal
(4, 13), (5, 14), (6,15), (7, 16),
(9, 17), (10, 18), (11, 19), (12, 20),
(13, 21), (14, 22), (15, 23), (16, 24))

# Cut a bounding face in half
def bounding_cuts(b_box, result, indices):
p0 = Vector(b_box[indices[0]])
p1 = Vector(b_box[indices[1]])
p2 = Vector(b_box[indices[2]])
p3 = Vector(b_box[indices[3]])
result.append(p0)
result.append((p0 + p1) / 2.0)
result.append(p1)
result.append((p1 + p2) / 2.0)
result.append(p2)
result.append((p2 + p3) / 2.0)
result.append(p3)
result.append((p3 + p0) / 2.0)
result.append((p0 + p1 + p2 + p3) / 4.0)

def up_down_slide(b_box):
up = []
down = []
slide = []
bounding_cuts(b_box, up, [0, 1, 2, 3])
bounding_cuts(b_box, down, [4, 5, 6, 7])
slide = [(p0+p1)/2.0 for p0,p1 in zip(up[:-1],down[:-1])]
return up, down, slide

# Get half parts of bounding box
def bounding_points(obj):
b_box = obj.bound_box[:]
up, down, slide = up_down_slide(b_box)
result = up + slide + down
return result

def draw_callback(self, context):
obj = context.active_object
if obj and obj.type == 'MESH':
# translate bounding in world co
mat = obj.matrix_world
obj_coords = [mat @ Vector(c) for c in bounding_points(obj)]

# draw boudings

# if close enough to a corner
if self.highlight != -1:
# draw a small sphere on it
scale = self.highlight_distance / 100.0
base_coord = obj_coords[self.highlight]
h_coords = [base_coord + (Vector(c) * scale) for c in self.highlight_coords]

class ModalOperator(bpy.types.Operator):
"""Set origin with the mouse"""
bl_idname = "object.set_origin_on_boundings"
bl_label = "Set origin on boundings"

# Create highlight (corner) geometry
def create_highlight(self):
# it is based on UV sphere triangulated for the GPU rendering
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments= 6, v_segments=4, diameter=1)
bmesh.ops.triangulate(bm, faces=bm.faces)
self.highlight_coords = [v.co.to_tuple() for f in bm.faces for v in f.verts]

# Update object information
def update_object(self, context, obj, view_loc, mouse_loc):
prev_highlight = self.highlight
self.highlight = -1
if obj and obj.type == 'MESH':
self.target = obj.name

# Find the closest corner using a dot product from the view
mat = obj.matrix_world
obj_coords = [mat @ Vector(c) for c in bounding_points(obj)]

axis = (mouse_loc - view_loc).normalized()
angles = [axis.dot((c - view_loc).normalized()) for c in obj_coords]
min_angle = max(angles)

# and keep it if close enough
if min_angle > 0.999:
self.highlight = angles.index(min_angle)
self.highlight_distance = (view_loc - obj_coords[self.highlight]).length

if self.highlight != prev_highlight:
context.area.tag_redraw()

# Mouse location in 3D
def mouse_location(self, context, event):
x, y = event.mouse_region_x, event.mouse_region_y
loc = region_2d_to_location_3d(context.region, context.space_data.region_3d, (x, y), (0, 0, 0))
return loc

# View location in 3D
def view_location(self, context):
camera_info = context.space_data.region_3d.view_matrix.inverted()
return camera_info.translation

# Move the object origin to the wanted corner
def set_origin(self, context, obj, index):
mat = obj.matrix_world
loc = mat @ Vector(bounding_points(obj)[index])
mode = obj.mode
me = obj.data
if mode == 'OBJECT':
mat0 =mat.copy()
t = loc - mat @ Vector()
mat.translation += t
local = mat.inverted() @ mat0 #move verts back
me.transform(local)
else:
local = mat.inverted() @ loc #move verts back (in prevision of next operation)
ml = Matrix.Translation(-local)
bm = bmesh.from_edit_mesh(me)
bm.transform(ml)
bmesh.update_edit_mesh(me)

t = loc - mat @ Vector()
mat.translation += t #move origin and verts

# Force the 3D view to redraw when needed
def redraw(self, context):
bpy.ops.wm.redraw_timer(type='DRAW_SWAP', iterations=1)

def modal(self, context, event):

mouse_loc = self.mouse_location(context, event)
view_loc = self.view_location(context)
obj = context.active_object

self.update_object(context, obj, view_loc, mouse_loc)

# when left click, set the origin if a highlight point is known
if event.type in {'LEFTMOUSE'}:
if self.highlight != -1:
self.set_origin(context, obj, self.highlight)
return {'RUNNING_MODAL'} #We don't want the click to be taken into account further

# esc is used to quit and remove the draw handler
elif event.type in {'ESC'}:
bpy.types.SpaceView3D.draw_handler_remove(self.handle, 'WINDOW')
context.area.tag_redraw()
return {'CANCELLED'}

return {'PASS_THROUGH'}

def invoke(self, context, event):
self.highlight = -1
if context.area.type == 'VIEW_3D':
self.create_highlight()
args = (self, context)
self.handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback, args, 'WINDOW', 'POST_VIEW')
context.area.tag_redraw()
return {'RUNNING_MODAL'}
return {'FINISHED'}

keymaps = []

def register():
bpy.utils.register_class(ModalOperator)

wm = bpy.context.window_manager

# Ctrl+Shif+B as shortcut
kmi = km.keymap_items.new(ModalOperator.bl_idname, 'B', 'PRESS', ctrl=True, shift=True)

keymaps.append((km, kmi))

def unregister():
for km, kmi in keymaps:
km.keymap_items.remove(kmi)
keymaps.clear()

bpy.utils.unregister_class(ModalOperator)

if __name__ == "__main__":
register()

# test call
bpy.ops.object.set_origin_on_boundings('INVOKE_DEFAULT')


Run the script and use ctrlshiftB to activate the operator and esc to stop it.

Note: I'm still using right click select, so I've set the trigger that position the origin on the left click. You may want to change that.

• Really nice answer! Sep 2 '20 at 19:23
• Hi thanks man this is exactly what I want. I ran the operator and searched for set origin on bounding but I couldn't find that. I use blender 2.83 Sep 2 '20 at 19:30
• Can confirm for me it works straight out of the box. @lemon had something similar in mind for blender.stackexchange.com/a/163258/15543 Sep 3 '20 at 5:43
• @SeyedMortezaKamali, have added ctrl+shift+b as shortcut in meantime (answer edited). Sep 3 '20 at 5:54
• @SeyedMortezaKamali, the way I did it is not the best for what you want to do as vertices of the boundings are duplicated here (for simplicity when wrote it). I think you should rework it a bit so that bounding_points function return 1 point per location and so you'll need to change the bounding_indices appropriately. Feel free to ask if not clear enough. Once done, the result of bounding_points function will give you the coordinates (just to be transformed with obj world matrix to have them in world space if this is what you want to display) Sep 5 '20 at 6:42