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