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