add_camera_rigs: refactor and cleanup
[blender-addons.git] / space_view3d_align_tools.py
blob6ae21e0b550e4d9179b55b507b46059abce55525
1 # -*- coding: utf-8 -*-
2 # ##### BEGIN GPL LICENSE BLOCK #####
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ##### END GPL LICENSE BLOCK #####
19 # Contributed to by gabhead, Lell, Anfeo, meta-androcto
21 bl_info = {
22 "name": "Align Tools",
23 "author": "gabhead, Lell, Anfeo",
24 "version": (0, 3, 4),
25 "blender": (2, 80, 0),
26 "location": "View3D > Sidebar > Item Tab",
27 "description": "Align Selected Objects to Active Object",
28 "warning": "",
29 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
30 "object/align_tools.html",
31 "category": "Object",
34 import bpy
35 from bpy.types import (
36 Operator,
37 Panel,
38 AddonPreferences,
40 from bpy.props import (
41 EnumProperty,
42 BoolProperty,
43 FloatVectorProperty,
44 StringProperty,
46 from mathutils import (
47 Vector,
48 Matrix,
52 # Simple Align Defs #
54 # Align all
55 def main(context):
56 for i in bpy.context.selected_objects:
57 i.matrix_world.translation = bpy.context.active_object.matrix_world.translation.copy()
58 i.rotation_euler = bpy.context.active_object.rotation_euler
61 # Align Location
62 def LocAll(context):
63 for i in bpy.context.selected_objects:
64 i.matrix_world.translation = bpy.context.active_object.matrix_world.translation.copy()
67 def LocX(context):
68 for i in bpy.context.selected_objects:
69 i.matrix_world.translation.x = bpy.context.active_object.matrix_world.translation.x
72 def LocY(context):
73 for i in bpy.context.selected_objects:
74 i.matrix_world.translation.y = bpy.context.active_object.matrix_world.translation.y
77 def LocZ(context):
78 for i in bpy.context.selected_objects:
79 i.matrix_world.translation.z = bpy.context.active_object.matrix_world.translation.z
82 # Align Rotation
83 def RotAll(context):
84 for i in bpy.context.selected_objects:
85 i.rotation_euler = bpy.context.active_object.rotation_euler
88 def RotX(context):
89 for i in bpy.context.selected_objects:
90 i.rotation_euler.x = bpy.context.active_object.rotation_euler.x
93 def RotY(context):
94 for i in bpy.context.selected_objects:
95 i.rotation_euler.y = bpy.context.active_object.rotation_euler.y
98 def RotZ(context):
99 for i in bpy.context.selected_objects:
100 i.rotation_euler.z = bpy.context.active_object.rotation_euler.z
103 # Align Scale
104 def ScaleAll(context):
105 for i in bpy.context.selected_objects:
106 i.scale = bpy.context.active_object.scale
109 def ScaleX(context):
110 for i in bpy.context.selected_objects:
111 i.scale.x = bpy.context.active_object.scale.x
114 def ScaleY(context):
115 for i in bpy.context.selected_objects:
116 i.scale.y = bpy.context.active_object.scale.y
119 def ScaleZ(context):
120 for i in bpy.context.selected_objects:
121 i.scale.z = bpy.context.active_object.scale.z
124 # Advanced Align Defs #
126 # subject to object 0, 1 and 2 to pivot for cursor
127 def align_function(subject, active_too, consistent, self_or_active, loc_x, loc_y, loc_z, ref1, ref2, loc_offset,
128 rot_x, rot_y, rot_z, rot_offset, scale_x, scale_y, scale_z, scale_offset,
129 fit_x, fit_y, fit_z):
131 sel_obj = bpy.context.selected_objects
132 act_obj = bpy.context.active_object
134 global sel_max
135 global sel_min
136 global sel_center
137 global ref2_co
139 def get_reference_points(obj, space):
141 me = obj.data
142 co_list = []
143 # let's get all the points coordinates
144 if space == "global":
145 ok = False
146 obj_mtx = obj.matrix_world
147 if obj.type == 'MESH' and len(me.vertices) > 0:
148 ok = True
149 for p in me.vertices:
150 co_list.append((obj_mtx @ p.co))
152 elif obj.type == 'SURFACE' and len(me.splines) > 0:
153 ok = True
154 for s in me.splines:
155 for p in s.points:
156 co_list.append((obj_mtx @ p.co))
157 elif obj.type == 'FONT' and len(me.splines) > 0:
158 ok = True
159 for s in me.splines:
160 for p in s.bezier_points:
161 co_list.append((obj_mtx @ p.co))
163 elif space == "local":
164 ok = False
165 if obj.type == 'MESH' and len(me.vertices) > 0:
166 ok = True
167 for p in me.vertices:
168 co_list.append(p.co)
170 elif obj.type == 'SURFACE' and len(me.splines) > 0:
171 ok = True
172 for s in me.splines:
173 for p in s.points:
174 co_list.append(p.co)
175 elif obj.type == 'FONT' and len(obj.data.splines) > 0:
176 ok = True
177 for s in me.splines:
178 for p in s.bezier_points:
179 co_list.append(p.co)
181 # if a valid point found
182 # proceed to calculate the extremes
183 if ok:
184 max_x = co_list[0][0]
185 min_x = co_list[0][0]
186 max_y = co_list[0][1]
187 min_y = co_list[0][1]
188 max_z = co_list[0][2]
189 min_z = co_list[0][2]
191 for v in co_list:
192 # the strings of the list compared with the smaller and more found
193 # in order to find the minor and major for each axis
194 act_x = v[0]
195 if act_x > max_x:
196 max_x = act_x
197 if act_x < min_x:
198 min_x = act_x
200 act_y = v[1]
201 if act_y > max_y:
202 max_y = act_y
203 if act_y < min_y:
204 min_y = act_y
206 act_z = v[2]
207 if act_z > max_z:
208 max_z = act_z
209 if act_z < min_z:
210 min_z = act_z
212 else:
213 # otherwise use the pivot object
214 a = obj.matrix_world.translation
215 min_x = a[0]
216 max_x = a[0]
217 min_y = a[1]
218 max_y = a[1]
219 min_z = a[2]
220 max_z = a[2]
222 center_x = min_x + ((max_x - min_x) / 2)
223 center_y = min_y + ((max_y - min_y) / 2)
224 center_z = min_z + ((max_z - min_z) / 2)
226 reference_points = [min_x, center_x, max_x, min_y, center_y, max_y, min_z, center_z, max_z]
227 return reference_points
229 def get_sel_ref(ref_co, sel_obj): # I look for the selection end points
231 sel_min = ref_co.copy()
232 sel_max = ref_co.copy()
234 for obj in sel_obj:
235 if obj != act_obj or (active_too and obj == act_obj):
237 ref_points = get_reference_points(obj, "global")
238 ref_min = Vector([ref_points[0], ref_points[3], ref_points[6]])
239 ref_max = Vector([ref_points[2], ref_points[5], ref_points[8]])
241 if ref_min[0] < sel_min[0]:
242 sel_min[0] = ref_min[0]
243 if ref_max[0] > sel_max[0]:
244 sel_max[0] = ref_max[0]
245 if ref_min[1] < sel_min[1]:
246 sel_min[1] = ref_min[1]
247 if ref_max[1] > sel_max[1]:
248 sel_max[1] = ref_max[1]
249 if ref_min[2] < sel_min[2]:
250 sel_min[2] = ref_min[2]
251 if ref_max[2] > sel_max[2]:
252 sel_max[2] = ref_max[2]
254 return sel_min, sel_max
256 def find_ref2_co(act_obj):
257 # It contains the coordinates of the reference point for the positioning
258 if ref2 == "0":
259 ref_points = get_reference_points(act_obj, "global")
260 ref2_co = [ref_points[0], ref_points[3], ref_points[6]]
261 ref2_co = Vector(ref2_co)
262 elif ref2 == "1":
263 ref_points = get_reference_points(act_obj, "global")
264 ref2_co = [ref_points[1], ref_points[4], ref_points[7]]
265 ref2_co = Vector(ref2_co)
266 elif ref2 == "2":
267 ref2_co = act_obj.location
268 ref2_co = Vector(ref2_co)
269 elif ref2 == "3":
270 ref_points = get_reference_points(act_obj, "global")
271 ref2_co = [ref_points[2], ref_points[5], ref_points[8]]
272 ref2_co = Vector(ref2_co)
273 elif ref2 == "4":
274 ref2_co = bpy.context.scene.cursor.location
276 return ref2_co
278 def find_new_coord(obj):
280 ref_points = get_reference_points(obj, "global")
282 if loc_x is True:
283 if ref1 == "0":
284 min_x = ref_points[0]
285 new_x = ref2_co[0] + (obj.location[0] - min_x) + loc_offset[0]
286 elif ref1 == "1":
287 center_x = ref_points[1]
288 new_x = ref2_co[0] + (obj.location[0] - center_x) + loc_offset[0]
289 elif ref1 == "2":
290 new_x = ref2_co[0] + loc_offset[0]
291 elif ref1 == "3":
292 max_x = ref_points[2]
293 new_x = ref2_co[0] - (max_x - obj.location[0]) + loc_offset[0]
294 obj.matrix_world.translation[0] = new_x
295 if loc_y is True:
296 if ref1 == "0":
297 min_y = ref_points[3]
298 new_y = ref2_co[1] + (obj.location[1] - min_y) + loc_offset[1]
299 elif ref1 == "1":
300 center_y = ref_points[4]
301 new_y = ref2_co[1] + (obj.location[1] - center_y) + loc_offset[1]
302 elif ref1 == "2":
303 new_y = ref2_co[1] + loc_offset[1]
304 elif ref1 == "3":
305 max_y = ref_points[5]
306 new_y = ref2_co[1] - (max_y - obj.location[1]) + loc_offset[1]
307 obj.matrix_world.translation[1] = new_y
308 if loc_z is True:
309 if ref1 == "0":
310 min_z = ref_points[6]
311 new_z = ref2_co[2] + (obj.location[2] - min_z) + loc_offset[2]
312 elif ref1 == "1":
313 center_z = ref_points[7]
314 new_z = ref2_co[2] + (obj.location[2] - center_z) + loc_offset[2]
315 elif ref1 == "2":
316 new_z = ref2_co[2] + loc_offset[2]
317 elif ref1 == "3":
318 max_z = ref_points[8]
319 new_z = ref2_co[2] - (max_z - obj.location[2]) + loc_offset[2]
320 obj.matrix_world.translation[2] = new_z
322 def find_new_rotation(obj):
323 if rot_x is True:
324 obj.rotation_euler[0] = act_obj.rotation_euler[0] + rot_offset[0]
325 if rot_y is True:
326 obj.rotation_euler[1] = act_obj.rotation_euler[1] + rot_offset[1]
327 if rot_z is True:
328 obj.rotation_euler[2] = act_obj.rotation_euler[2] + rot_offset[2]
330 def find_new_scale(obj):
331 if scale_x is True:
332 obj.scale[0] = act_obj.scale[0] + scale_offset[0]
333 if scale_y is True:
334 obj.scale[1] = act_obj.scale[1] + scale_offset[1]
335 if scale_z is True:
336 obj.scale[2] = act_obj.scale[2] + scale_offset[2]
338 def find_new_dimensions(obj, ref_dim):
339 ref_points = get_reference_points(obj, "local")
340 if fit_x:
341 dim = ref_points[2] - ref_points[0]
342 obj.scale[0] = (ref_dim[0] / dim) * act_obj.scale[0]
343 if fit_y:
344 dim = ref_points[5] - ref_points[3]
345 obj.scale[1] = (ref_dim[1] / dim) * act_obj.scale[1]
346 if fit_z:
347 dim = ref_points[8] - ref_points[6]
348 obj.scale[2] = (ref_dim[2] / dim) * act_obj.scale[2]
350 def move_pivot(obj):
351 me = obj.data
352 vec_ref2_co = Vector(ref2_co)
353 offset = vec_ref2_co - obj.location
354 offset_x = [offset[0] + loc_offset[0], 0, 0]
355 offset_y = [0, offset[1] + loc_offset[1], 0]
356 offset_z = [0, 0, offset[2] + loc_offset[2]]
358 def movement(vec):
359 obj_mtx = obj.matrix_world.copy()
360 # What's the displacement vector for the pivot?
361 move_pivot = Vector(vec)
363 # Move the pivot point (which is the object's location)
364 pivot = obj.location
365 pivot += move_pivot
367 nm = obj_mtx.inverted() @ Matrix.Translation(-move_pivot) @ obj_mtx
369 # Transform the mesh now
370 me.transform(nm)
372 if loc_x:
373 movement(offset_x)
374 if loc_y:
375 movement(offset_y)
376 if loc_z:
377 movement(offset_z)
379 def point_in_selection(act_obj, sel_obj):
380 ok = False
381 for o in sel_obj:
382 if o != act_obj:
383 ref_ob = o
384 obj_mtx = o.matrix_world
385 if o.type == 'MESH' and len(o.data.vertices) > 0:
386 ref_co = o.data.vertices[0].co.copy()
387 ref_co = obj_mtx @ ref_co
388 ok = True
389 break
390 elif o.type == 'CURVE' and len(o.data.splines) > 0:
391 ref_co = o.data.splines[0].bezier_point[0].co.copy()
392 ref_co = obj_mtx @ ref_co
393 ok = True
394 break
395 elif o.type == 'SURFACE' and len(o.data.splines) > 0:
396 ref_co = o.data.splines[0].points[0].co.copy()
397 ref_co = obj_mtx @ ref_co
398 ok = True
399 break
400 elif o.type == 'FONT' and len(o.data.splines) > 0:
401 ref_co = o.data.splines[0].bezier_points[0].co.copy()
402 ref_co = obj_mtx @ ref_co
403 ok = True
404 break
405 # if no object had data, use the position of an object that was not active as an internal
406 # point of selection
407 if ok is False:
408 ref_co = ref_ob.matrix_world.translation
410 return ref_co
412 if subject == "0":
413 # if act_obj.type == ('MESH' or 'FONT' or 'CURVE' or 'SURFACE'):
414 if act_obj.type == 'MESH' or act_obj.type == 'FONT' or act_obj.type == 'SURFACE':
415 ref2_co = find_ref2_co(act_obj)
416 else:
417 if ref2 == "4":
418 ref2_co = bpy.context.scene.cursor.location
419 else:
420 ref2_co = act_obj.matrix_world.translation
422 # in the case of substantial selection
423 if consistent:
424 # I am seeking a point that is in the selection space
425 ref_co = point_in_selection(act_obj, sel_obj)
427 sel_min, sel_max = get_sel_ref(ref_co, sel_obj)
429 sel_center = sel_min + ((sel_max - sel_min) / 2)
430 translate = [0, 0, 0]
432 # calculating how much to move the selection
433 if ref1 == "0":
434 translate = ref2_co - sel_min + loc_offset
435 elif ref1 == "1":
436 translate = ref2_co - sel_center + loc_offset
437 elif ref1 == "3":
438 translate = ref2_co - sel_max + loc_offset
440 # Move the various objects
441 for obj in sel_obj:
443 if obj != act_obj or (active_too and obj == act_obj):
445 if loc_x:
446 obj.location[0] += translate[0]
447 if loc_y:
448 obj.location[1] += translate[1]
449 if loc_z:
450 obj.location[2] += translate[2]
451 else: # not consistent
452 for obj in sel_obj:
453 if obj != act_obj:
454 if rot_x or rot_y or rot_z:
455 find_new_rotation(obj)
457 if fit_x or fit_y or fit_z:
458 dim = [0, 0, 0]
459 ref_points = get_reference_points(act_obj, "local")
460 dim[0] = ref_points[2] - ref_points[0]
461 dim[1] = ref_points[5] - ref_points[3]
462 dim[2] = ref_points[8] - ref_points[6]
463 find_new_dimensions(obj, dim)
465 if scale_x or scale_y or scale_z:
466 find_new_scale(obj)
468 if loc_x or loc_y or loc_z:
469 # print("ehy", ref2_co)
470 find_new_coord(obj)
472 if active_too is True:
473 if loc_x or loc_y or loc_z:
474 find_new_coord(act_obj)
475 if rot_x or rot_y or rot_z:
476 find_new_rotation(act_obj)
477 if scale_x or scale_y or scale_z:
478 find_new_scale(act_obj)
479 # add dimensions if dim offset will be added
481 elif subject == "1":
482 if self_or_active == "1":
483 if act_obj.type == 'MESH':
484 ref2_co = find_ref2_co(act_obj)
485 for obj in sel_obj:
486 if self_or_active == "0":
487 ref2_co = find_ref2_co(obj)
488 if loc_x or loc_y or loc_z:
489 if obj != act_obj and obj.type == 'MESH':
490 move_pivot(obj)
492 if active_too is True:
493 if act_obj.type == 'MESH':
494 if loc_x or loc_y or loc_z:
495 if self_or_active == "0":
496 ref2_co = find_ref2_co(act_obj)
497 move_pivot(act_obj)
499 elif subject == "2":
500 if self_or_active == "1":
501 if act_obj.type == 'MESH' or act_obj.type == 'FONT' or act_obj.type == 'SURFACE':
502 ref2_co = find_ref2_co(act_obj)
503 ref_points = get_reference_points(act_obj, "global")
504 else:
505 ref2_co = act_obj.matrix_world.translation
506 ref_points = [ref2_co[0], ref2_co[0], ref2_co[0],
507 ref2_co[1], ref2_co[1], ref2_co[1],
508 ref2_co[2], ref2_co[2], ref2_co[2]]
510 if ref2 == "0":
511 if loc_x is True:
512 bpy.context.scene.cursor.location[0] = ref_points[0] + loc_offset[0]
513 if loc_y is True:
514 bpy.context.scene.cursor.location[1] = ref_points[3] + loc_offset[1]
515 if loc_z is True:
516 bpy.context.scene.cursor.location[2] = ref_points[6] + loc_offset[2]
517 elif ref2 == "1":
518 if loc_x is True:
519 bpy.context.scene.cursor.location[0] = ref_points[1] + loc_offset[0]
520 if loc_y is True:
521 bpy.context.scene.cursor.location[1] = ref_points[4] + loc_offset[1]
522 if loc_z is True:
523 bpy.context.scene.cursor.location[2] = ref_points[7] + loc_offset[2]
524 elif ref2 == "2":
525 if loc_x is True:
526 bpy.context.scene.cursor.location[0] = act_obj.location[0] + loc_offset[0]
527 if loc_y is True:
528 bpy.context.scene.cursor.location[1] = act_obj.location[1] + loc_offset[1]
529 if loc_z is True:
530 bpy.context.scene.cursor.location[2] = act_obj.location[2] + loc_offset[2]
531 elif ref2 == "3":
532 if loc_x is True:
533 bpy.context.scene.cursor.location[0] = ref_points[2] + loc_offset[0]
534 if loc_y is True:
535 bpy.context.scene.cursor.location[1] = ref_points[5] + loc_offset[1]
536 if loc_z is True:
537 bpy.context.scene.cursor.location[2] = ref_points[8] + loc_offset[2]
538 elif self_or_active == "2":
539 ref_co = point_in_selection(act_obj, sel_obj)
541 sel_min, sel_max = get_sel_ref(ref_co, sel_obj)
542 sel_center = sel_min + ((sel_max - sel_min) / 2)
544 if ref2 == "0":
545 if loc_x is True:
546 bpy.context.scene.cursor.location[0] = sel_min[0] + loc_offset[0]
547 if loc_y is True:
548 bpy.context.scene.cursor.location[1] = sel_min[1] + loc_offset[1]
549 if loc_z is True:
550 bpy.context.scene.cursor.location[2] = sel_min[2] + loc_offset[2]
551 elif ref2 == "1":
552 if loc_x is True:
553 bpy.context.scene.cursor.location[0] = sel_center[0] + loc_offset[0]
554 if loc_y is True:
555 bpy.context.scene.cursor.location[1] = sel_center[1] + loc_offset[1]
556 if loc_z is True:
557 bpy.context.scene.cursor.location[2] = sel_center[2] + loc_offset[2]
558 elif ref2 == "3":
559 if loc_x is True:
560 bpy.context.scene.cursor.location[0] = sel_max[0] + loc_offset[0]
561 if loc_y is True:
562 bpy.context.scene.cursor.location[1] = sel_max[1] + loc_offset[1]
563 if loc_z is True:
564 bpy.context.scene.cursor.location[2] = sel_max[2] + loc_offset[2]
567 # Classes #
569 # Advanced Align
570 class OBJECT_OT_align_tools(Operator):
571 bl_idname = "object.align_tools"
572 bl_label = "Align Operator"
573 bl_description = "Align Object Tools"
574 bl_options = {'REGISTER', 'UNDO'}
576 # property definitions
578 # Object-Pivot-Cursor:
579 subject: EnumProperty(
580 items=(("0", "Object", "Align Objects"),
581 ("1", "Pivot", "Align Objects Pivot"),
582 ("2", "Cursor", "Align Cursor To Active")),
583 name="Align To",
584 description="What will be moved"
586 # Move active Too:
587 active_too: BoolProperty(
588 name="Active too",
589 default=False,
590 description="Move the active object too"
592 # advanced options
593 advanced: BoolProperty(
594 name="Advanced Options",
595 default=False,
596 description="Show advanced options"
598 consistent: BoolProperty(
599 name="Consistent Selection",
600 default=False,
601 description="Use consistent selection"
603 # Align Location:
604 loc_x: BoolProperty(
605 name="Align to X axis",
606 default=False,
607 description="Enable X axis alignment"
609 loc_y: BoolProperty(
610 name="Align to Y axis",
611 default=False,
612 description="Enable Y axis alignment"
614 loc_z: BoolProperty(
615 name="Align to Z axis",
616 default=False,
617 description="Enable Z axis alignment"
619 # Selection Option:
620 ref1: EnumProperty(
621 items=(("3", "Max", "Align the maximum point"),
622 ("1", "Center", "Align the center point"),
623 ("2", "Pivot", "Align the pivot"),
624 ("0", "Min", "Align the minimum point")),
625 name="Selection reference",
626 description="Moved objects reference point"
628 # Active Object Option:
629 ref2: EnumProperty(
630 items=(("3", "Max", "Align to the maximum point"),
631 ("1", "Center", "Align to the center point"),
632 ("2", "Pivot", "Align to the pivot"),
633 ("0", "Min", "Align to the minimum point"),
634 ("4", "Cursor", "Description")),
635 name="Active reference",
636 description="Destination point"
638 self_or_active: EnumProperty(
639 items=(("0", "Self", "In relation of itself"),
640 ("1", "Active", "In relation of the active object"),
641 ("2", "Selection", "In relation of the entire selection")),
642 name="Relation",
643 default="1",
644 description="To what the pivot will be aligned"
646 # Location Offset
647 loc_offset: FloatVectorProperty(
648 name="Location Offset",
649 description="Offset for location align position",
650 default=(0.0, 0.0, 0.0),
651 subtype='XYZ', size=3
653 # Rotation Offset
654 rot_offset: FloatVectorProperty(
655 name="Rotation Offset",
656 description="Offset for rotation alignment",
657 default=(0.0, 0.0, 0.0),
658 subtype='EULER', size=3
660 # Scale Offset
661 scale_offset: FloatVectorProperty(
662 name="Scale Offset",
663 description="Offset for scale match",
664 default=(0.0, 0.0, 0.0),
665 subtype='XYZ', size=3
667 # Fit Dimension Prop:
668 fit_x: BoolProperty(
669 name="Fit Dimension to X axis",
670 default=False,
671 description=""
673 fit_y: BoolProperty(
674 name="Fit Dimension to Y axis",
675 default=False,
676 description=""
678 fit_z: BoolProperty(
679 name="Fit Dimension to Z axis",
680 default=False,
681 description=""
683 # Apply Fit Dimension:
684 apply_dim: BoolProperty(
685 name="Apply Dimension",
686 default=False,
687 description=""
689 # Align Rot Prop:
690 rot_x: BoolProperty(
691 name="Align Rotation to X axis",
692 default=False,
693 description=""
695 rot_y: BoolProperty(
696 name="Align Rotation to Y axis",
697 default=False,
698 description=""
700 rot_z: BoolProperty(
701 name="Align Rotation to Z axis",
702 default=False,
703 description=""
705 # Apply Rot:
706 apply_rot: BoolProperty(
707 name="Apply Rotation",
708 default=False,
709 description=""
711 # Align Scale:
712 scale_x: BoolProperty(
713 name="Match Scale to X axis",
714 default=False,
715 description=""
717 scale_y: BoolProperty(
718 name="Match Scale to Y axis",
719 default=False,
720 description=""
722 scale_z: BoolProperty(
723 name="match Scale to Z axis",
724 default=False,
725 description=""
727 # Apply Scale:
728 apply_scale: BoolProperty(
729 name="Apply Scale",
730 default=False,
731 description=""
734 def draw(self, context):
735 layout = self.layout
736 obj = context.object
737 row = layout.row()
738 row.label(text="Active object is: ", icon='OBJECT_DATA')
739 box = layout.box()
740 box.label(text=obj.name, icon='EDITMODE_HLT')
741 # Object-Pivot-Cursor:
742 row0 = layout.row()
743 row0.prop(self, 'subject', expand=True)
745 # Move active Too:
746 row1 = layout.row()
747 row1.prop(self, 'active_too')
748 row1.prop(self, 'advanced')
749 if self.advanced:
750 row1b = layout.row()
751 row1b.prop(self, 'consistent')
753 row2 = layout.row()
754 row2.label(text="Align Location:")
756 # Align Location:
757 row3 = layout.row()
758 row3.prop(self, "loc_x", text="X", toggle=True)
759 row3.prop(self, "loc_y", text="Y", toggle=True)
760 row3.prop(self, "loc_z", text="Z", toggle=True)
762 # Offset:
763 if self.advanced is True:
764 # row8 = col.row()
765 # row8.label(text='Location Offset')
766 row9 = layout.row()
767 row9.prop(self, 'loc_offset', text='')
769 # Selection Options
770 if self.advanced is True:
771 sel = bpy.context.selected_objects
772 sel_obs = len(sel)
773 if sel_obs != 0:
774 row4 = layout.row()
775 row4.label(text="Selected: " + str(sel_obs) + " Objects", icon='OBJECT_DATA')
776 if self.subject == "1" or self.subject == "2":
777 row5b = layout.row()
778 row5b.prop(self, 'self_or_active', expand=True)
779 else:
780 row5 = layout.row()
781 row5.prop(self, 'ref1', expand=True)
783 # Active Object Options: Number of select objects
784 act = bpy.context.active_object
786 if self.advanced is True:
787 if act:
788 row6 = layout.row()
789 row6.label(text="Active: " + act.name, icon='OBJECT_DATA')
790 row7 = layout.row()
791 row7.prop(self, 'ref2', expand=True)
793 if self.subject == "0":
794 row12 = layout.row()
795 row12.label(text='Align Rotation:')
796 row13 = layout.row(align=True)
797 row13.prop(self, 'rot_x', text='X', toggle=True)
798 row13.prop(self, 'rot_y', text='Y', toggle=True)
799 row13.prop(self, 'rot_z', text='Z', toggle=True)
800 row13.prop(self, 'apply_rot', text='Apply', toggle=True)
801 if self.advanced is True:
802 row13b = layout.row()
803 row13b.prop(self, 'rot_offset', text='')
805 row14 = layout.row()
806 row14.label(text='Match Scale:')
807 row15 = layout.row(align=True)
808 row15.prop(self, 'scale_x', text='X', toggle=True)
809 row15.prop(self, 'scale_y', text='Y', toggle=True)
810 row15.prop(self, 'scale_z', text='Z', toggle=True)
811 row15.prop(self, 'apply_scale', text='Apply', toggle=True)
812 if self.advanced is True:
813 row15b = layout.row()
814 row15b.prop(self, 'scale_offset', text='')
816 row10 = layout.row()
817 row10.label(text='Fit Dimensions:')
818 row11 = layout.row(align=True)
819 row11.prop(self, 'fit_x', text='X', toggle=True)
820 row11.prop(self, 'fit_y', text='Y', toggle=True)
821 row11.prop(self, 'fit_z', text='Z', toggle=True)
822 row11.prop(self, 'apply_dim', text='Apply', toggle=True)
824 def execute(self, context):
825 align_function(
826 self.subject, self.active_too, self.consistent,
827 self.self_or_active, self.loc_x, self.loc_y, self.loc_z,
828 self.ref1, self.ref2, self.loc_offset,
829 self.rot_x, self.rot_y, self.rot_z, self.rot_offset,
830 self.scale_x, self.scale_y, self.scale_z, self.scale_offset,
831 self.fit_x, self.fit_y, self.fit_z
834 return {'FINISHED'}
837 # Simple Align Classes #
839 # Align All Rotation And Location
840 class OBJECT_OT_AlignOperator(Operator):
841 bl_idname = "object.align"
842 bl_label = "Align Selected To Active"
843 bl_description = "Align Selected To Active"
845 @classmethod
846 def poll(cls, context):
847 return context.active_object is not None
849 def execute(self, context):
850 main(context)
851 return {'FINISHED'}
854 # Align Location All
855 class OBJECT_OT_AlignLocationOperator(Operator):
856 bl_idname = "object.align_location_all"
857 bl_label = "Align Selected Location To Active"
858 bl_description = "Align Selected Location To Active"
860 @classmethod
861 def poll(cls, context):
862 return context.active_object is not None
864 def execute(self, context):
865 LocAll(context)
866 return {'FINISHED'}
869 # Align Location X
870 class OBJECT_OT_AlignLocationXOperator(Operator):
871 bl_idname = "object.align_location_x"
872 bl_label = "Align Selected Location X To Active"
873 bl_description = "Align Selected Location X To Active"
875 @classmethod
876 def poll(cls, context):
877 return context.active_object is not None
879 def execute(self, context):
880 LocX(context)
881 return {'FINISHED'}
884 # Align Location Y
885 class OBJECT_OT_AlignLocationYOperator(Operator):
886 bl_idname = "object.align_location_y"
887 bl_label = "Align Selected Location Y To Active"
888 bl_description = "Align Selected Location Y To Active"
890 @classmethod
891 def poll(cls, context):
892 return context.active_object is not None
894 def execute(self, context):
895 LocY(context)
896 return {'FINISHED'}
899 # Align LocationZ
900 class OBJECT_OT_AlignLocationZOperator(Operator):
901 bl_idname = "object.align_location_z"
902 bl_label = "Align Selected Location Z To Active"
903 bl_description = "Align Selected Location Z To Active"
905 @classmethod
906 def poll(cls, context):
907 return context.active_object is not None
909 def execute(self, context):
910 LocZ(context)
911 return {'FINISHED'}
914 # Align Rotation All
915 class OBJECT_OT_AlignRotationOperator(Operator):
916 bl_idname = "object.align_rotation_all"
917 bl_label = "Align Selected Rotation To Active"
918 bl_description = "Align Selected Rotation To Active"
920 @classmethod
921 def poll(cls, context):
922 return context.active_object is not None
924 def execute(self, context):
925 RotAll(context)
926 return {'FINISHED'}
929 # Align Rotation X
930 class OBJECT_OT_AlignRotationXOperator(Operator):
931 bl_idname = "object.align_rotation_x"
932 bl_label = "Align Selected Rotation X To Active"
933 bl_description = "Align Selected Rotation X To Active"
935 @classmethod
936 def poll(cls, context):
937 return context.active_object is not None
939 def execute(self, context):
940 RotX(context)
941 return {'FINISHED'}
944 # Align Rotation Y
945 class OBJECT_OT_AlignRotationYOperator(Operator):
946 bl_idname = "object.align_rotation_y"
947 bl_label = "Align Selected Rotation Y To Active"
948 bl_description = "Align Selected Rotation Y To Active"
950 @classmethod
951 def poll(cls, context):
952 return context.active_object is not None
954 def execute(self, context):
955 RotY(context)
956 return {'FINISHED'}
959 # Align Rotation Z
960 class OBJECT_OT_AlignRotationZOperator(Operator):
961 bl_idname = "object.align_rotation_z"
962 bl_label = "Align Selected Rotation Z To Active"
963 bl_description = "Align Selected Rotation Z To Active"
965 @classmethod
966 def poll(cls, context):
967 return context.active_object is not None
969 def execute(self, context):
970 RotZ(context)
971 return {'FINISHED'}
974 # Scale All
975 class OBJECT_OT_AlignScaleOperator(Operator):
976 bl_idname = "object.align_objects_scale_all"
977 bl_label = "Align Selected Scale To Active"
978 bl_description = "Align Selected Scale To Active"
980 @classmethod
981 def poll(cls, context):
982 return context.active_object is not None
984 def execute(self, context):
985 ScaleAll(context)
986 return {'FINISHED'}
989 # Align Scale X
990 class OBJECT_OT_AlignScaleXOperator(Operator):
991 bl_idname = "object.align_objects_scale_x"
992 bl_label = "Align Selected Scale X To Active"
993 bl_description = "Align Selected Scale X To Active"
995 @classmethod
996 def poll(cls, context):
997 return context.active_object is not None
999 def execute(self, context):
1000 ScaleX(context)
1001 return {'FINISHED'}
1004 # Align Scale Y
1005 class OBJECT_OT_AlignScaleYOperator(Operator):
1006 bl_idname = "object.align_objects_scale_y"
1007 bl_label = "Align Selected Scale Y To Active"
1008 bl_description = "Align Selected Scale Y To Active"
1010 @classmethod
1011 def poll(cls, context):
1012 return context.active_object is not None
1014 def execute(self, context):
1015 ScaleY(context)
1016 return {'FINISHED'}
1019 # Align Scale Z
1020 class OBJECT_OT_AlignScaleZOperator(Operator):
1021 bl_idname = "object.align_objects_scale_z"
1022 bl_label = "Align Selected Scale Z To Active"
1023 bl_description = "Align Selected Scale Z To Active"
1025 @classmethod
1026 def poll(cls, context):
1027 return context.active_object is not None
1029 def execute(self, context):
1030 ScaleZ(context)
1031 return {'FINISHED'}
1034 # Interface Panel
1036 class VIEW3D_PT_AlignUi(Panel):
1037 bl_space_type = 'VIEW_3D'
1038 bl_region_type = 'UI'
1039 bl_label = "Align Tools"
1040 bl_context = "objectmode"
1041 bl_category = 'Item'
1042 bl_options = {'DEFAULT_CLOSED'}
1044 def draw(self, context):
1045 layout = self.layout
1046 obj = context.object
1048 if obj is not None:
1049 row = layout.row()
1050 row.label(text="Active object is: ", icon='OBJECT_DATA')
1051 box = layout.box()
1052 box.label(text=obj.name, icon='EDITMODE_HLT')
1054 col = layout.column()
1055 col.label(text="Align Loc + Rot:")
1057 col = layout.column(align=False)
1058 col.operator("object.align", text="XYZ")
1060 col = layout.column()
1061 col.label(text="Align Location:")
1063 col = layout.column_flow(columns=4, align=True)
1064 col.operator("object.align_location_x", text="X")
1065 col.operator("object.align_location_y", text="Y")
1066 col.operator("object.align_location_z", text="Z")
1067 col.operator("object.align_location_all", text="All")
1069 col = layout.column()
1070 col.label(text="Align Rotation:")
1072 col = layout.column_flow(columns=4, align=True)
1073 col.operator("object.align_rotation_x", text="X")
1074 col.operator("object.align_rotation_y", text="Y")
1075 col.operator("object.align_rotation_z", text="Z")
1076 col.operator("object.align_rotation_all", text="All")
1078 col = layout.column()
1079 col.label(text="Align Scale:")
1081 col = layout.column_flow(columns=4, align=True)
1082 col.operator("object.align_objects_scale_x", text="X")
1083 col.operator("object.align_objects_scale_y", text="Y")
1084 col.operator("object.align_objects_scale_z", text="Z")
1085 col.operator("object.align_objects_scale_all", text="All")
1087 if obj is not None:
1088 col = layout.column()
1089 col.label(text="Advanced Align Operations")
1090 layout = self.layout
1091 self.layout.operator("object.align_tools", text="Advanced")
1094 # Add-ons Preferences Update Panel
1096 # Define Panel classes for updating
1097 panels = (
1098 VIEW3D_PT_AlignUi,
1102 def update_panel(self, context):
1103 message = "Align Tools: Updating Panel locations has failed"
1104 try:
1105 for panel in panels:
1106 if "bl_rna" in panel.__dict__:
1107 bpy.utils.unregister_class(panel)
1109 for panel in panels:
1110 panel.bl_category = context.preferences.addons[__name__].preferences.category
1111 bpy.utils.register_class(panel)
1113 except Exception as e:
1114 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
1115 pass
1118 class AlignAddonPreferences(AddonPreferences):
1119 # this must match the addon name, use '__package__'
1120 # when defining this in a submodule of a python package.
1121 bl_idname = __name__
1123 category: StringProperty(
1124 name="Tab Category",
1125 description="Choose a name for the category of the panel",
1126 default="Item",
1127 update=update_panel
1130 def draw(self, context):
1131 layout = self.layout
1133 row = layout.row()
1134 col = row.column()
1135 col.label(text="Tab Category:")
1136 col.prop(self, "category", text="")
1139 # Class List
1140 classes = (
1141 VIEW3D_PT_AlignUi,
1142 OBJECT_OT_AlignOperator,
1143 OBJECT_OT_AlignLocationOperator,
1144 OBJECT_OT_AlignLocationXOperator,
1145 OBJECT_OT_AlignLocationYOperator,
1146 OBJECT_OT_AlignLocationZOperator,
1147 OBJECT_OT_AlignRotationOperator,
1148 OBJECT_OT_AlignRotationXOperator,
1149 OBJECT_OT_AlignRotationYOperator,
1150 OBJECT_OT_AlignRotationZOperator,
1151 OBJECT_OT_AlignScaleOperator,
1152 OBJECT_OT_AlignScaleXOperator,
1153 OBJECT_OT_AlignScaleYOperator,
1154 OBJECT_OT_AlignScaleZOperator,
1155 OBJECT_OT_align_tools,
1156 AlignAddonPreferences,
1160 # Register all operators and panels
1161 def register():
1162 for cls in classes:
1163 bpy.utils.register_class(cls)
1166 def unregister():
1167 for cls in classes:
1168 bpy.utils.unregister_class(cls)
1171 if __name__ == "__main__":
1172 register()