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