# Set origin to bottom center of multiple objects

I have a lot of 3D objects of various sizes, shapes in blender 2.76b and I would like to set the origin to the bottom center of the objects bounds.

I cannot select the bottom edge and snap to cursor etc as each shapes edges are in different X/Y positions.

Is there a script or tool that will allow me to set the origin to the lowest edge z position?

• If I'm not mistaken what you're asking for is a way to set an object's pivot to the center of the bottom of its bounds in world space, is that correct? – MaVCArt Nov 25 '15 at 16:43
• Yes, that is 100% correct – MrDave Nov 25 '15 at 18:54
• – zeffii Nov 26 '15 at 13:53

Move the origin to bottom of all selected mesh objects.

EDIT updated for 2.8. See prior revision for prior.

Object mode script. Moves origin to bottom of mesh, leaving object in place. Pass the matrix world to move origin to global z.

• Looks at the 8 coords of objects bounding box. The bounding box is in local coordinates. To change space pass a matrix. eg the matrix world of the object will determine lowest z in global space.

• o is the bounding box center. Set o.z for new origin from min z of bounding box points.

• Transform all vertices by -o

• Move the object back globally by o to keep the object in place.

import bpy
from mathutils import Matrix, Vector

def origin_to_bottom(ob, matrix=Matrix()):
me = ob.data
mw = ob.matrix_world
local_verts = [matrix @ Vector(v[:]) for v in ob.bound_box]
o = sum(local_verts, Vector()) / 8
o.z = min(v.z for v in local_verts)
o = matrix.inverted() @ o
me.transform(Matrix.Translation(-o))

mw.translation = mw @ o

for o in bpy.context.scene.objects:
if o.type == 'MESH':
origin_to_bottom(o)
#origin_to_bottom(o, matrix=o.matrix_world) # global


With some numpy

The global version above drops in global z direction from bbox center of geom, which can lead to an origin outside the bounding box.

Instead here is a version that finds all z minima and sets the origin to their mean

from mathutils import Matrix, Vector
import numpy as np

def origin_to_bottom(ob, matrix=Matrix(), use_verts=False):
me = ob.data
mw = ob.matrix_world
if use_verts:
data = (v.co for v in me.vertices)
else:
data = (Vector(v) for v in ob.bound_box)

coords = np.array([matrix @ v for v in data])
z = coords.T[2]
mins = np.take(coords, np.where(z == z.min())[0], axis=0)

o = Vector(np.mean(mins, axis=0))
o = matrix.inverted() @ o
me.transform(Matrix.Translation(-o))

mw.translation = mw @ o


This could be further enhanced by using methods outlined in this excellent Q / A

Replace matrix @ vector list comprehensions with something more efficient

Note: Remember objects can share a mesh. The test code above runs over all mesh objects in the scene without checking for this.

• That doesn't work, it seems to set the origin to the center of the bounds in all axis, I need Z position to be bottom of the object bounds. – MrDave Nov 25 '15 at 18:54
• actually it seems to set the x origin position to the highest x vert position ? Strange as the code looks like it should work for lowest z?? – MrDave Nov 25 '15 at 19:08
• I'd suggest calculating the average XY and minimum Z of all the global vertex coordinates (rather than calculating it for the local coordinates, then calculating the global value only for the resulting origin), because rotations, scale, etc can give you weird results which will result with a wrong origin. – TLousky Nov 25 '15 at 19:19
• Added the origin to geometry operator as the code only worked for origins within a mesh. The position given is local (put 3d cursor in local mode) and the origin will be on minz, is your object rotated? Do you want the origin of the object to be relative to their current world position? If so perhaps adding an empty parent as a handle would be another approach – batFINGER Nov 25 '15 at 19:20
• This is very helpful for text objects. I appreciate the script! – Jeff Lange Jan 23 '17 at 18:07

Just wanted to add this here:

bl_info = {
"name": "Origin to...",
"author": "Martynas Žiemys",
"version": (1, 0),
"blender": (2, 82, 0),
"location": "3D Viewport, Alt+Shift+Ctrl+D",
"description": "Moves the origin point to top, botom and sides of objects' bounding boxes in global space.",
}

import bpy
from mathutils import Vector
from bpy.props import (
BoolProperty,
EnumProperty,
)

class OBJECT_OT_origin_to(bpy.types.Operator):
"""Move the origin to top, botom or sides(global space)"""
bl_idname = "object.origin_to"
bl_label = "Origin to..."
bl_options = {'REGISTER', 'UNDO'}

center: BoolProperty(
name="Center in other axis",
description="Center in other axis",
default=True,
)
move: BoolProperty(
name="Move to 3D Cursor",
description="Move to 3D Cursor",
default=False,
)

sides =  (
('X', "X", "X"),
('-X', "-X", "-X"),
('Y', "Y", "Y"),
('-Y', "-Y", "-Y"),
('Z', "Z", "Z"),
('-Z', "-Z", "-Z"),
)
side: EnumProperty(
name="Side",
items=sides,
default='-Z',
)

@classmethod
def poll(cls, context):
return context.object is not None

def execute(self, context):
switch_mode = False
if context.mode != 'OBJECT':
switch_mode = True
bpy.ops.object.mode_set(mode='OBJECT', toggle=True)

for o in context.selected_objects:
if o.type == "MESH":
d = o.data
m = o.matrix_world
if self.center:
bounds_center = (sum((m @ Vector(b) for b in o.bound_box), Vector()))/8
difference = m.translation - bounds_center
local_difference = difference @ m
for v in d.vertices:
v.co += local_difference
m.translation -= difference

difference = Vector((0,0,0))
if self.side == 'X':
bound = max((m @ v.co).x for v in d.vertices)
difference.x = m.translation.x - bound
elif self.side == '-X':
bound = min((m @ v.co).x for v in d.vertices)
difference.x = m.translation.x - bound
elif self.side == 'Y':
bound = max((m @ v.co).y for v in d.vertices)
difference.y = m.translation.y - bound
elif self.side == '-Y':
bound = min((m @ v.co).y for v in d.vertices)
difference.y = m.translation.y - bound
elif self.side == 'Z':
bound = max((m @ v.co).z for v in d.vertices)
difference.z = m.translation.z - bound
elif self.side == '-Z':
bound = min((m @ v.co).z for v in d.vertices)
difference.z = m.translation.z - bound

local_difference = difference @ m
for v in d.vertices:
v.co += local_difference
m.translation -= difference

if self.move:
o.location = context.scene.cursor.location
if switch_mode:
bpy.ops.object.mode_set(mode='OBJECT', toggle=True)

return {'FINISHED'}

bl_idname = "view3d.origin_to_pie"
bl_label = "Origin to..."

def draw(self, context):
layout = self.layout

op = pie.operator("object.origin_to", text = "Origin to Left(-X)")
op.side = '-X'
op.move = False
op = pie.operator("object.origin_to", text = "Origin to Right(X)")
op.side = 'X'
op.move = False
op = pie.operator("object.origin_to", text = "Origin to Bottom(-Z)")
op.side = '-Z'
op.move = False
op = pie.operator("object.origin_to", text = "Origin to Top(Z)")
op.side = 'Z'
op.move = False
op = pie.operator("object.origin_to", text = "Origin to Back(Y)")
op.side = 'Y'
op.move = False
op = pie.operator("object.origin_to", text = "Origin to Top and cursor")
op.side = 'Z'
op.move = True
op = pie.operator("object.origin_to", text = "Origin to Front(-Y)")
op.side = '-Y'
op.move = False
op = pie.operator("object.origin_to", text = "Origin to Bottom and cursor")
op.side = '-Z'
op.move = True

def register():
bpy.utils.register_class(OBJECT_OT_origin_to)
bpy.utils.register_class(VIEW3D_MT_PIE_origin_to_pie)

if kcfg:
km = kcfg.keymaps.new(name='3D View Generic', space_type='VIEW_3D')
kmi = km.keymap_items.new("wm.call_menu_pie", 'D', 'PRESS', any=False, alt=True, ctrl=True,shift=True)
kmi.properties.name = "view3d.origin_to_pie"

def unregister():

for kmi in km.keymap_items:
if kmi.idname == kmi_idname:
km.keymap_items.remove(kmi)

bpy.utils.unregister_class(VIEW3D_MT_PIE_origin_to_pie)
bpy.utils.unregister_class(OBJECT_OT_origin_to)

if __name__ == "__main__":
register()

# test calls
#bpy.ops.object.origin_to()


That's made into a little add-on, that if saved to a file with .py extension from Blender's text editor(utf-8 encoding) can be installed as any add-on and creates a pie menu called with Alt+Ctrl+Shift+D:

I wanted to make it for myself for quite some time to help me with placing objects in my scene like ceiling lights, chairs, books or accessories on shelves and so on. I suppose you do not need to manipulate the origin for that, but I find it convenient so since I wrote it now and it's quite related to the question, I can share it here. It works on all selected objects and if you happen to be in edit mode, it switches back and forth as well.

• Oh, the mode is not checked for because I want it to work from other modes. I switch to Object mode if it's different and the object type is also taken into consideration as it only acts on mesh objects, I didn't think there was a need to loop through all the selection all the time just to check if I have mesh objects selected and I want it to work on all mesh objects that are amongst selected objects, so that was a conscious decision. – Martynas Žiemys Apr 21 '20 at 15:06
• Works really nice when testing on a cube I add fresh to my scene. However if the object has been rotated the actual object starts moving rather than just the origin. (Add cube, scale along an axis, rotate along different axis, <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>Shft</kbd>+<kbd>D</kbd> and moving to bottom shifts my cube up and left) – Caz Oct 18 '20 at 11:15

This script will move the origin to the median point of the vertex/vertices with the lowest Z value (not necessarily the bottom center of the object).

Copy this script to Text window and Run Script, this will work in Edit mode and object mode, results:
1- Object mode is activated in the end result.
2- Origin set to the median point of the vertex/vertices with the lowest Z value.
4- 3D cursor is set at the new origin.

    import bpy

def select_lower_Z():

o = bpy.context.object  # active object
mw = o.matrix_world      # Active object's world matrix
glob_vertex_coordinates = [ mw * v.co for v in o.data.vertices ] # Global coordinates of vertices

bpy.ops.object.mode_set(mode = 'EDIT') #Change mode of selected object to Edit mode
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') #Set the type in Edit mode to Vertices
bpy.ops.mesh.select_all(action = 'DESELECT') #Deselect all

bpy.ops.object.mode_set(mode = 'OBJECT') #Change mode of selected object to Object mode

# Find the lowest Z value amongst the object's verts
minZ = min( [ co.z for co in glob_vertex_coordinates ] )

# Select all the vertices that are on the lowest Z
for v in o.data.vertices:
if (mw * v.co).z == minZ:
v.select = True

bpy.ops.object.mode_set(mode = 'EDIT') #Change mode of selected object to Edit mode

current_area_type = bpy.context.area.type#Save the current area type to a variable
area = bpy.context.area #Change the area to 3D view in order to get rid of wrong context error
old_type = area.type #Change the area to 3D view in order to get rid of wrong context error
area.type = 'VIEW_3D' #Change the area to 3D view in order to get rid of wrong context error
bpy.ops.view3d.snap_cursor_to_selected() #Move the cursor to selected
bpy.ops.object.mode_set(mode= 'OBJECT') #Set the mode back to Object mode
bpy.ops.object.origin_set(type='ORIGIN_CURSOR') #Move the selected object origing's to the 3D cursor's location
bpy.context.area.type = current_area_type #Set the area type back to what it was before changing it to 3D view

select_lower_Z()

• Only works for one at a time – AzulShiva May 15 '17 at 14:48
• Yes, this script is part of an add-on I was working on, the add-on now is complete, works for multiple mesh objects and has many features. Here's a link to the tutorial youtu.be/OaieiTAh5ZA the download link is in the description – Georges D May 16 '17 at 16:11
• " This video is unavailable. Sorry about that." Link is dead – AzulShiva May 16 '17 at 18:42
• youtu.be/oaieiTAh5ZA – Georges D May 20 '17 at 9:22

well someone noticed it was not working when adding shape keys. here is a solution

import bpy
import bmesh
from mathutils import Matrix, Vector

obj=bpy.context.object
me = obj.data
mw = obj.matrix_world
difference = Vector((0,0,0))
local_verts = [mw @ Vector(v[:]) for v in obj.bound_box]
o = sum(local_verts, Vector()) / 8
o.z = min(v.z for v in local_verts)

if obj.data.shape_keys:
difference.z = mw.translation.z - o.z
local_difference = difference @ mw
# select basis
obj.active_shape_key_index = 0
# edit verts
bpy.ops.object.editmode_toggle()
bm = bmesh.from_edit_mesh(me)

for v in bm.verts:
v.co += local_difference

bpy.ops.object.editmode_toggle()
mw.translation -= difference


but the interest of this code go further you can move origin in editmode

import bpy
import bmesh
from mathutils import Matrix, Vector

bpy.ops.object.mode_set(mode='EDIT')
obj=bpy.context.object
me = obj.data
mw = obj.matrix_world
difference = Vector((0,0,0))
local_verts = [mw @ Vector(v[:]) for v in obj.bound_box]
o = sum(local_verts, Vector()) / 8
o.z = min(v.z for v in local_verts)

difference.z = mw.translation.z - o.z
local_difference = difference @ mw

bm = bmesh.from_edit_mesh(me)

for v in bm.verts:
v.co += local_difference

bmesh.update_edit_mesh(me)
mw.translation -= difference


n.b: It sets the origin to the center of selected vertices.

This procedure will work on blender 2.8 and above

Steps:

1. Install addon - Set Origin Plus
2. You will get an option Set Origin Plus in your sidebar
3. Now select all objects
4. Go to Edit mode
5. Select all faces at whose center, the origins will be set
6. Open side bar
7. Go to set origin plus
8. Click on update origins
9. Job is done

Set origin plus - Youtube tutorial