Merged revision(s) 57828-57907 from trunk/blender into soc-2013-dingto
[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(context,
118                   align_x,
119                   align_y,
120                   align_z,
121                   align_mode,
122                   relative_to,
123                   bb_quality):
124
125     cursor = context.scene.cursor_location
126
127     Left_Front_Up_SEL = [0.0, 0.0, 0.0]
128     Right_Back_Down_SEL = [0.0, 0.0, 0.0]
129
130     flag_first = True
131
132     objects = []
133
134     for obj in context.selected_objects:
135         matrix_world = obj.matrix_world.copy()
136         bb_world = [matrix_world * Vector(v[:]) for v in obj.bound_box]
137         objects.append((obj, bb_world))
138
139     if not objects:
140         return False
141
142     for obj, bb_world in objects:
143
144         if bb_quality and obj.type == 'MESH':
145             GBB = GlobalBB_HQ(obj)
146         else:
147             GBB = GlobalBB_LQ(bb_world)
148
149         Left_Front_Up = GBB[0]
150         Right_Back_Down = GBB[1]
151
152         # Active Center
153
154         if obj == context.active_object:
155
156             center_active_x = (Left_Front_Up[0] + Right_Back_Down[0]) / 2.0
157             center_active_y = (Left_Front_Up[1] + Right_Back_Down[1]) / 2.0
158             center_active_z = (Left_Front_Up[2] + Right_Back_Down[2]) / 2.0
159
160             size_active_x = (Right_Back_Down[0] - Left_Front_Up[0]) / 2.0
161             size_active_y = (Right_Back_Down[1] - Left_Front_Up[1]) / 2.0
162             size_active_z = (Left_Front_Up[2] - Right_Back_Down[2]) / 2.0
163
164         # Selection Center
165
166         if flag_first:
167             flag_first = False
168
169             Left_Front_Up_SEL[0] = Left_Front_Up[0]
170             Left_Front_Up_SEL[1] = Left_Front_Up[1]
171             Left_Front_Up_SEL[2] = Left_Front_Up[2]
172
173             Right_Back_Down_SEL[0] = Right_Back_Down[0]
174             Right_Back_Down_SEL[1] = Right_Back_Down[1]
175             Right_Back_Down_SEL[2] = Right_Back_Down[2]
176
177         else:
178             # X axis
179             if Left_Front_Up[0] < Left_Front_Up_SEL[0]:
180                 Left_Front_Up_SEL[0] = Left_Front_Up[0]
181             # Y axis
182             if Left_Front_Up[1] < Left_Front_Up_SEL[1]:
183                 Left_Front_Up_SEL[1] = Left_Front_Up[1]
184             # Z axis
185             if Left_Front_Up[2] > Left_Front_Up_SEL[2]:
186                 Left_Front_Up_SEL[2] = Left_Front_Up[2]
187
188             # X axis
189             if Right_Back_Down[0] > Right_Back_Down_SEL[0]:
190                 Right_Back_Down_SEL[0] = Right_Back_Down[0]
191             # Y axis
192             if Right_Back_Down[1] > Right_Back_Down_SEL[1]:
193                 Right_Back_Down_SEL[1] = Right_Back_Down[1]
194             # Z axis
195             if Right_Back_Down[2] < Right_Back_Down_SEL[2]:
196                 Right_Back_Down_SEL[2] = Right_Back_Down[2]
197
198     center_sel_x = (Left_Front_Up_SEL[0] + Right_Back_Down_SEL[0]) / 2.0
199     center_sel_y = (Left_Front_Up_SEL[1] + Right_Back_Down_SEL[1]) / 2.0
200     center_sel_z = (Left_Front_Up_SEL[2] + Right_Back_Down_SEL[2]) / 2.0
201
202     # Main Loop
203
204     for obj, bb_world in objects:
205         matrix_world = obj.matrix_world.copy()
206         bb_world = [matrix_world * Vector(v[:]) for v in obj.bound_box]
207
208         if bb_quality and obj.type == 'MESH':
209             GBB = GlobalBB_HQ(obj)
210         else:
211             GBB = GlobalBB_LQ(bb_world)
212
213         Left_Front_Up = GBB[0]
214         Right_Back_Down = GBB[1]
215
216         center_x = (Left_Front_Up[0] + Right_Back_Down[0]) / 2.0
217         center_y = (Left_Front_Up[1] + Right_Back_Down[1]) / 2.0
218         center_z = (Left_Front_Up[2] + Right_Back_Down[2]) / 2.0
219
220         positive_x = Right_Back_Down[0]
221         positive_y = Right_Back_Down[1]
222         positive_z = Left_Front_Up[2]
223
224         negative_x = Left_Front_Up[0]
225         negative_y = Left_Front_Up[1]
226         negative_z = Right_Back_Down[2]
227
228         obj_loc = obj.location
229
230         if align_x:
231
232             # Align Mode
233
234             if relative_to == 'OPT_4':  # Active relative
235                 if align_mode == 'OPT_1':
236                     obj_x = obj_loc[0] - negative_x - size_active_x
237
238                 elif align_mode == 'OPT_3':
239                     obj_x = obj_loc[0] - positive_x + size_active_x
240
241             else:  # Everything else relative
242                 if align_mode == 'OPT_1':
243                     obj_x = obj_loc[0] - negative_x
244
245                 elif align_mode == 'OPT_3':
246                     obj_x = obj_loc[0] - positive_x
247
248             if align_mode == 'OPT_2':  # All relative
249                 obj_x = obj_loc[0] - center_x
250
251             # Relative To
252
253             if relative_to == 'OPT_1':
254                 loc_x = obj_x
255
256             elif relative_to == 'OPT_2':
257                 loc_x = obj_x + cursor[0]
258
259             elif relative_to == 'OPT_3':
260                 loc_x = obj_x + center_sel_x
261
262             elif relative_to == 'OPT_4':
263                 loc_x = obj_x + center_active_x
264
265             obj.location[0] = loc_x
266
267         if align_y:
268             # Align Mode
269
270             if relative_to == 'OPT_4':  # Active relative
271                 if align_mode == 'OPT_1':
272                     obj_y = obj_loc[1] - negative_y - size_active_y
273
274                 elif align_mode == 'OPT_3':
275                     obj_y = obj_loc[1] - positive_y + size_active_y
276
277             else:  # Everything else relative
278                 if align_mode == 'OPT_1':
279                     obj_y = obj_loc[1] - negative_y
280
281                 elif align_mode == 'OPT_3':
282                     obj_y = obj_loc[1] - positive_y
283
284             if align_mode == 'OPT_2':  # All relative
285                 obj_y = obj_loc[1] - center_y
286
287             # Relative To
288
289             if relative_to == 'OPT_1':
290                 loc_y = obj_y
291
292             elif relative_to == 'OPT_2':
293                 loc_y = obj_y + cursor[1]
294
295             elif relative_to == 'OPT_3':
296                 loc_y = obj_y + center_sel_y
297
298             elif relative_to == 'OPT_4':
299                 loc_y = obj_y + center_active_y
300
301             obj.location[1] = loc_y
302
303         if align_z:
304             # Align Mode
305             if relative_to == 'OPT_4':  # Active relative
306                 if align_mode == 'OPT_1':
307                     obj_z = obj_loc[2] - negative_z - size_active_z
308
309                 elif align_mode == 'OPT_3':
310                     obj_z = obj_loc[2] - positive_z + size_active_z
311
312             else:  # Everything else relative
313                 if align_mode == 'OPT_1':
314                     obj_z = obj_loc[2] - negative_z
315
316                 elif align_mode == 'OPT_3':
317                     obj_z = obj_loc[2] - positive_z
318
319             if align_mode == 'OPT_2':  # All relative
320                 obj_z = obj_loc[2] - center_z
321
322             # Relative To
323
324             if relative_to == 'OPT_1':
325                 loc_z = obj_z
326
327             elif relative_to == 'OPT_2':
328                 loc_z = obj_z + cursor[2]
329
330             elif relative_to == 'OPT_3':
331                 loc_z = obj_z + center_sel_z
332
333             elif relative_to == 'OPT_4':
334                 loc_z = obj_z + center_active_z
335
336             obj.location[2] = loc_z
337
338     return True
339
340
341 from bpy.props import EnumProperty, BoolProperty
342
343
344 class AlignObjects(Operator):
345     """Align Objects"""
346     bl_idname = "object.align"
347     bl_label = "Align Objects"
348     bl_options = {'REGISTER', 'UNDO'}
349
350     bb_quality = BoolProperty(
351             name="High Quality",
352             description=("Enables high quality calculation of the "
353                          "bounding box for perfect results on complex "
354                          "shape meshes with rotation/scale (Slow)"),
355             default=True,
356             )
357     align_mode = EnumProperty(
358             name="Align Mode:",
359             items=(('OPT_1', "Negative Sides", ""),
360                    ('OPT_2', "Centers", ""),
361                    ('OPT_3', "Positive Sides", ""),
362                    ),
363             default='OPT_2',
364             )
365     relative_to = EnumProperty(
366             name="Relative To:",
367             items=(('OPT_1', "Scene Origin", ""),
368                    ('OPT_2', "3D Cursor", ""),
369                    ('OPT_3', "Selection", ""),
370                    ('OPT_4', "Active", ""),
371                    ),
372             default='OPT_4',
373             )
374     align_axis = EnumProperty(
375             name="Align",
376             description="Align to axis",
377             items=(('X', "X", ""),
378                    ('Y', "Y", ""),
379                    ('Z', "Z", ""),
380                    ),
381             options={'ENUM_FLAG'},
382             )
383
384     @classmethod
385     def poll(cls, context):
386         return context.mode == 'OBJECT'
387
388     def execute(self, context):
389         align_axis = self.align_axis
390         ret = align_objects(context,
391                             'X' in align_axis,
392                             'Y' in align_axis,
393                             'Z' in align_axis,
394                             self.align_mode,
395                             self.relative_to,
396                             self.bb_quality)
397
398         if not ret:
399             self.report({'WARNING'}, "No objects with bound-box selected")
400             return {'CANCELLED'}
401         else:
402             return {'FINISHED'}