1

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.

ttsesm
  • 409
  • 3
  • 10
  • 1
    So you want to shift objects to some point keeping their respective position? – lemon Sep 21 '20 at 17:14
  • @lemon yup, where this point for example in my case is the center of the plane. – ttsesm Sep 22 '20 at 07:46
  • 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. – lemon Sep 22 '20 at 08:00
  • 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. – ttsesm Sep 22 '20 at 08:48
  • 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. – lemon Sep 22 '20 at 08:51
  • you can get a sample scene from the following link https://we.tl/t-mlPoOgnNjp I will update my initial post with some pics what I want to achieve. – ttsesm Sep 22 '20 at 09:44

2 Answers2

1

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

lemon
  • 60,295
  • 3
  • 66
  • 136
1

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)

batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • Interesting approach, thanks. Can you explain what do you mean by calling to set the origin operator to 'BOUNDS'. I did not get it. – ttsesm Sep 22 '20 at 11:44
  • 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). – batFINGER Sep 22 '20 at 12:01
  • 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. – ttsesm Sep 22 '20 at 12:19
  • 1
    Deja Vu https://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. – batFINGER Sep 22 '20 at 13:05
  • 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 (https://blender.stackexchange.com/questions/195333/subdivide-object-faces-by-area-in-python-script).... – ttsesm Sep 22 '20 at 15:54
  • ...and based on your corresponding examples from https://blender.stackexchange.com/a/93597/106722, https://blender.stackexchange.com/a/168193/106722, https://blender.stackexchange.com/a/120244/106722, https://blender.stackexchange.com/a/163936/106722 I need to be in EDIT mode. – ttsesm Sep 22 '20 at 15:56
  • Has nothing to do with edit mode ???? It is an object mode operator bpy.ops. object The links appear to be about subdividing edges which appears to be regarding your next question. Running the script here https://blender.stackexchange.com/a/186009/15543 does exactly what is required. Simply scrap using the set origin operator. – batFINGER Sep 22 '20 at 16:15
  • Yes, the links were related with the other question because I was testing the subdivision part based on edges and for that I had to switch to EDIT mode. Thus I was using the set origin operator but it was not working and I thought that it might be related. In any case I will report in the other thread if needed. Thanks a lot for your time. – ttsesm Sep 22 '20 at 16:34