The script we wrote for quickly moving an object’s origin to the selection works well. As long as instances aren’t used in the scene. Moving the origin to the selection on an instanced object will cause all instances of the object to move from their original positions to the new ones. This happens because by moving the origin relative to the object’s geometry, we’re essentially moving the geometry itself relative to its center point, and then moving this center point (origin) to the new position. For instanced objects, the origin remains in the same place, and the geometry’s offset doesn’t disappear, resulting in a jump.
The solution is quite simple. Essentially, we simply need to iterate through all the object instances and shift their origins so that the displaced geometry returns to its original position.
We’ll modify our script to account for instance behavior and return them to their original position when the origin is moved to the center of the selection.
We begin, as before, from getting the cursor position.
|
1 |
cursor_src_location = bpy.context.scene.cursor.location.copy() |
Now, for future corrections, let’s compile a list of the current object’s instances. We won’t include the active object itself in this list, since we’re working with it initially, and it doesn’t require additional position correction.
We’ll store a pointer to the instance itself, as well as its world matrix; we’ll need it for further transformations.
|
1 2 3 4 5 |
instances = [(o, o.matrix_world) for o in bpy.data.objects \ if o.data == bpy.context.object.data and o != bpy.context.active_object] # [(bpy.data.objects['Cube.001'], Matrix(((-0.6280437707901001, -0.21648520231246948, -0.7474591135978699, 2.7219083309173584), ... # (bpy.data.objects['Cube.003'], Matrix(((0.4437439739704132, -0.6796227693557739, -0.5841268301010132, 4.871679782867432), ...] |
As before, we get the current object’s position and a pointer to the 3D viewport’s area to use context override (so that the script works correctly when called from the Text Edit).
|
1 2 3 4 |
obj_location = bpy.context.object.location area = next((area for area in bpy.context.screen.areas if area.type == 'VIEW_3D')) |
Override the context and place the cursor in the center of the current selection.
|
1 2 |
with bpy.context.temp_override(area=area): bpy.ops.view3d.snap_cursor_to_selected() |
And now let’s return to the additional transformations needed to fix the instances.
To move the instances to their proper locations, we need to understand how much the geometry is shifted relative to the origin. This seems simple: just take a vector from the current center of the object to the cursor position where it will be moved.
However, it’s important to consider that unapplied transformations may exist on the current object, so we need to multiply the cursor position vector by the inverted transformation matrix of the object.
|
1 2 3 |
cur_lok = bpy.context.object.matrix_world.inverted() @ bpy.context.scene.cursor.location # <Vector (-1.0000, 1.0000, 1.0000)> |
This is the vector by which we need to shift all instances to fix them.
Now let’s move the origin of the current object to the center of the selection, as we did before.
|
1 2 |
bpy.ops.object.mode_set(mode = 'OBJECT') bpy.ops.object.origin_set(type='ORIGIN_CURSOR') |
Before returning to edit mode, we’ll apply corrective offsets to all previously acquired instances.
|
1 2 |
for _o, _m in instances: _o.location = (_m @ cur_lok) |
Since instances may have their own unapplied transformation matrices, we must multiply the local origin offset for the geometry by the instance’s transformation matrix.
All we have to do now is return to edit mode and move the cursor to its original position in the scene.
|
1 2 3 |
bpy.ops.object.mode_set(mode = 'EDIT') bpy.context.scene.cursor.location = cursor_src_location |
Now, if we execute our script with these modifications, all object instances will remain in their original locations.

.blend file on Patreon