8669c2ddbd6e047d59e270cc515ce711249d3600
[blender-staging.git] / release / scripts / startup / bl_operators / object_align.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2
3 #
4 #  This program is free software; you can redistribute it and/or
5 #  modify it under the terms of the GNU General Public License
6 #  as published by the Free Software Foundation; either version 2
7 #  of the License, or (at your option) any later version.
8 #
9 #  This program is distributed in the hope that it will be useful,
10 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #  GNU General Public License for more details.
13 #
14 #  You should have received a copy of the GNU General Public License
15 #  along with this program; if not, write to the Free Software Foundation,
16 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 #
18 # ##### END GPL LICENSE BLOCK #####
19
20 # <pep8-80 compliant>
21
22 import bpy
23 from bpy.types import Operator
24 from mathutils import Vector
25
26
27 def GlobalBB_LQ(bb_world):
28
29     # Initialize the variables with the 8th vertex
30     left, right, front, back, down, up = (
31         bb_world[7][0],
32         bb_world[7][0],
33         bb_world[7][1],
34         bb_world[7][1],
35         bb_world[7][2],
36         bb_world[7][2],
37     )
38
39     # Test against the other 7 verts
40     for i in range(7):
41
42         # X Range
43         val = bb_world[i][0]
44         if val < left:
45             left = val
46
47         if val > right:
48             right = val
49
50         # Y Range
51         val = bb_world[i][1]
52         if val < front:
53             front = val
54
55         if val > back:
56             back = val
57
58         # Z Range
59         val = bb_world[i][2]
60         if val < down:
61             down = val
62
63         if val > up:
64             up = val
65
66     return (Vector((left, front, up)), Vector((right, back, down)))
67
68
69 def GlobalBB_HQ(scene, obj):
70
71     matrix_world = obj.matrix_world.copy()
72
73     # Initialize the variables with the last vertex
74
75     me = obj.to_mesh(scene=scene, apply_modifiers=True, settings='PREVIEW')
76     verts = me.vertices
77
78     val = matrix_world * verts[-1].co
79
80     left, right, front, back, down, up = (val[0],
81                                           val[0],
82                                           val[1],
83                                           val[1],
84                                           val[2],
85                                           val[2],
86                                           )
87
88     # Test against all other verts
89     for i in range(len(verts) - 1):
90
91         vco = matrix_world * verts[i].co
92
93         # X Range
94         val = vco[0]
95         if val < left:
96             left = val
97
98         if val > right:
99             right = val
100
101         # Y Range
102         val = vco[1]
103         if val < front:
104             front = val
105
106         if val > back:
107             back = val
108
109         # Z Range
110         val = vco[2]
111         if val < down:
112             down = val
113
114         if val > up:
115             up = val
116
117     bpy.data.meshes.remove(me)
118
119     return Vector((left, front, up)), Vector((right, back, down))
120
121
122 def align_objects(context,
123                   align_x,
124                   align_y,
125                   align_z,
126                   align_mode,
127                   relative_to,
128                   bb_quality):
129
130     scene = context.scene
131     space = context.space_data
132
133     cursor = (space if space and space.type == 'VIEW_3D' else scene).cursor_location
134
135     # We are accessing runtime data such as evaluated bounding box, so we need to
136     # be sure it is properly updated and valid (bounding box might be lost on operator
137     # redo).
138     scene.update()
139
140     Left_Front_Up_SEL = [0.0, 0.0, 0.0]
141     Right_Back_Down_SEL = [0.0, 0.0, 0.0]
142
143     flag_first = True
144
145     objects = []
146
147     for obj in context.selected_objects:
148         matrix_world = obj.matrix_world.copy()
149         bb_world = [matrix_world * Vector(v) for v in obj.bound_box]
150         objects.append((obj, bb_world))
151
152     if not objects:
153         return False
154
155     for obj, bb_world in objects:
156
157         if bb_quality and obj.type == 'MESH':
158             GBB = GlobalBB_HQ(scene, obj)
159         else:
160             GBB = GlobalBB_LQ(bb_world)
161
162         Left_Front_Up = GBB[0]
163         Right_Back_Down = GBB[1]
164
165         # Active Center
166
167         if obj == context.active_object:
168
169             center_active_x = (Left_Front_Up[0] + Right_Back_Down[0]) / 2.0
170             center_active_y = (Left_Front_Up[1] + Right_Back_Down[1]) / 2.0
171             center_active_z = (Left_Front_Up[2] + Right_Back_Down[2]) / 2.0
172
173             size_active_x = (Right_Back_Down[0] - Left_Front_Up[0]) / 2.0
174             size_active_y = (Right_Back_Down[1] - Left_Front_Up[1]) / 2.0
175             size_active_z = (Left_Front_Up[2] - Right_Back_Down[2]) / 2.0
176
177         # Selection Center
178
179         if flag_first:
180             flag_first = False
181
182             Left_Front_Up_SEL[0] = Left_Front_Up[0]
183             Left_Front_Up_SEL[1] = Left_Front_Up[1]
184             Left_Front_Up_SEL[2] = Left_Front_Up[2]
185
186             Right_Back_Down_SEL[0] = Right_Back_Down[0]
187             Right_Back_Down_SEL[1] = Right_Back_Down[1]
188             Right_Back_Down_SEL[2] = Right_Back_Down[2]
189
190         else:
191             # X axis
192             if Left_Front_Up[0] < Left_Front_Up_SEL[0]:
193                 Left_Front_Up_SEL[0] = Left_Front_Up[0]
194             # Y axis
195             if Left_Front_Up[1] < Left_Front_Up_SEL[1]:
196                 Left_Front_Up_SEL[1] = Left_Front_Up[1]
197             # Z axis
198             if Left_Front_Up[2] > Left_Front_Up_SEL[2]:
199                 Left_Front_Up_SEL[2] = Left_Front_Up[2]
200
201             # X axis
202             if Right_Back_Down[0] > Right_Back_Down_SEL[0]:
203                 Right_Back_Down_SEL[0] = Right_Back_Down[0]
204             # Y axis
205             if Right_Back_Down[1] > Right_Back_Down_SEL[1]:
206                 Right_Back_Down_SEL[1] = Right_Back_Down[1]
207             # Z axis
208             if Right_Back_Down[2] < Right_Back_Down_SEL[2]:
209                 Right_Back_Down_SEL[2] = Right_Back_Down[2]
210
211     center_sel_x = (Left_Front_Up_SEL[0] + Right_Back_Down_SEL[0]) / 2.0
212     center_sel_y = (Left_Front_Up_SEL[1] + Right_Back_Down_SEL[1]) / 2.0
213     center_sel_z = (Left_Front_Up_SEL[2] + Right_Back_Down_SEL[2]) / 2.0
214
215     # Main Loop
216
217     for obj, bb_world in objects:
218         matrix_world = obj.matrix_world.copy()
219         bb_world = [matrix_world * Vector(v[:]) for v in obj.bound_box]
220
221         if bb_quality and obj.type == 'MESH':
222             GBB = GlobalBB_HQ(scene, obj)
223         else:
224             GBB = GlobalBB_LQ(bb_world)
225
226         Left_Front_Up = GBB[0]
227         Right_Back_Down = GBB[1]
228
229         center_x = (Left_Front_Up[0] + Right_Back_Down[0]) / 2.0
230         center_y = (Left_Front_Up[1] + Right_Back_Down[1]) / 2.0
231         center_z = (Left_Front_Up[2] + Right_Back_Down[2]) / 2.0
232
233         positive_x = Right_Back_Down[0]
234         positive_y = Right_Back_Down[1]
235         positive_z = Left_Front_Up[2]
236
237         negative_x = Left_Front_Up[0]
238         negative_y = Left_Front_Up[1]
239         negative_z = Right_Back_Down[2]
240
241         obj_loc = obj.location
242
243         if align_x:
244
245             # Align Mode
246
247             if relative_to == 'OPT_4':  # Active relative
248                 if align_mode == 'OPT_1':
249                     obj_x = obj_loc[0] - negative_x - size_active_x
250
251                 elif align_mode == 'OPT_3':
252                     obj_x = obj_loc[0] - positive_x + size_active_x
253
254             else:  # Everything else relative
255                 if align_mode == 'OPT_1':
256                     obj_x = obj_loc[0] - negative_x
257
258                 elif align_mode == 'OPT_3':
259                     obj_x = obj_loc[0] - positive_x
260
261             if align_mode == 'OPT_2':  # All relative
262                 obj_x = obj_loc[0] - center_x
263
264             # Relative To
265
266             if relative_to == 'OPT_1':
267                 loc_x = obj_x
268
269             elif relative_to == 'OPT_2':
270                 loc_x = obj_x + cursor[0]
271
272             elif relative_to == 'OPT_3':
273                 loc_x = obj_x + center_sel_x
274
275             elif relative_to == 'OPT_4':
276                 loc_x = obj_x + center_active_x
277
278             obj.location[0] = loc_x
279
280         if align_y:
281             # Align Mode
282
283             if relative_to == 'OPT_4':  # Active relative
284                 if align_mode == 'OPT_1':
285                     obj_y = obj_loc[1] - negative_y - size_active_y
286
287                 elif align_mode == 'OPT_3':
288                     obj_y = obj_loc[1] - positive_y + size_active_y
289
290             else:  # Everything else relative
291                 if align_mode == 'OPT_1':
292                     obj_y = obj_loc[1] - negative_y
293
294                 elif align_mode == 'OPT_3':
295                     obj_y = obj_loc[1] - positive_y
296
297             if align_mode == 'OPT_2':  # All relative
298                 obj_y = obj_loc[1] - center_y
299
300             # Relative To
301
302             if relative_to == 'OPT_1':
303                 loc_y = obj_y
304
305             elif relative_to == 'OPT_2':
306                 loc_y = obj_y + cursor[1]
307
308             elif relative_to == 'OPT_3':
309                 loc_y = obj_y + center_sel_y
310
311             elif relative_to == 'OPT_4':
312                 loc_y = obj_y + center_active_y
313
314             obj.location[1] = loc_y
315
316         if align_z:
317             # Align Mode
318             if relative_to == 'OPT_4':  # Active relative
319                 if align_mode == 'OPT_1':
320                     obj_z = obj_loc[2] - negative_z - size_active_z
321
322                 elif align_mode == 'OPT_3':
323                     obj_z = obj_loc[2] - positive_z + size_active_z
324
325             else:  # Everything else relative
326                 if align_mode == 'OPT_1':
327                     obj_z = obj_loc[2] - negative_z
328
329                 elif align_mode == 'OPT_3':
330                     obj_z = obj_loc[2] - positive_z
331
332             if align_mode == 'OPT_2':  # All relative
333                 obj_z = obj_loc[2] - center_z
334
335             # Relative To
336
337             if relative_to == 'OPT_1':
338                 loc_z = obj_z
339
340             elif relative_to == 'OPT_2':
341                 loc_z = obj_z + cursor[2]
342
343             elif relative_to == 'OPT_3':
344                 loc_z = obj_z + center_sel_z
345
346             elif relative_to == 'OPT_4':
347                 loc_z = obj_z + center_active_z
348
349             obj.location[2] = loc_z
350
351     return True
352
353
354 from bpy.props import (
355     BoolProperty,
356     EnumProperty,
357 )
358
359
360 class AlignObjects(Operator):
361     """Align Objects"""
362     bl_idname = "object.align"
363     bl_label = "Align Objects"
364     bl_options = {'REGISTER', 'UNDO'}
365
366     bb_quality = BoolProperty(
367             name="High Quality",
368             description=("Enables high quality calculation of the "
369                          "bounding box for perfect results on complex "
370                          "shape meshes with rotation/scale (Slow)"),
371             default=True,
372             )
373     align_mode = EnumProperty(
374             name="Align Mode:",
375             description="Side of object to use for alignment",
376             items=(('OPT_1', "Negative Sides", ""),
377                    ('OPT_2', "Centers", ""),
378                    ('OPT_3', "Positive Sides", ""),
379                    ),
380             default='OPT_2',
381             )
382     relative_to = EnumProperty(
383             name="Relative To:",
384             description="Reference location to align to",
385             items=(('OPT_1', "Scene Origin", "Use the Scene Origin as the position for the selected objects to align to"),
386                    ('OPT_2', "3D Cursor", "Use the 3D cursor as the position for the selected objects to align to"),
387                    ('OPT_3', "Selection", "Use the selected objects as the position for the selected objects to align to"),
388                    ('OPT_4', "Active", "Use the active object as the position for the selected objects to align to"),
389                    ),
390             default='OPT_4',
391             )
392     align_axis = EnumProperty(
393             name="Align",
394             description="Align to axis",
395             items=(('X', "X", ""),
396                    ('Y', "Y", ""),
397                    ('Z', "Z", ""),
398                    ),
399             options={'ENUM_FLAG'},
400             )
401
402     @classmethod
403     def poll(cls, context):
404         return context.mode == 'OBJECT'
405
406     def execute(self, context):
407         align_axis = self.align_axis
408         ret = align_objects(
409             context,
410             'X' in align_axis,
411             'Y' in align_axis,
412             'Z' in align_axis,
413             self.align_mode,
414             self.relative_to,
415             self.bb_quality,
416         )
417
418         if not ret:
419             self.report({'WARNING'}, "No objects with bound-box selected")
420             return {'CANCELLED'}
421         else:
422             return {'FINISHED'}
423
424
425 classes = (
426     AlignObjects,
427 )