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
22 "name": "Align Tools",
23 "author": "gabhead, Lell, Anfeo",
25 "blender": (2, 80, 0),
26 "location": "View3D > Sidebar > Item Tab",
27 "description": "Align Selected Objects to Active Object",
29 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/align_tools.html",
34 from bpy
.types
import (
39 from bpy
.props
import (
45 from mathutils
import (
55 for i
in bpy
.context
.selected_objects
:
56 i
.matrix_world
.translation
= bpy
.context
.active_object
.matrix_world
.translation
.copy()
57 i
.rotation_euler
= bpy
.context
.active_object
.rotation_euler
62 for i
in bpy
.context
.selected_objects
:
63 i
.matrix_world
.translation
= bpy
.context
.active_object
.matrix_world
.translation
.copy()
67 for i
in bpy
.context
.selected_objects
:
68 i
.matrix_world
.translation
.x
= bpy
.context
.active_object
.matrix_world
.translation
.x
72 for i
in bpy
.context
.selected_objects
:
73 i
.matrix_world
.translation
.y
= bpy
.context
.active_object
.matrix_world
.translation
.y
77 for i
in bpy
.context
.selected_objects
:
78 i
.matrix_world
.translation
.z
= bpy
.context
.active_object
.matrix_world
.translation
.z
83 for i
in bpy
.context
.selected_objects
:
84 i
.rotation_euler
= bpy
.context
.active_object
.rotation_euler
88 for i
in bpy
.context
.selected_objects
:
89 i
.rotation_euler
.x
= bpy
.context
.active_object
.rotation_euler
.x
93 for i
in bpy
.context
.selected_objects
:
94 i
.rotation_euler
.y
= bpy
.context
.active_object
.rotation_euler
.y
98 for i
in bpy
.context
.selected_objects
:
99 i
.rotation_euler
.z
= bpy
.context
.active_object
.rotation_euler
.z
103 def ScaleAll(context
):
104 for i
in bpy
.context
.selected_objects
:
105 i
.scale
= bpy
.context
.active_object
.scale
109 for i
in bpy
.context
.selected_objects
:
110 i
.scale
.x
= bpy
.context
.active_object
.scale
.x
114 for i
in bpy
.context
.selected_objects
:
115 i
.scale
.y
= bpy
.context
.active_object
.scale
.y
119 for i
in bpy
.context
.selected_objects
:
120 i
.scale
.z
= bpy
.context
.active_object
.scale
.z
123 # Advanced Align Defs #
125 # subject to object 0, 1 and 2 to pivot for cursor
126 def align_function(subject
, active_too
, consistent
, self_or_active
, loc_x
, loc_y
, loc_z
, ref1
, ref2
, loc_offset
,
127 rot_x
, rot_y
, rot_z
, rot_offset
, scale_x
, scale_y
, scale_z
, scale_offset
,
128 fit_x
, fit_y
, fit_z
):
130 sel_obj
= bpy
.context
.selected_objects
131 act_obj
= bpy
.context
.active_object
138 def get_reference_points(obj
, space
):
142 # let's get all the points coordinates
143 if space
== "global":
145 obj_mtx
= obj
.matrix_world
146 if obj
.type == 'MESH' and len(me
.vertices
) > 0:
148 for p
in me
.vertices
:
149 co_list
.append((obj_mtx
@ p
.co
))
151 elif obj
.type == 'SURFACE' and len(me
.splines
) > 0:
155 co_list
.append((obj_mtx
@ p
.co
))
156 elif obj
.type == 'FONT' and len(me
.splines
) > 0:
159 for p
in s
.bezier_points
:
160 co_list
.append((obj_mtx
@ p
.co
))
162 elif space
== "local":
164 if obj
.type == 'MESH' and len(me
.vertices
) > 0:
166 for p
in me
.vertices
:
169 elif obj
.type == 'SURFACE' and len(me
.splines
) > 0:
174 elif obj
.type == 'FONT' and len(obj
.data
.splines
) > 0:
177 for p
in s
.bezier_points
:
180 # if a valid point found
181 # proceed to calculate the extremes
183 max_x
= co_list
[0][0]
184 min_x
= co_list
[0][0]
185 max_y
= co_list
[0][1]
186 min_y
= co_list
[0][1]
187 max_z
= co_list
[0][2]
188 min_z
= co_list
[0][2]
191 # the strings of the list compared with the smaller and more found
192 # in order to find the minor and major for each axis
212 # otherwise use the pivot object
213 a
= obj
.matrix_world
.translation
221 center_x
= min_x
+ ((max_x
- min_x
) / 2)
222 center_y
= min_y
+ ((max_y
- min_y
) / 2)
223 center_z
= min_z
+ ((max_z
- min_z
) / 2)
225 reference_points
= [min_x
, center_x
, max_x
, min_y
, center_y
, max_y
, min_z
, center_z
, max_z
]
226 return reference_points
228 def get_sel_ref(ref_co
, sel_obj
): # I look for the selection end points
230 sel_min
= ref_co
.copy()
231 sel_max
= ref_co
.copy()
234 if obj
!= act_obj
or (active_too
and obj
== act_obj
):
236 ref_points
= get_reference_points(obj
, "global")
237 ref_min
= Vector([ref_points
[0], ref_points
[3], ref_points
[6]])
238 ref_max
= Vector([ref_points
[2], ref_points
[5], ref_points
[8]])
240 if ref_min
[0] < sel_min
[0]:
241 sel_min
[0] = ref_min
[0]
242 if ref_max
[0] > sel_max
[0]:
243 sel_max
[0] = ref_max
[0]
244 if ref_min
[1] < sel_min
[1]:
245 sel_min
[1] = ref_min
[1]
246 if ref_max
[1] > sel_max
[1]:
247 sel_max
[1] = ref_max
[1]
248 if ref_min
[2] < sel_min
[2]:
249 sel_min
[2] = ref_min
[2]
250 if ref_max
[2] > sel_max
[2]:
251 sel_max
[2] = ref_max
[2]
253 return sel_min
, sel_max
255 def find_ref2_co(act_obj
):
256 # It contains the coordinates of the reference point for the positioning
258 ref_points
= get_reference_points(act_obj
, "global")
259 ref2_co
= [ref_points
[0], ref_points
[3], ref_points
[6]]
260 ref2_co
= Vector(ref2_co
)
262 ref_points
= get_reference_points(act_obj
, "global")
263 ref2_co
= [ref_points
[1], ref_points
[4], ref_points
[7]]
264 ref2_co
= Vector(ref2_co
)
266 ref2_co
= act_obj
.location
267 ref2_co
= Vector(ref2_co
)
269 ref_points
= get_reference_points(act_obj
, "global")
270 ref2_co
= [ref_points
[2], ref_points
[5], ref_points
[8]]
271 ref2_co
= Vector(ref2_co
)
273 ref2_co
= bpy
.context
.scene
.cursor
.location
277 def find_new_coord(obj
):
279 ref_points
= get_reference_points(obj
, "global")
283 min_x
= ref_points
[0]
284 new_x
= ref2_co
[0] + (obj
.location
[0] - min_x
) + loc_offset
[0]
286 center_x
= ref_points
[1]
287 new_x
= ref2_co
[0] + (obj
.location
[0] - center_x
) + loc_offset
[0]
289 new_x
= ref2_co
[0] + loc_offset
[0]
291 max_x
= ref_points
[2]
292 new_x
= ref2_co
[0] - (max_x
- obj
.location
[0]) + loc_offset
[0]
293 obj
.matrix_world
.translation
[0] = new_x
296 min_y
= ref_points
[3]
297 new_y
= ref2_co
[1] + (obj
.location
[1] - min_y
) + loc_offset
[1]
299 center_y
= ref_points
[4]
300 new_y
= ref2_co
[1] + (obj
.location
[1] - center_y
) + loc_offset
[1]
302 new_y
= ref2_co
[1] + loc_offset
[1]
304 max_y
= ref_points
[5]
305 new_y
= ref2_co
[1] - (max_y
- obj
.location
[1]) + loc_offset
[1]
306 obj
.matrix_world
.translation
[1] = new_y
309 min_z
= ref_points
[6]
310 new_z
= ref2_co
[2] + (obj
.location
[2] - min_z
) + loc_offset
[2]
312 center_z
= ref_points
[7]
313 new_z
= ref2_co
[2] + (obj
.location
[2] - center_z
) + loc_offset
[2]
315 new_z
= ref2_co
[2] + loc_offset
[2]
317 max_z
= ref_points
[8]
318 new_z
= ref2_co
[2] - (max_z
- obj
.location
[2]) + loc_offset
[2]
319 obj
.matrix_world
.translation
[2] = new_z
321 def find_new_rotation(obj
):
323 obj
.rotation_euler
[0] = act_obj
.rotation_euler
[0] + rot_offset
[0]
325 obj
.rotation_euler
[1] = act_obj
.rotation_euler
[1] + rot_offset
[1]
327 obj
.rotation_euler
[2] = act_obj
.rotation_euler
[2] + rot_offset
[2]
329 def find_new_scale(obj
):
331 obj
.scale
[0] = act_obj
.scale
[0] + scale_offset
[0]
333 obj
.scale
[1] = act_obj
.scale
[1] + scale_offset
[1]
335 obj
.scale
[2] = act_obj
.scale
[2] + scale_offset
[2]
337 def find_new_dimensions(obj
, ref_dim
):
338 ref_points
= get_reference_points(obj
, "local")
340 dim
= ref_points
[2] - ref_points
[0]
341 obj
.scale
[0] = (ref_dim
[0] / dim
) * act_obj
.scale
[0]
343 dim
= ref_points
[5] - ref_points
[3]
344 obj
.scale
[1] = (ref_dim
[1] / dim
) * act_obj
.scale
[1]
346 dim
= ref_points
[8] - ref_points
[6]
347 obj
.scale
[2] = (ref_dim
[2] / dim
) * act_obj
.scale
[2]
351 vec_ref2_co
= Vector(ref2_co
)
352 offset
= vec_ref2_co
- obj
.location
353 offset_x
= [offset
[0] + loc_offset
[0], 0, 0]
354 offset_y
= [0, offset
[1] + loc_offset
[1], 0]
355 offset_z
= [0, 0, offset
[2] + loc_offset
[2]]
358 obj_mtx
= obj
.matrix_world
.copy()
359 # What's the displacement vector for the pivot?
360 move_pivot
= Vector(vec
)
362 # Move the pivot point (which is the object's location)
366 nm
= obj_mtx
.inverted() @ Matrix
.Translation(-move_pivot
) @ obj_mtx
368 # Transform the mesh now
378 def point_in_selection(act_obj
, sel_obj
):
383 obj_mtx
= o
.matrix_world
384 if o
.type == 'MESH' and len(o
.data
.vertices
) > 0:
385 ref_co
= o
.data
.vertices
[0].co
.copy()
386 ref_co
= obj_mtx
@ ref_co
389 elif o
.type == 'CURVE' and len(o
.data
.splines
) > 0:
390 ref_co
= o
.data
.splines
[0].bezier_point
[0].co
.copy()
391 ref_co
= obj_mtx
@ ref_co
394 elif o
.type == 'SURFACE' and len(o
.data
.splines
) > 0:
395 ref_co
= o
.data
.splines
[0].points
[0].co
.copy()
396 ref_co
= obj_mtx
@ ref_co
399 elif o
.type == 'FONT' and len(o
.data
.splines
) > 0:
400 ref_co
= o
.data
.splines
[0].bezier_points
[0].co
.copy()
401 ref_co
= obj_mtx
@ ref_co
404 # if no object had data, use the position of an object that was not active as an internal
407 ref_co
= ref_ob
.matrix_world
.translation
412 # if act_obj.type == ('MESH' or 'FONT' or 'CURVE' or 'SURFACE'):
413 if act_obj
.type == 'MESH' or act_obj
.type == 'FONT' or act_obj
.type == 'SURFACE':
414 ref2_co
= find_ref2_co(act_obj
)
417 ref2_co
= bpy
.context
.scene
.cursor
.location
419 ref2_co
= act_obj
.matrix_world
.translation
421 # in the case of substantial selection
423 # I am seeking a point that is in the selection space
424 ref_co
= point_in_selection(act_obj
, sel_obj
)
426 sel_min
, sel_max
= get_sel_ref(ref_co
, sel_obj
)
428 sel_center
= sel_min
+ ((sel_max
- sel_min
) / 2)
429 translate
= [0, 0, 0]
431 # calculating how much to move the selection
433 translate
= ref2_co
- sel_min
+ loc_offset
435 translate
= ref2_co
- sel_center
+ loc_offset
437 translate
= ref2_co
- sel_max
+ loc_offset
439 # Move the various objects
442 if obj
!= act_obj
or (active_too
and obj
== act_obj
):
445 obj
.location
[0] += translate
[0]
447 obj
.location
[1] += translate
[1]
449 obj
.location
[2] += translate
[2]
450 else: # not consistent
453 if rot_x
or rot_y
or rot_z
:
454 find_new_rotation(obj
)
456 if fit_x
or fit_y
or fit_z
:
458 ref_points
= get_reference_points(act_obj
, "local")
459 dim
[0] = ref_points
[2] - ref_points
[0]
460 dim
[1] = ref_points
[5] - ref_points
[3]
461 dim
[2] = ref_points
[8] - ref_points
[6]
462 find_new_dimensions(obj
, dim
)
464 if scale_x
or scale_y
or scale_z
:
467 if loc_x
or loc_y
or loc_z
:
468 # print("ehy", ref2_co)
471 if active_too
is True:
472 if loc_x
or loc_y
or loc_z
:
473 find_new_coord(act_obj
)
474 if rot_x
or rot_y
or rot_z
:
475 find_new_rotation(act_obj
)
476 if scale_x
or scale_y
or scale_z
:
477 find_new_scale(act_obj
)
478 # add dimensions if dim offset will be added
481 if self_or_active
== "1":
482 if act_obj
.type == 'MESH':
483 ref2_co
= find_ref2_co(act_obj
)
485 if self_or_active
== "0":
486 ref2_co
= find_ref2_co(obj
)
487 if loc_x
or loc_y
or loc_z
:
488 if obj
!= act_obj
and obj
.type == 'MESH':
491 if active_too
is True:
492 if act_obj
.type == 'MESH':
493 if loc_x
or loc_y
or loc_z
:
494 if self_or_active
== "0":
495 ref2_co
= find_ref2_co(act_obj
)
499 if self_or_active
== "1":
500 if act_obj
.type == 'MESH' or act_obj
.type == 'FONT' or act_obj
.type == 'SURFACE':
501 ref2_co
= find_ref2_co(act_obj
)
502 ref_points
= get_reference_points(act_obj
, "global")
504 ref2_co
= act_obj
.matrix_world
.translation
505 ref_points
= [ref2_co
[0], ref2_co
[0], ref2_co
[0],
506 ref2_co
[1], ref2_co
[1], ref2_co
[1],
507 ref2_co
[2], ref2_co
[2], ref2_co
[2]]
511 bpy
.context
.scene
.cursor
.location
[0] = ref_points
[0] + loc_offset
[0]
513 bpy
.context
.scene
.cursor
.location
[1] = ref_points
[3] + loc_offset
[1]
515 bpy
.context
.scene
.cursor
.location
[2] = ref_points
[6] + loc_offset
[2]
518 bpy
.context
.scene
.cursor
.location
[0] = ref_points
[1] + loc_offset
[0]
520 bpy
.context
.scene
.cursor
.location
[1] = ref_points
[4] + loc_offset
[1]
522 bpy
.context
.scene
.cursor
.location
[2] = ref_points
[7] + loc_offset
[2]
525 bpy
.context
.scene
.cursor
.location
[0] = act_obj
.location
[0] + loc_offset
[0]
527 bpy
.context
.scene
.cursor
.location
[1] = act_obj
.location
[1] + loc_offset
[1]
529 bpy
.context
.scene
.cursor
.location
[2] = act_obj
.location
[2] + loc_offset
[2]
532 bpy
.context
.scene
.cursor
.location
[0] = ref_points
[2] + loc_offset
[0]
534 bpy
.context
.scene
.cursor
.location
[1] = ref_points
[5] + loc_offset
[1]
536 bpy
.context
.scene
.cursor
.location
[2] = ref_points
[8] + loc_offset
[2]
537 elif self_or_active
== "2":
538 ref_co
= point_in_selection(act_obj
, sel_obj
)
540 sel_min
, sel_max
= get_sel_ref(ref_co
, sel_obj
)
541 sel_center
= sel_min
+ ((sel_max
- sel_min
) / 2)
545 bpy
.context
.scene
.cursor
.location
[0] = sel_min
[0] + loc_offset
[0]
547 bpy
.context
.scene
.cursor
.location
[1] = sel_min
[1] + loc_offset
[1]
549 bpy
.context
.scene
.cursor
.location
[2] = sel_min
[2] + loc_offset
[2]
552 bpy
.context
.scene
.cursor
.location
[0] = sel_center
[0] + loc_offset
[0]
554 bpy
.context
.scene
.cursor
.location
[1] = sel_center
[1] + loc_offset
[1]
556 bpy
.context
.scene
.cursor
.location
[2] = sel_center
[2] + loc_offset
[2]
559 bpy
.context
.scene
.cursor
.location
[0] = sel_max
[0] + loc_offset
[0]
561 bpy
.context
.scene
.cursor
.location
[1] = sel_max
[1] + loc_offset
[1]
563 bpy
.context
.scene
.cursor
.location
[2] = sel_max
[2] + loc_offset
[2]
569 class OBJECT_OT_align_tools(Operator
):
570 bl_idname
= "object.align_tools"
571 bl_label
= "Align Operator"
572 bl_description
= "Align Object Tools"
573 bl_options
= {'REGISTER', 'UNDO'}
575 # property definitions
577 # Object-Pivot-Cursor:
578 subject
: EnumProperty(
579 items
=(("0", "Object", "Align Objects"),
580 ("1", "Pivot", "Align Objects Pivot"),
581 ("2", "Cursor", "Align Cursor To Active")),
583 description
="What will be moved"
586 active_too
: BoolProperty(
589 description
="Move the active object too"
592 advanced
: BoolProperty(
593 name
="Advanced Options",
595 description
="Show advanced options"
597 consistent
: BoolProperty(
598 name
="Consistent Selection",
600 description
="Use consistent selection"
604 name
="Align to X axis",
606 description
="Enable X axis alignment"
609 name
="Align to Y axis",
611 description
="Enable Y axis alignment"
614 name
="Align to Z axis",
616 description
="Enable Z axis alignment"
620 items
=(("3", "Max", "Align the maximum point"),
621 ("1", "Center", "Align the center point"),
622 ("2", "Pivot", "Align the pivot"),
623 ("0", "Min", "Align the minimum point")),
624 name
="Selection reference",
625 description
="Moved objects reference point"
627 # Active Object Option:
629 items
=(("3", "Max", "Align to the maximum point"),
630 ("1", "Center", "Align to the center point"),
631 ("2", "Pivot", "Align to the pivot"),
632 ("0", "Min", "Align to the minimum point"),
633 ("4", "Cursor", "Description")),
634 name
="Active reference",
635 description
="Destination point"
637 self_or_active
: EnumProperty(
638 items
=(("0", "Self", "In relation of itself"),
639 ("1", "Active", "In relation of the active object"),
640 ("2", "Selection", "In relation of the entire selection")),
643 description
="To what the pivot will be aligned"
646 loc_offset
: FloatVectorProperty(
647 name
="Location Offset",
648 description
="Offset for location align position",
649 default
=(0.0, 0.0, 0.0),
650 subtype
='XYZ', size
=3
653 rot_offset
: FloatVectorProperty(
654 name
="Rotation Offset",
655 description
="Offset for rotation alignment",
656 default
=(0.0, 0.0, 0.0),
657 subtype
='EULER', size
=3
660 scale_offset
: FloatVectorProperty(
662 description
="Offset for scale match",
663 default
=(0.0, 0.0, 0.0),
664 subtype
='XYZ', size
=3
666 # Fit Dimension Prop:
668 name
="Fit Dimension to X axis",
673 name
="Fit Dimension to Y axis",
678 name
="Fit Dimension to Z axis",
682 # Apply Fit Dimension:
683 apply_dim
: BoolProperty(
684 name
="Apply Dimension",
690 name
="Align Rotation to X axis",
695 name
="Align Rotation to Y axis",
700 name
="Align Rotation to Z axis",
705 apply_rot
: BoolProperty(
706 name
="Apply Rotation",
711 scale_x
: BoolProperty(
712 name
="Match Scale to X axis",
716 scale_y
: BoolProperty(
717 name
="Match Scale to Y axis",
721 scale_z
: BoolProperty(
722 name
="match Scale to Z axis",
727 apply_scale
: BoolProperty(
733 def draw(self
, context
):
737 row
.label(text
="Active object is: ", icon
='OBJECT_DATA')
739 box
.label(text
=obj
.name
, icon
='EDITMODE_HLT')
740 # Object-Pivot-Cursor:
742 row0
.prop(self
, 'subject', expand
=True)
746 row1
.prop(self
, 'active_too')
747 row1
.prop(self
, 'advanced')
750 row1b
.prop(self
, 'consistent')
753 row2
.label(text
="Align Location:")
757 row3
.prop(self
, "loc_x", text
="X", toggle
=True)
758 row3
.prop(self
, "loc_y", text
="Y", toggle
=True)
759 row3
.prop(self
, "loc_z", text
="Z", toggle
=True)
762 if self
.advanced
is True:
764 # row8.label(text='Location Offset')
766 row9
.prop(self
, 'loc_offset', text
='')
769 if self
.advanced
is True:
770 sel
= bpy
.context
.selected_objects
774 row4
.label(text
="Selected: " + str(sel_obs
) + " Objects", icon
='OBJECT_DATA')
775 if self
.subject
== "1" or self
.subject
== "2":
777 row5b
.prop(self
, 'self_or_active', expand
=True)
780 row5
.prop(self
, 'ref1', expand
=True)
782 # Active Object Options: Number of select objects
783 act
= bpy
.context
.active_object
785 if self
.advanced
is True:
788 row6
.label(text
="Active: " + act
.name
, icon
='OBJECT_DATA')
790 row7
.prop(self
, 'ref2', expand
=True)
792 if self
.subject
== "0":
794 row12
.label(text
='Align Rotation:')
795 row13
= layout
.row(align
=True)
796 row13
.prop(self
, 'rot_x', text
='X', toggle
=True)
797 row13
.prop(self
, 'rot_y', text
='Y', toggle
=True)
798 row13
.prop(self
, 'rot_z', text
='Z', toggle
=True)
799 row13
.prop(self
, 'apply_rot', text
='Apply', toggle
=True)
800 if self
.advanced
is True:
801 row13b
= layout
.row()
802 row13b
.prop(self
, 'rot_offset', text
='')
805 row14
.label(text
='Match Scale:')
806 row15
= layout
.row(align
=True)
807 row15
.prop(self
, 'scale_x', text
='X', toggle
=True)
808 row15
.prop(self
, 'scale_y', text
='Y', toggle
=True)
809 row15
.prop(self
, 'scale_z', text
='Z', toggle
=True)
810 row15
.prop(self
, 'apply_scale', text
='Apply', toggle
=True)
811 if self
.advanced
is True:
812 row15b
= layout
.row()
813 row15b
.prop(self
, 'scale_offset', text
='')
816 row10
.label(text
='Fit Dimensions:')
817 row11
= layout
.row(align
=True)
818 row11
.prop(self
, 'fit_x', text
='X', toggle
=True)
819 row11
.prop(self
, 'fit_y', text
='Y', toggle
=True)
820 row11
.prop(self
, 'fit_z', text
='Z', toggle
=True)
821 row11
.prop(self
, 'apply_dim', text
='Apply', toggle
=True)
823 def execute(self
, context
):
825 self
.subject
, self
.active_too
, self
.consistent
,
826 self
.self_or_active
, self
.loc_x
, self
.loc_y
, self
.loc_z
,
827 self
.ref1
, self
.ref2
, self
.loc_offset
,
828 self
.rot_x
, self
.rot_y
, self
.rot_z
, self
.rot_offset
,
829 self
.scale_x
, self
.scale_y
, self
.scale_z
, self
.scale_offset
,
830 self
.fit_x
, self
.fit_y
, self
.fit_z
836 # Simple Align Classes #
838 # Align All Rotation And Location
839 class OBJECT_OT_AlignOperator(Operator
):
840 bl_idname
= "object.align"
841 bl_label
= "Align Selected To Active"
842 bl_description
= "Align Selected To Active"
845 def poll(cls
, context
):
846 return context
.active_object
is not None
848 def execute(self
, context
):
854 class OBJECT_OT_AlignLocationOperator(Operator
):
855 bl_idname
= "object.align_location_all"
856 bl_label
= "Align Selected Location To Active"
857 bl_description
= "Align Selected Location To Active"
860 def poll(cls
, context
):
861 return context
.active_object
is not None
863 def execute(self
, context
):
869 class OBJECT_OT_AlignLocationXOperator(Operator
):
870 bl_idname
= "object.align_location_x"
871 bl_label
= "Align Selected Location X To Active"
872 bl_description
= "Align Selected Location X To Active"
875 def poll(cls
, context
):
876 return context
.active_object
is not None
878 def execute(self
, context
):
884 class OBJECT_OT_AlignLocationYOperator(Operator
):
885 bl_idname
= "object.align_location_y"
886 bl_label
= "Align Selected Location Y To Active"
887 bl_description
= "Align Selected Location Y To Active"
890 def poll(cls
, context
):
891 return context
.active_object
is not None
893 def execute(self
, context
):
899 class OBJECT_OT_AlignLocationZOperator(Operator
):
900 bl_idname
= "object.align_location_z"
901 bl_label
= "Align Selected Location Z To Active"
902 bl_description
= "Align Selected Location Z To Active"
905 def poll(cls
, context
):
906 return context
.active_object
is not None
908 def execute(self
, context
):
914 class OBJECT_OT_AlignRotationOperator(Operator
):
915 bl_idname
= "object.align_rotation_all"
916 bl_label
= "Align Selected Rotation To Active"
917 bl_description
= "Align Selected Rotation To Active"
920 def poll(cls
, context
):
921 return context
.active_object
is not None
923 def execute(self
, context
):
929 class OBJECT_OT_AlignRotationXOperator(Operator
):
930 bl_idname
= "object.align_rotation_x"
931 bl_label
= "Align Selected Rotation X To Active"
932 bl_description
= "Align Selected Rotation X To Active"
935 def poll(cls
, context
):
936 return context
.active_object
is not None
938 def execute(self
, context
):
944 class OBJECT_OT_AlignRotationYOperator(Operator
):
945 bl_idname
= "object.align_rotation_y"
946 bl_label
= "Align Selected Rotation Y To Active"
947 bl_description
= "Align Selected Rotation Y To Active"
950 def poll(cls
, context
):
951 return context
.active_object
is not None
953 def execute(self
, context
):
959 class OBJECT_OT_AlignRotationZOperator(Operator
):
960 bl_idname
= "object.align_rotation_z"
961 bl_label
= "Align Selected Rotation Z To Active"
962 bl_description
= "Align Selected Rotation Z To Active"
965 def poll(cls
, context
):
966 return context
.active_object
is not None
968 def execute(self
, context
):
974 class OBJECT_OT_AlignScaleOperator(Operator
):
975 bl_idname
= "object.align_objects_scale_all"
976 bl_label
= "Align Selected Scale To Active"
977 bl_description
= "Align Selected Scale To Active"
980 def poll(cls
, context
):
981 return context
.active_object
is not None
983 def execute(self
, context
):
989 class OBJECT_OT_AlignScaleXOperator(Operator
):
990 bl_idname
= "object.align_objects_scale_x"
991 bl_label
= "Align Selected Scale X To Active"
992 bl_description
= "Align Selected Scale X To Active"
995 def poll(cls
, context
):
996 return context
.active_object
is not None
998 def execute(self
, context
):
1004 class OBJECT_OT_AlignScaleYOperator(Operator
):
1005 bl_idname
= "object.align_objects_scale_y"
1006 bl_label
= "Align Selected Scale Y To Active"
1007 bl_description
= "Align Selected Scale Y To Active"
1010 def poll(cls
, context
):
1011 return context
.active_object
is not None
1013 def execute(self
, context
):
1019 class OBJECT_OT_AlignScaleZOperator(Operator
):
1020 bl_idname
= "object.align_objects_scale_z"
1021 bl_label
= "Align Selected Scale Z To Active"
1022 bl_description
= "Align Selected Scale Z To Active"
1025 def poll(cls
, context
):
1026 return context
.active_object
is not None
1028 def execute(self
, context
):
1035 class VIEW3D_PT_AlignUi(Panel
):
1036 bl_space_type
= 'VIEW_3D'
1037 bl_region_type
= 'UI'
1038 bl_label
= "Align Tools"
1039 bl_context
= "objectmode"
1040 bl_category
= 'Item'
1041 bl_options
= {'DEFAULT_CLOSED'}
1043 def draw(self
, context
):
1044 layout
= self
.layout
1045 obj
= context
.object
1049 row
.label(text
="Active object is: ", icon
='OBJECT_DATA')
1051 box
.label(text
=obj
.name
, icon
='EDITMODE_HLT')
1053 col
= layout
.column()
1054 col
.label(text
="Align Loc + Rot:")
1056 col
= layout
.column(align
=False)
1057 col
.operator("object.align", text
="XYZ")
1059 col
= layout
.column()
1060 col
.label(text
="Align Location:")
1062 col
= layout
.column_flow(columns
=4, align
=True)
1063 col
.operator("object.align_location_x", text
="X")
1064 col
.operator("object.align_location_y", text
="Y")
1065 col
.operator("object.align_location_z", text
="Z")
1066 col
.operator("object.align_location_all", text
="All")
1068 col
= layout
.column()
1069 col
.label(text
="Align Rotation:")
1071 col
= layout
.column_flow(columns
=4, align
=True)
1072 col
.operator("object.align_rotation_x", text
="X")
1073 col
.operator("object.align_rotation_y", text
="Y")
1074 col
.operator("object.align_rotation_z", text
="Z")
1075 col
.operator("object.align_rotation_all", text
="All")
1077 col
= layout
.column()
1078 col
.label(text
="Align Scale:")
1080 col
= layout
.column_flow(columns
=4, align
=True)
1081 col
.operator("object.align_objects_scale_x", text
="X")
1082 col
.operator("object.align_objects_scale_y", text
="Y")
1083 col
.operator("object.align_objects_scale_z", text
="Z")
1084 col
.operator("object.align_objects_scale_all", text
="All")
1087 col
= layout
.column()
1088 col
.label(text
="Advanced Align Operations")
1089 layout
= self
.layout
1090 self
.layout
.operator("object.align_tools", text
="Advanced")
1093 # Add-ons Preferences Update Panel
1095 # Define Panel classes for updating
1101 def update_panel(self
, context
):
1102 message
= "Align Tools: Updating Panel locations has failed"
1104 for panel
in panels
:
1105 if "bl_rna" in panel
.__dict
__:
1106 bpy
.utils
.unregister_class(panel
)
1108 for panel
in panels
:
1109 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
1110 bpy
.utils
.register_class(panel
)
1112 except Exception as e
:
1113 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
1117 class AlignAddonPreferences(AddonPreferences
):
1118 # this must match the addon name, use '__package__'
1119 # when defining this in a submodule of a python package.
1120 bl_idname
= __name__
1122 category
: StringProperty(
1123 name
="Tab Category",
1124 description
="Choose a name for the category of the panel",
1129 def draw(self
, context
):
1130 layout
= self
.layout
1134 col
.label(text
="Tab Category:")
1135 col
.prop(self
, "category", text
="")
1141 OBJECT_OT_AlignOperator
,
1142 OBJECT_OT_AlignLocationOperator
,
1143 OBJECT_OT_AlignLocationXOperator
,
1144 OBJECT_OT_AlignLocationYOperator
,
1145 OBJECT_OT_AlignLocationZOperator
,
1146 OBJECT_OT_AlignRotationOperator
,
1147 OBJECT_OT_AlignRotationXOperator
,
1148 OBJECT_OT_AlignRotationYOperator
,
1149 OBJECT_OT_AlignRotationZOperator
,
1150 OBJECT_OT_AlignScaleOperator
,
1151 OBJECT_OT_AlignScaleXOperator
,
1152 OBJECT_OT_AlignScaleYOperator
,
1153 OBJECT_OT_AlignScaleZOperator
,
1154 OBJECT_OT_align_tools
,
1155 AlignAddonPreferences
,
1159 # Register all operators and panels
1162 bpy
.utils
.register_class(cls
)
1167 bpy
.utils
.unregister_class(cls
)
1170 if __name__
== "__main__":