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