1
$\begingroup$

I am trying to do what more or less is described in this thread How to place objects on the center of the ground plane via python? the accepted solution seems to work fine if you have only one object in the scene. However, in my case my scene is structured from individual objects thus this leads to the result all the objects to be scrambled together in the center of the plane as you can see below:

enter image description here

Initially my scene is like this:

enter image description here

By using the GUI I was able to center all the objects and maintaining the mesh structure by following these https://blender.stackexchange.com/a/26452 instructions.

enter image description here

Thus, I would like to know how I can achieve that in a python script.

Thanks.

$\endgroup$
6
  • 1
    $\begingroup$ So you want to shift objects to some point keeping their respective position? $\endgroup$ – lemon Sep 21 '20 at 17:14
  • $\begingroup$ @lemon yup, where this point for example in my case is the center of the plane. $\endgroup$ – ttsesm Sep 22 '20 at 7:46
  • $\begingroup$ So that can be either: - calculate objects barycenter and shift all their location by diff between this barycenter and target point. Or: - just set their location to the target point. $\endgroup$ – lemon Sep 22 '20 at 8:00
  • $\begingroup$ Wouldn't this have the problem that part of my objects would be below plane as the guy describes in the first link that I've posted? Bear with my questions but I literally started using blender 3 days ago. $\endgroup$ – ttsesm Sep 22 '20 at 8:48
  • $\begingroup$ Could you give a little example with few objects? The point is: your question mainly show what does not work for you, more than what you want to do. So this is a bit confusing. $\endgroup$ – lemon Sep 22 '20 at 8:51
1
$\begingroup$

A way to do it:

  • Loops overs selected meshes to get each individual center in world coordinates
  • Then calculates overall center
  • Loops again over them to shift their vertices considering the overall center

The script:

import bpy
from mathutils import Vector

# Choose the axis you want to center on
center_x = True
center_y = True
center_z = False

# Get objects
objects = bpy.context.selected_objects

centers = []

# Loop over meshes to get their centers
for obj in [o for o in objects if o.type == 'MESH']:
    world_matrix = obj.matrix_world
    vertices = obj.data.vertices
    # Get object center in world coordinates
    center = sum([world_matrix @ Vector(v.co) for v in vertices], Vector()) / len(vertices)
    centers.append(center)

# Calculate overall center
center = sum([c for c in centers], Vector()) / len(centers)

# Keep axis you want
center.x = center.x if center_x else 0
center.y = center.y if center_y else 0
center.z = center.z if center_z else 0

# Loop over meshes to shift them
for obj in [o for o in objects if o.type == 'MESH']:
    vertices = obj.data.vertices
    world_matrix_inv = obj.matrix_world.inverted()
    # Calculate the shift in object coordinates
    delta = world_matrix_inv @ center
    # Shift vertices
    for v in obj.data.vertices:
        v.co = v.co - delta

$\endgroup$
1
  • $\begingroup$ Thanks a lot. It seems to do the trick. $\endgroup$ – ttsesm Sep 22 '20 at 10:39
1
$\begingroup$

Make and move a global bound box

enter image description here Result of Import, origin to bounds, run script

Calculate the global bounding box coordinates of each mesh object of the selection. From it garner the median x and y, then translate globally all selected objects without parent.

A bounding box is the axis aligned extent of an object.

import bpy
from mathutils import Vector
from bpy import context
import numpy as np

obs = [o for o in context.selected_objects
        if o.type == 'MESH']
        
coords = []
for o in obs:
    coords.extend(o.matrix_world @ Vector(b) for b in o.bound_box) 

x, y, z = np.array(coords).reshape((-1, 3)).T

global_xy_trans = Vector(
    (
        (x.min() + x.max()) / 2 , 
        (y.min() + y.max()) / 2
        )
    )


for o in obs:
    
    if o.parent in obs:
        continue
    o.matrix_world.translation.xy -= global_xy_trans

Prior or after a call to set origin operator. This operator is available via UI Object > Set Origin > Origin to Geometry > Bounds

enter image description here

bpy.ops.object.origin_set(center='BOUNDS') 

will give each part a more sensible origin. It can be run on all selected objects and leaves them in place, it simply moves the origin point. See Changing object origin to arbitrary point without origin_set()?

Applying the translation will give each object an origin at global (0, 0, 0)

$\endgroup$
8
  • $\begingroup$ Interesting approach, thanks. Can you explain what do you mean by calling to set the origin operator to 'BOUNDS'. I did not get it. $\endgroup$ – ttsesm Sep 22 '20 at 11:44
  • $\begingroup$ The obj importer often uses what look like global coordinates for a mesh which leaves the origin way outside the mesh, often at (0, 0, 0). However it is often more useful to have a meaningful origin, in the center of geometry or at a hinge point for a door. The operator does this in one fell swoop, and can be run before or after code above... eg so an object like in image above that is around (0, 0, 0) doesn't have a location (origin point at) (-48, -30, 0). $\endgroup$ – batFINGER Sep 22 '20 at 12:01
  • $\begingroup$ I see. What I've noticed though is that, if I apply the operator via the UI as you showing it does what you describe. On the other hand if I put the bpy.ops.object.origin_set(center='BOUNDS') command at the end (or the beginning) of the code above this results again to a scrambled object output. Aren't the two operations compatible to each other? As I understood both they should be doing the same thing. $\endgroup$ – ttsesm Sep 22 '20 at 12:19
  • 1
    $\begingroup$ Deja Vu blender.stackexchange.com/a/186009/15543 Could incorporate the low level origin set into extend loop above. Appears I never did get around to finding the diff between EXEC call and If the op is invoked ie throws the choice menu bpy.ops.object.origin_set('INVOKE_DEFAULT') or to do the low level thing. Using bounds only looks at 8 bbox coords whereas geom sums all the verts as in lemon's answer. $\endgroup$ – batFINGER Sep 22 '20 at 13:05
  • $\begingroup$ I am sorry but I still do not understand why bpy.ops.object.origin_set(center='BOUNDS') does not work. Also could this be related with the following error "RuntimeError: Operator bpy.ops.object.mode_set.poll() failed, context is incorrect" that I get once I try to select all objects and try to change mode with bpy.ops.object.select_all(action='SELECT') and bpy.ops.object.mode_set(mode="EDIT") respectively. Since I want to continue with further process (blender.stackexchange.com/questions/195333/…).... $\endgroup$ – ttsesm Sep 22 '20 at 15:54

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.