addons-contrib: more view_layer syntax updates
[blender-addons-contrib.git] / space_view3d_align_tools.py
1 # -*- coding: utf-8 -*-
2 # ##### BEGIN GPL LICENSE BLOCK #####
3 #
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.
8 #
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.
13 #
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.
17 #
18 # ##### END GPL LICENSE BLOCK #####
19 # Contributed to by gabhead, Lell, Anfeo, meta-androcto
20
21 bl_info = {
22     "name": "Align Tools",
23     "author": "gabhead, Lell, Anfeo",
24     "version": (0, 3, 2),
25     "blender": (2, 77, 0),
26     "location": "View3D > Tool Shelf > Tools",
27     "description": "Align Selected Objects to Active Object",
28     "warning": "",
29     "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
30                 "Scripts/3D interaction/Align_Tools",
31     "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
32     "category": "3D View",
33 }
34
35 import bpy
36 from bpy.types import (
37         Operator,
38         Panel,
39         AddonPreferences,
40         )
41 from bpy.props import (
42         EnumProperty,
43         BoolProperty,
44         FloatVectorProperty,
45         StringProperty,
46         )
47 from mathutils import (
48         Vector,
49         Matrix,
50         )
51
52
53 # Simple Align Defs #
54
55 # Align all
56 def main(context):
57     for i in bpy.context.selected_objects:
58         i.location = bpy.context.active_object.location
59         i.rotation_euler = bpy.context.active_object.rotation_euler
60
61
62 # Align Location
63 def LocAll(context):
64     for i in bpy.context.selected_objects:
65         i.location = bpy.context.active_object.location
66
67
68 def LocX(context):
69     for i in bpy.context.selected_objects:
70         i.location.x = bpy.context.active_object.location.x
71
72
73 def LocY(context):
74     for i in bpy.context.selected_objects:
75         i.location.y = bpy.context.active_object.location.y
76
77
78 def LocZ(context):
79     for i in bpy.context.selected_objects:
80         i.location.z = bpy.context.active_object.location.z
81
82
83 # Align Rotation
84 def RotAll(context):
85     for i in bpy.context.selected_objects:
86         i.rotation_euler = bpy.context.active_object.rotation_euler
87
88
89 def RotX(context):
90     for i in bpy.context.selected_objects:
91         i.rotation_euler.x = bpy.context.active_object.rotation_euler.x
92
93
94 def RotY(context):
95     for i in bpy.context.selected_objects:
96         i.rotation_euler.y = bpy.context.active_object.rotation_euler.y
97
98
99 def RotZ(context):
100     for i in bpy.context.selected_objects:
101         i.rotation_euler.z = bpy.context.active_object.rotation_euler.z
102
103
104 # Align Scale
105 def ScaleAll(context):
106     for i in bpy.context.selected_objects:
107         i.scale = bpy.context.active_object.scale
108
109
110 def ScaleX(context):
111     for i in bpy.context.selected_objects:
112         i.scale.x = bpy.context.active_object.scale.x
113
114
115 def ScaleY(context):
116     for i in bpy.context.selected_objects:
117         i.scale.y = bpy.context.active_object.scale.y
118
119
120 def ScaleZ(context):
121     for i in bpy.context.selected_objects:
122         i.scale.z = bpy.context.active_object.scale.z
123
124
125 # Advanced Align Defs #
126
127 # subject to object 0, 1 and 2 to pivot for cursor
128 def align_function(subject, active_too, consistent, self_or_active, loc_x, loc_y, loc_z, ref1, ref2, loc_offset,
129                    rot_x, rot_y, rot_z, rot_offset, scale_x, scale_y, scale_z, scale_offset,
130                    fit_x, fit_y, fit_z):
131
132     sel_obj = bpy.context.selected_objects
133     act_obj = bpy.context.active_object
134
135     global sel_max
136     global sel_min
137     global sel_center
138     global ref2_co
139
140     def get_reference_points(obj, space):
141
142         me = obj.data
143         co_list = []
144         # let's get all the points coodinates
145         if space == "global":
146             ok = False
147             obj_mtx = obj.matrix_world
148             if obj.type == 'MESH' and len(me.vertices) > 0:
149                 ok = True
150                 for p in me.vertices:
151                     co_list.append((obj_mtx * p.co))
152
153             elif obj.type == 'SURFACE' and len(me.splines) > 0:
154                 ok = True
155                 for s in me.splines:
156                     for p in s.points:
157                         co_list.append((obj_mtx * p.co))
158             elif obj.type == 'FONT' and len(me.splines) > 0:
159                 ok = True
160                 for s in me.splines:
161                     for p in s.bezier_points:
162                         co_list.append((obj_mtx * p.co))
163
164         elif space == "local":
165             ok = False
166             if obj.type == 'MESH' and len(me.vertices) > 0:
167                 ok = True
168                 for p in me.vertices:
169                     co_list.append(p.co)
170
171             elif obj.type == 'SURFACE' and len(me.splines) > 0:
172                 ok = True
173                 for s in me.splines:
174                     for p in s.points:
175                         co_list.append(p.co)
176             elif obj.type == 'FONT' and len(obj.data.splines) > 0:
177                 ok = True
178                 for s in me.splines:
179                     for p in s.bezier_points:
180                         co_list.append(p.co)
181
182         # if a valid point found
183         # proceed to calculate the extremes
184         if ok:
185             max_x = co_list[0][0]
186             min_x = co_list[0][0]
187             max_y = co_list[0][1]
188             min_y = co_list[0][1]
189             max_z = co_list[0][2]
190             min_z = co_list[0][2]
191
192             for v in co_list:
193                 # the strings of the list compared with the smaller and more found
194                 # in order to find the minor and major for each axis
195                 act_x = v[0]
196                 if act_x > max_x:
197                     max_x = act_x
198                 if act_x < min_x:
199                     min_x = act_x
200
201                 act_y = v[1]
202                 if act_y > max_y:
203                     max_y = act_y
204                 if act_y < min_y:
205                     min_y = act_y
206
207                 act_z = v[2]
208                 if act_z > max_z:
209                     max_z = act_z
210                 if act_z < min_z:
211                     min_z = act_z
212
213         else:
214             # otherwise use the pivot object
215             a = obj.location
216             min_x = a[0]
217             max_x = a[0]
218             min_y = a[1]
219             max_y = a[1]
220             min_z = a[2]
221             max_z = a[2]
222
223         center_x = min_x + ((max_x - min_x) / 2)
224         center_y = min_y + ((max_y - min_y) / 2)
225         center_z = min_z + ((max_z - min_z) / 2)
226
227         reference_points = [min_x, center_x, max_x, min_y, center_y, max_y, min_z, center_z, max_z]
228         return reference_points
229
230     def get_sel_ref(ref_co, sel_obj):  # I look for the selection end points
231
232         sel_min = ref_co.copy()
233         sel_max = ref_co.copy()
234
235         for obj in sel_obj:
236             if obj != act_obj or (active_too and obj == act_obj):
237
238                 ref_points = get_reference_points(obj, "global")
239                 ref_min = Vector([ref_points[0], ref_points[3], ref_points[6]])
240                 ref_max = Vector([ref_points[2], ref_points[5], ref_points[8]])
241
242                 if ref_min[0] < sel_min[0]:
243                     sel_min[0] = ref_min[0]
244                 if ref_max[0] > sel_max[0]:
245                     sel_max[0] = ref_max[0]
246                 if ref_min[1] < sel_min[1]:
247                     sel_min[1] = ref_min[1]
248                 if ref_max[1] > sel_max[1]:
249                     sel_max[1] = ref_max[1]
250                 if ref_min[2] < sel_min[2]:
251                     sel_min[2] = ref_min[2]
252                 if ref_max[2] > sel_max[2]:
253                     sel_max[2] = ref_max[2]
254
255         return sel_min, sel_max
256
257     def find_ref2_co(act_obj):
258         # It contains the coordinates of the reference point for the positioning
259         if ref2 == "0":
260             ref_points = get_reference_points(act_obj, "global")
261             ref2_co = [ref_points[0], ref_points[3], ref_points[6]]
262             ref2_co = Vector(ref2_co)
263         elif ref2 == "1":
264             ref_points = get_reference_points(act_obj, "global")
265             ref2_co = [ref_points[1], ref_points[4], ref_points[7]]
266             ref2_co = Vector(ref2_co)
267         elif ref2 == "2":
268             ref2_co = act_obj.location
269             ref2_co = Vector(ref2_co)
270         elif ref2 == "3":
271             ref_points = get_reference_points(act_obj, "global")
272             ref2_co = [ref_points[2], ref_points[5], ref_points[8]]
273             ref2_co = Vector(ref2_co)
274         elif ref2 == "4":
275             ref2_co = bpy.context.scene.cursor_location
276
277         return ref2_co
278
279     def find_new_coord(obj):
280
281         ref_points = get_reference_points(obj, "global")
282
283         if loc_x is True:
284             if ref1 == "0":
285                 min_x = ref_points[0]
286                 new_x = ref2_co[0] + (obj.location[0] - min_x) + loc_offset[0]
287             elif ref1 == "1":
288                 center_x = ref_points[1]
289                 new_x = ref2_co[0] + (obj.location[0] - center_x) + loc_offset[0]
290             elif ref1 == "2":
291                 new_x = ref2_co[0] + loc_offset[0]
292             elif ref1 == "3":
293                 max_x = ref_points[2]
294                 new_x = ref2_co[0] - (max_x - obj.location[0]) + loc_offset[0]
295             obj.location[0] = new_x
296         if loc_y is True:
297             if ref1 == "0":
298                 min_y = ref_points[3]
299                 new_y = ref2_co[1] + (obj.location[1] - min_y) + loc_offset[1]
300             elif ref1 == "1":
301                 center_y = ref_points[4]
302                 new_y = ref2_co[1] + (obj.location[1] - center_y) + loc_offset[1]
303             elif ref1 == "2":
304                 new_y = ref2_co[1] + loc_offset[1]
305             elif ref1 == "3":
306                 max_y = ref_points[5]
307                 new_y = ref2_co[1] - (max_y - obj.location[1]) + loc_offset[1]
308             obj.location[1] = new_y
309         if loc_z is True:
310             if ref1 == "0":
311                 min_z = ref_points[6]
312                 new_z = ref2_co[2] + (obj.location[2] - min_z) + loc_offset[2]
313             elif ref1 == "1":
314                 center_z = ref_points[7]
315                 new_z = ref2_co[2] + (obj.location[2] - center_z) + loc_offset[2]
316             elif ref1 == "2":
317                 new_z = ref2_co[2] + loc_offset[2]
318             elif ref1 == "3":
319                 max_z = ref_points[8]
320                 new_z = ref2_co[2] - (max_z - obj.location[2]) + loc_offset[2]
321             obj.location[2] = new_z
322
323     def find_new_rotation(obj):
324         if rot_x is True:
325             obj.rotation_euler[0] = act_obj.rotation_euler[0] + rot_offset[0]
326         if rot_y is True:
327             obj.rotation_euler[1] = act_obj.rotation_euler[1] + rot_offset[1]
328         if rot_z is True:
329             obj.rotation_euler[2] = act_obj.rotation_euler[2] + rot_offset[2]
330
331     def find_new_scale(obj):
332         if scale_x is True:
333             obj.scale[0] = act_obj.scale[0] + scale_offset[0]
334         if scale_y is True:
335             obj.scale[1] = act_obj.scale[1] + scale_offset[1]
336         if scale_z is True:
337             obj.scale[2] = act_obj.scale[2] + scale_offset[2]
338
339     def find_new_dimensions(obj, ref_dim):
340         ref_points = get_reference_points(obj, "local")
341         if fit_x:
342             dim = ref_points[2] - ref_points[0]
343             obj.scale[0] = (ref_dim[0] / dim) * act_obj.scale[0]
344         if fit_y:
345             dim = ref_points[5] - ref_points[3]
346             obj.scale[1] = (ref_dim[1] / dim) * act_obj.scale[1]
347         if fit_z:
348             dim = ref_points[8] - ref_points[6]
349             obj.scale[2] = (ref_dim[2] / dim) * act_obj.scale[2]
350
351     def move_pivot(obj):
352         me = obj.data
353         vec_ref2_co = Vector(ref2_co)
354         offset = vec_ref2_co - obj.location
355         offset_x = [offset[0] + loc_offset[0], 0, 0]
356         offset_y = [0, offset[1] + loc_offset[1], 0]
357         offset_z = [0, 0, offset[2] + loc_offset[2]]
358
359         def movement(vec):
360             obj_mtx = obj.matrix_world.copy()
361             # What's the displacement vector for the pivot?
362             move_pivot = Vector(vec)
363
364             # Move the pivot point (which is the object's location)
365             pivot = obj.location
366             pivot += move_pivot
367
368             nm = obj_mtx.inverted() * Matrix.Translation(-move_pivot) * obj_mtx
369
370             # Transform the mesh now
371             me.transform(nm)
372
373         if loc_x:
374             movement(offset_x)
375         if loc_y:
376             movement(offset_y)
377         if loc_z:
378             movement(offset_z)
379
380     def point_in_selection(act_obj, sel_obj):
381         ok = False
382         for o in sel_obj:
383             if o != act_obj:
384                 ref_ob = o
385                 obj_mtx = o.matrix_world
386                 if o.type == 'MESH' and len(o.data.vertices) > 0:
387                     ref_co = o.data.vertices[0].co.copy()
388                     ref_co = obj_mtx * ref_co
389                     ok = True
390                     break
391                 elif o.type == 'CURVE' and len(o.data.splines) > 0:
392                     ref_co = o.data.splines[0].bezier_point[0].co.copy()
393                     ref_co = obj_mtx * ref_co
394                     ok = True
395                     break
396                 elif o.type == 'SURFACE' and len(o.data.splines) > 0:
397                     ref_co = o.data.splines[0].points[0].co.copy()
398                     ref_co = obj_mtx * ref_co
399                     ok = True
400                     break
401                 elif o.type == 'FONT' and len(o.data.splines) > 0:
402                     ref_co = o.data.splines[0].bezier_points[0].co.copy()
403                     ref_co = obj_mtx * ref_co
404                     ok = True
405                     break
406         # if no object had data, use the position of an object that was not active as an internal
407         # point of selection
408         if ok is False:
409             ref_co = ref_ob.location
410
411         return ref_co
412
413     if subject == "0":
414         # if act_obj.type == ('MESH' or 'FONT' or 'CURVE' or 'SURFACE'):
415         if act_obj.type == 'MESH' or act_obj.type == 'FONT' or act_obj.type == 'SURFACE':
416             ref2_co = find_ref2_co(act_obj)
417         else:
418             if ref2 == "4":
419                 ref2_co = bpy.context.scene.cursor_location
420             else:
421                 ref2_co = act_obj.location
422
423         # in the case of substantial selection
424         if consistent:
425             # I am seeking a point that is in the selection space
426             ref_co = point_in_selection(act_obj, sel_obj)
427
428             sel_min, sel_max = get_sel_ref(ref_co, sel_obj)
429
430             sel_center = sel_min + ((sel_max - sel_min) / 2)
431             translate = [0, 0, 0]
432
433             # calculating how much to move the selection
434             if ref1 == "0":
435                 translate = ref2_co - sel_min + loc_offset
436             elif ref1 == "1":
437                 translate = ref2_co - sel_center + loc_offset
438             elif ref1 == "3":
439                 translate = ref2_co - sel_max + loc_offset
440
441             # Move the various objects
442             for obj in sel_obj:
443
444                 if obj != act_obj or (active_too and obj == act_obj):
445
446                     if loc_x:
447                         obj.location[0] += translate[0]
448                     if loc_y:
449                         obj.location[1] += translate[1]
450                     if loc_z:
451                         obj.location[2] += translate[2]
452         else:  # not consistent
453             for obj in sel_obj:
454                 if obj != act_obj:
455                     if rot_x or rot_y or rot_z:
456                         find_new_rotation(obj)
457
458                     if fit_x or fit_y or fit_z:
459                         dim = [0, 0, 0]
460                         ref_points = get_reference_points(act_obj, "local")
461                         dim[0] = ref_points[2] - ref_points[0]
462                         dim[1] = ref_points[5] - ref_points[3]
463                         dim[2] = ref_points[8] - ref_points[6]
464                         find_new_dimensions(obj, dim)
465
466                     if scale_x or scale_y or scale_z:
467                         find_new_scale(obj)
468
469                     if loc_x or loc_y or loc_z:
470                         # print("ehy", ref2_co)
471                         find_new_coord(obj)
472
473             if active_too is True:
474                 if loc_x or loc_y or loc_z:
475                     find_new_coord(act_obj)
476                 if rot_x or rot_y or rot_z:
477                     find_new_rotation(act_obj)
478                 if scale_x or scale_y or scale_z:
479                     find_new_scale(act_obj)
480                 # add dimensions if dim offset will be added
481
482     elif subject == "1":
483         if self_or_active == "1":
484             if act_obj.type == 'MESH':
485                 ref2_co = find_ref2_co(act_obj)
486         for obj in sel_obj:
487             if self_or_active == "0":
488                 ref2_co = find_ref2_co(obj)
489             if loc_x or loc_y or loc_z:
490                 if obj != act_obj and obj.type == 'MESH':
491                     move_pivot(obj)
492
493         if active_too is True:
494             if act_obj.type == 'MESH':
495                 if loc_x or loc_y or loc_z:
496                     if self_or_active == "0":
497                         ref2_co = find_ref2_co(act_obj)
498                     move_pivot(act_obj)
499
500     elif subject == "2":
501         if self_or_active == "1":
502             if act_obj.type == 'MESH' or act_obj.type == 'FONT' or act_obj.type == 'SURFACE':
503                 ref2_co = find_ref2_co(act_obj)
504                 ref_points = get_reference_points(act_obj, "global")
505             else:
506                 ref2_co = act_obj.location
507                 ref_points = [act_obj.location[0], act_obj.location[0], act_obj.location[0],
508                               act_obj.location[1], act_obj.location[1], act_obj.location[1],
509                               act_obj.location[2], act_obj.location[2], act_obj.location[2]]
510
511             if ref2 == "0":
512                 if loc_x is True:
513                     bpy.context.scene.cursor_location[0] = ref_points[0] + loc_offset[0]
514                 if loc_y is True:
515                     bpy.context.scene.cursor_location[1] = ref_points[3] + loc_offset[1]
516                 if loc_z is True:
517                     bpy.context.scene.cursor_location[2] = ref_points[6] + loc_offset[2]
518             elif ref2 == "1":
519                 if loc_x is True:
520                     bpy.context.scene.cursor_location[0] = ref_points[1] + loc_offset[0]
521                 if loc_y is True:
522                     bpy.context.scene.cursor_location[1] = ref_points[4] + loc_offset[1]
523                 if loc_z is True:
524                     bpy.context.scene.cursor_location[2] = ref_points[7] + loc_offset[2]
525             elif ref2 == "2":
526                 if loc_x is True:
527                     bpy.context.scene.cursor_location[0] = act_obj.location[0] + loc_offset[0]
528                 if loc_y is True:
529                     bpy.context.scene.cursor_location[1] = act_obj.location[1] + loc_offset[1]
530                 if loc_z is True:
531                     bpy.context.scene.cursor_location[2] = act_obj.location[2] + loc_offset[2]
532             elif ref2 == "3":
533                 if loc_x is True:
534                     bpy.context.scene.cursor_location[0] = ref_points[2] + loc_offset[0]
535                 if loc_y is True:
536                     bpy.context.scene.cursor_location[1] = ref_points[5] + loc_offset[1]
537                 if loc_z is True:
538                     bpy.context.scene.cursor_location[2] = ref_points[8] + loc_offset[2]
539         elif self_or_active == "2":
540             ref_co = point_in_selection(act_obj, sel_obj)
541
542             sel_min, sel_max = get_sel_ref(ref_co, sel_obj)
543             sel_center = sel_min + ((sel_max - sel_min) / 2)
544
545             if ref2 == "0":
546                 if loc_x is True:
547                     bpy.context.scene.cursor_location[0] = sel_min[0] + loc_offset[0]
548                 if loc_y is True:
549                     bpy.context.scene.cursor_location[1] = sel_min[1] + loc_offset[1]
550                 if loc_z is True:
551                     bpy.context.scene.cursor_location[2] = sel_min[2] + loc_offset[2]
552             elif ref2 == "1":
553                 if loc_x is True:
554                     bpy.context.scene.cursor_location[0] = sel_center[0] + loc_offset[0]
555                 if loc_y is True:
556                     bpy.context.scene.cursor_location[1] = sel_center[1] + loc_offset[1]
557                 if loc_z is True:
558                     bpy.context.scene.cursor_location[2] = sel_center[2] + loc_offset[2]
559             elif ref2 == "3":
560                 if loc_x is True:
561                     bpy.context.scene.cursor_location[0] = sel_max[0] + loc_offset[0]
562                 if loc_y is True:
563                     bpy.context.scene.cursor_location[1] = sel_max[1] + loc_offset[1]
564                 if loc_z is True:
565                     bpy.context.scene.cursor_location[2] = sel_max[2] + loc_offset[2]
566
567
568 # Classes #
569
570 # Advanced Align
571 class OBJECT_OT_align_tools(Operator):
572     bl_idname = "object.align_tools"
573     bl_label = "Align Operator"
574     bl_description = "Align Object Tools"
575     bl_options = {'REGISTER', 'UNDO'}
576
577     # property definitions
578
579     # Object-Pivot-Cursor:
580     subject: EnumProperty(
581             items=(("0", "Object", "Align Objects"),
582                    ("1", "Pivot", "Align Objects Pivot"),
583                    ("2", "Cursor", "Align Cursor To Active")),
584             name="Align To",
585             description="What will be moved"
586             )
587     # Move active Too:
588     active_too: BoolProperty(
589             name="Active too",
590             default=False,
591             description="Move the active object too"
592             )
593     # advanced options
594     advanced: BoolProperty(
595             name="Advanced Options",
596             default=False,
597             description="Show advanced options"
598             )
599     consistent: BoolProperty(
600             name="Consistent Selection",
601             default=False,
602             description="Use consistent selection"
603             )
604     # Align Location:
605     loc_x: BoolProperty(
606             name="Align to X axis",
607             default=False,
608             description="Enable X axis alignment"
609             )
610     loc_y: BoolProperty(
611             name="Align to Y axis",
612             default=False,
613             description="Enable Y axis alignment"
614             )
615     loc_z: BoolProperty(
616             name="Align to Z axis",
617             default=False,
618             description="Enable Z axis alignment"
619             )
620     # Selection Option:
621     ref1: EnumProperty(
622             items=(("3", "Max", "Align the maximum point"),
623                    ("1", "Center", "Align the center point"),
624                    ("2", "Pivot", "Align the pivot"),
625                    ("0", "Min", "Align the minimum point")),
626             name="Selection reference",
627             description="Moved objects reference point"
628             )
629     # Active Oject Option:
630     ref2: EnumProperty(
631             items=(("3", "Max", "Align to the maximum point"),
632                    ("1", "Center", "Align to the center point"),
633                    ("2", "Pivot", "Align to the pivot"),
634                    ("0", "Min", "Align to the minimum point"),
635                    ("4", "Cursor", "Description")),
636             name="Active reference",
637             description="Destination point"
638             )
639     self_or_active: EnumProperty(
640             items=(("0", "Self", "In relation of itself"),
641                    ("1", "Active", "In relation of the active object"),
642                    ("2", "Selection", "In relation of the entire selection")),
643             name="Relation",
644             default="1",
645             description="To what the pivot will be aligned"
646             )
647     # Location Offset
648     loc_offset: FloatVectorProperty(
649             name="Location Offset",
650             description="Offset for location align position",
651             default=(0.0, 0.0, 0.0),
652             subtype='XYZ', size=3
653             )
654     # Rotation Offset
655     rot_offset: FloatVectorProperty(
656             name="Rotation Offset",
657             description="Offset for rotation alignment",
658             default=(0.0, 0.0, 0.0),
659             subtype='EULER', size=3
660             )
661     # Scale Offset
662     scale_offset: FloatVectorProperty(
663             name="Scale Offset",
664             description="Offset for scale match",
665             default=(0.0, 0.0, 0.0),
666             subtype='XYZ', size=3
667             )
668     # Fit Dimension Prop:
669     fit_x: BoolProperty(
670             name="Fit Dimension to X axis",
671             default=False,
672             description=""
673             )
674     fit_y: BoolProperty(
675             name="Fit Dimension to Y axis",
676             default=False,
677             description=""
678             )
679     fit_z: BoolProperty(
680             name="Fit Dimension to Z axis",
681             default=False,
682             description=""
683             )
684     # Apply Fit Dimension:
685     apply_dim: BoolProperty(
686             name="Apply  Dimension",
687             default=False,
688             description=""
689             )
690     # Align Rot Prop:
691     rot_x: BoolProperty(
692             name="Align Rotation to X axis",
693             default=False,
694             description=""
695             )
696     rot_y: BoolProperty(
697             name="Align Rotation to Y axis",
698             default=False,
699             description=""
700             )
701     rot_z: BoolProperty(
702             name="Align Rotation to Z axis",
703             default=False,
704             description=""
705             )
706     # Apply Rot:
707     apply_rot: BoolProperty(
708             name="Apply Rotation",
709             default=False,
710             description=""
711             )
712     # Align Scale:
713     scale_x: BoolProperty(
714             name="Match Scale to X axis",
715             default=False,
716             description=""
717             )
718     scale_y: BoolProperty(
719             name="Match Scale to Y axis",
720             default=False,
721             description=""
722             )
723     scale_z: BoolProperty(
724             name="match Scale to Z axis",
725             default=False,
726             description=""
727             )
728     # Apply Scale:
729     apply_scale: BoolProperty(
730             name="Apply Scale",
731             default=False,
732             description=""
733             )
734
735     def draw(self, context):
736         layout = self.layout
737
738         # Object-Pivot-Cursor:
739         row0 = layout.row()
740         row0.prop(self, 'subject', expand=True)
741
742         # Move active Too:
743         row1 = layout.row()
744         row1.prop(self, 'active_too')
745         row1.prop(self, 'advanced')
746         if self.advanced:
747             row1b = layout.row()
748             row1b.prop(self, 'consistent')
749
750         row2 = layout.row()
751         row2.label(icon='MAN_TRANS', text="Align Location:")
752
753         # Align Location:
754         row3 = layout.row()
755         row3.prop(self, "loc_x", text="X", toggle=True)
756         row3.prop(self, "loc_y", text="Y", toggle=True)
757         row3.prop(self, "loc_z", text="Z", toggle=True)
758
759         # Offset:
760         if self.advanced is True:
761             # row8 = col.row()
762             # row8.label(text='Location Offset')
763             row9 = layout.row()
764             row9.prop(self, 'loc_offset', text='')
765
766         # Selection Options
767         if self.advanced is True:
768             sel = bpy.context.selected_objects
769             sel_obs = len(sel)
770             if sel_obs != 0:
771                 row4 = layout.row()
772                 row4.label(text="Selected: " + str(sel_obs) + " Objects", icon='OBJECT_DATA')
773         if self.subject == "1" or self.subject == "2":
774             row5b = layout.row()
775             row5b.prop(self, 'self_or_active', expand=True)
776         else:
777             row5 = layout.row()
778             row5.prop(self, 'ref1', expand=True)
779
780         # Active Object Options: Number of select objects
781         act = bpy.context.active_object
782
783         if self.advanced is True:
784             if act:
785                 row6 = layout.row()
786                 row6.label(text="Active: " + act.name, icon='OBJECT_DATA')
787         row7 = layout.row()
788         row7.prop(self, 'ref2', expand=True)
789
790         if self.subject == "0":
791             row12 = layout.row()
792             row12.label(icon='MAN_ROT', text='Align Rotation:')
793             row13 = layout.row(align=True)
794             row13.prop(self, 'rot_x', text='X', toggle=True)
795             row13.prop(self, 'rot_y', text='Y', toggle=True)
796             row13.prop(self, 'rot_z', text='Z', toggle=True)
797             row13.prop(self, 'apply_rot', text='Apply', toggle=True)
798             if self.advanced is True:
799                 row13b = layout.row()
800                 row13b.prop(self, 'rot_offset', text='')
801
802             row14 = layout.row()
803             row14.label(icon='MAN_SCALE', text='Match Scale:')
804             row15 = layout.row(align=True)
805             row15.prop(self, 'scale_x', text='X', toggle=True)
806             row15.prop(self, 'scale_y', text='Y', toggle=True)
807             row15.prop(self, 'scale_z', text='Z', toggle=True)
808             row15.prop(self, 'apply_scale', text='Apply', toggle=True)
809             if self.advanced is True:
810                 row15b = layout.row()
811                 row15b.prop(self, 'scale_offset', text='')
812
813             row10 = layout.row()
814             row10.label(icon='MAN_SCALE', text='Fit Dimensions:')
815             row11 = layout.row(align=True)
816             row11.prop(self, 'fit_x', text='X', toggle=True)
817             row11.prop(self, 'fit_y', text='Y', toggle=True)
818             row11.prop(self, 'fit_z', text='Z', toggle=True)
819             row11.prop(self, 'apply_dim', text='Apply', toggle=True)
820
821     def execute(self, context):
822         align_function(
823                 self.subject, self.active_too, self.consistent,
824                 self.self_or_active, self.loc_x, self.loc_y, self.loc_z,
825                 self.ref1, self.ref2, self.loc_offset,
826                 self.rot_x, self.rot_y, self.rot_z, self.rot_offset,
827                 self.scale_x, self.scale_y, self.scale_z, self.scale_offset,
828                 self.fit_x, self.fit_y, self.fit_z
829                 )
830
831         return {'FINISHED'}
832
833
834 # Simple Align Classes #
835
836 # Align All Rotation And Location
837 class AlignOperator(Operator):
838     bl_idname = "object.align"
839     bl_label = "Align Selected To Active"
840     bl_description = "Align Selected To Active"
841
842     @classmethod
843     def poll(cls, context):
844         return context.active_object is not None
845
846     def execute(self, context):
847         main(context)
848         return {'FINISHED'}
849
850
851 # Align Location All
852 class AlignLocationOperator(Operator):
853     bl_idname = "object.align_location_all"
854     bl_label = "Align Selected Location To Active"
855     bl_description = "Align Selected Location To Active"
856
857     @classmethod
858     def poll(cls, context):
859         return context.active_object is not None
860
861     def execute(self, context):
862         LocAll(context)
863         return {'FINISHED'}
864
865
866 # Align Location X
867 class AlignLocationXOperator(Operator):
868     bl_idname = "object.align_location_x"
869     bl_label = "Align Selected Location X To Active"
870     bl_description = "Align Selected Location X To Active"
871
872     @classmethod
873     def poll(cls, context):
874         return context.active_object is not None
875
876     def execute(self, context):
877         LocX(context)
878         return {'FINISHED'}
879
880
881 # Align Location Y
882 class AlignLocationYOperator(Operator):
883     bl_idname = "object.align_location_y"
884     bl_label = "Align Selected Location Y To Active"
885     bl_description = "Align Selected Location Y To Active"
886
887     @classmethod
888     def poll(cls, context):
889         return context.active_object is not None
890
891     def execute(self, context):
892         LocY(context)
893         return {'FINISHED'}
894
895
896 # Align LocationZ
897 class AlignLocationZOperator(Operator):
898     bl_idname = "object.align_location_z"
899     bl_label = "Align Selected Location Z To Active"
900     bl_description = "Align Selected Location Z To Active"
901
902     @classmethod
903     def poll(cls, context):
904         return context.active_object is not None
905
906     def execute(self, context):
907         LocZ(context)
908         return {'FINISHED'}
909
910
911 # Align Rotation All
912 class AlignRotationOperator(Operator):
913     bl_idname = "object.align_rotation_all"
914     bl_label = "Align Selected Rotation To Active"
915     bl_description = "Align Selected Rotation To Active"
916
917     @classmethod
918     def poll(cls, context):
919         return context.active_object is not None
920
921     def execute(self, context):
922         RotAll(context)
923         return {'FINISHED'}
924
925
926 # Align Rotation X
927 class AlignRotationXOperator(Operator):
928     bl_idname = "object.align_rotation_x"
929     bl_label = "Align Selected Rotation X To Active"
930     bl_description = "Align Selected Rotation X To Active"
931
932     @classmethod
933     def poll(cls, context):
934         return context.active_object is not None
935
936     def execute(self, context):
937         RotX(context)
938         return {'FINISHED'}
939
940
941 # Align Rotation Y
942 class AlignRotationYOperator(Operator):
943     bl_idname = "object.align_rotation_y"
944     bl_label = "Align Selected Rotation Y To Active"
945     bl_description = "Align Selected Rotation Y To Active"
946
947     @classmethod
948     def poll(cls, context):
949         return context.active_object is not None
950
951     def execute(self, context):
952         RotY(context)
953         return {'FINISHED'}
954
955
956 # Align Rotation Z
957 class AlignRotationZOperator(Operator):
958     bl_idname = "object.align_rotation_z"
959     bl_label = "Align Selected Rotation Z To Active"
960     bl_description = "Align Selected Rotation Z To Active"
961
962     @classmethod
963     def poll(cls, context):
964         return context.active_object is not None
965
966     def execute(self, context):
967         RotZ(context)
968         return {'FINISHED'}
969
970
971 # Scale All
972 class AlignScaleOperator(Operator):
973     bl_idname = "object.align_objects_scale_all"
974     bl_label = "Align Selected Scale To Active"
975     bl_description = "Align Selected Scale To Active"
976
977     @classmethod
978     def poll(cls, context):
979         return context.active_object is not None
980
981     def execute(self, context):
982         ScaleAll(context)
983         return {'FINISHED'}
984
985
986 # Align Scale X
987 class AlignScaleXOperator(Operator):
988     bl_idname = "object.align_objects_scale_x"
989     bl_label = "Align Selected Scale X To Active"
990     bl_description = "Align Selected Scale X To Active"
991
992     @classmethod
993     def poll(cls, context):
994         return context.active_object is not None
995
996     def execute(self, context):
997         ScaleX(context)
998         return {'FINISHED'}
999
1000
1001 # Align Scale Y
1002 class AlignScaleYOperator(Operator):
1003     bl_idname = "object.align_objects_scale_y"
1004     bl_label = "Align Selected Scale Y To Active"
1005     bl_description = "Align Selected Scale Y To Active"
1006
1007     @classmethod
1008     def poll(cls, context):
1009         return context.active_object is not None
1010
1011     def execute(self, context):
1012         ScaleY(context)
1013         return {'FINISHED'}
1014
1015
1016 # Align Scale Z
1017 class AlignScaleZOperator(Operator):
1018     bl_idname = "object.align_objects_scale_z"
1019     bl_label = "Align Selected Scale Z To Active"
1020     bl_description = "Align Selected Scale Z To Active"
1021
1022     @classmethod
1023     def poll(cls, context):
1024         return context.active_object is not None
1025
1026     def execute(self, context):
1027         ScaleZ(context)
1028         return {'FINISHED'}
1029
1030
1031 # Interface Panel
1032
1033 class AlignUi(Panel):
1034     bl_space_type = 'VIEW_3D'
1035     bl_region_type = 'TOOLS'
1036     bl_label = "Align Tools"
1037     bl_context = "objectmode"
1038     bl_category = 'Tools'
1039     bl_options = {'DEFAULT_CLOSED'}
1040
1041     def draw(self, context):
1042         layout = self.layout
1043         obj = context.object
1044
1045         if obj is not None:
1046             row = layout.row()
1047             row.label(text="Active object is: ", icon='OBJECT_DATA')
1048             box = layout.box()
1049             box.label(text=obj.name, icon='EDITMODE_HLT')
1050
1051         col = layout.column()
1052         col.label(text="Align Loc + Rot:", icon='MANIPUL')
1053
1054         col = layout.column(align=False)
1055         col.operator("object.align", text="XYZ")
1056
1057         col = layout.column()
1058         col.label(text="Align Location:", icon='MAN_TRANS')
1059
1060         col = layout.column_flow(columns=4, align=True)
1061         col.operator("object.align_location_x", text="X")
1062         col.operator("object.align_location_y", text="Y")
1063         col.operator("object.align_location_z", text="Z")
1064         col.operator("object.align_location_all", text="All")
1065
1066         col = layout.column()
1067         col.label(text="Align Rotation:", icon='MAN_ROT')
1068
1069         col = layout.column_flow(columns=4, align=True)
1070         col.operator("object.align_rotation_x", text="X")
1071         col.operator("object.align_rotation_y", text="Y")
1072         col.operator("object.align_rotation_z", text="Z")
1073         col.operator("object.align_rotation_all", text="All")
1074
1075         col = layout.column()
1076         col.label(text="Align Scale:", icon='MAN_SCALE')
1077
1078         col = layout.column_flow(columns=4, align=True)
1079         col.operator("object.align_objects_scale_x", text="X")
1080         col.operator("object.align_objects_scale_y", text="Y")
1081         col.operator("object.align_objects_scale_z", text="Z")
1082         col.operator("object.align_objects_scale_all", text="All")
1083
1084         if obj is not None:
1085             col = layout.column()
1086             col.label(text="Advanced Align")
1087             layout = self.layout
1088             self.layout.operator("object.align_tools", text="Advanced")
1089
1090
1091 # Add-ons Preferences Update Panel
1092
1093 # Define Panel classes for updating
1094 panels = (
1095         AlignUi,
1096         )
1097
1098
1099 def update_panel(self, context):
1100     message = "Align Tools: Updating Panel locations has failed"
1101     try:
1102         for panel in panels:
1103             if "bl_rna" in panel.__dict__:
1104                 bpy.utils.unregister_class(panel)
1105
1106         for panel in panels:
1107             panel.bl_category = context.preferences.addons[__name__].preferences.category
1108             bpy.utils.register_class(panel)
1109
1110     except Exception as e:
1111         print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
1112         pass
1113
1114
1115 class AlignAddonPreferences(AddonPreferences):
1116     # this must match the addon name, use '__package__'
1117     # when defining this in a submodule of a python package.
1118     bl_idname = __name__
1119
1120     category: StringProperty(
1121             name="Tab Category",
1122             description="Choose a name for the category of the panel",
1123             default="Tools",
1124             update=update_panel
1125             )
1126
1127     def draw(self, context):
1128         layout = self.layout
1129
1130         row = layout.row()
1131         col = row.column()
1132         col.label(text="Tab Category:")
1133         col.prop(self, "category", text="")
1134
1135
1136 # Class List
1137 classes = (
1138     AlignUi,
1139     AlignOperator,
1140     AlignLocationOperator,
1141     AlignLocationXOperator,
1142     AlignLocationYOperator,
1143     AlignLocationZOperator,
1144     AlignRotationOperator,
1145     AlignRotationXOperator,
1146     AlignRotationYOperator,
1147     AlignRotationZOperator,
1148     AlignScaleOperator,
1149     AlignScaleXOperator,
1150     AlignScaleYOperator,
1151     AlignScaleZOperator,
1152     OBJECT_OT_align_tools,
1153     AlignAddonPreferences,
1154     )
1155
1156
1157 # Register all operators and panels
1158 def register():
1159     for cls in classes:
1160         bpy.utils.register_class(cls)
1161
1162
1163 def unregister():
1164     for cls in classes:
1165         bpy.utils.unregister_class(cls)
1166
1167
1168 if __name__ == "__main__":
1169     register()