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