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