Cleanup: trailing space
[blender-addons.git] / space_view3d_align_tools.py
blob8bfa1f6bab030c9ae303e734bf0ea941dfa256d9
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 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/align_tools.html",
30 "category": "Object",
33 import bpy
34 from bpy.types import (
35 Operator,
36 Panel,
37 AddonPreferences,
39 from bpy.props import (
40 EnumProperty,
41 BoolProperty,
42 FloatVectorProperty,
43 StringProperty,
45 from mathutils import (
46 Vector,
47 Matrix,
51 # Simple Align Defs #
53 # Align all
54 def main(context):
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
60 # Align Location
61 def LocAll(context):
62 for i in bpy.context.selected_objects:
63 i.matrix_world.translation = bpy.context.active_object.matrix_world.translation.copy()
66 def LocX(context):
67 for i in bpy.context.selected_objects:
68 i.matrix_world.translation.x = bpy.context.active_object.matrix_world.translation.x
71 def LocY(context):
72 for i in bpy.context.selected_objects:
73 i.matrix_world.translation.y = bpy.context.active_object.matrix_world.translation.y
76 def LocZ(context):
77 for i in bpy.context.selected_objects:
78 i.matrix_world.translation.z = bpy.context.active_object.matrix_world.translation.z
81 # Align Rotation
82 def RotAll(context):
83 for i in bpy.context.selected_objects:
84 i.rotation_euler = bpy.context.active_object.rotation_euler
87 def RotX(context):
88 for i in bpy.context.selected_objects:
89 i.rotation_euler.x = bpy.context.active_object.rotation_euler.x
92 def RotY(context):
93 for i in bpy.context.selected_objects:
94 i.rotation_euler.y = bpy.context.active_object.rotation_euler.y
97 def RotZ(context):
98 for i in bpy.context.selected_objects:
99 i.rotation_euler.z = bpy.context.active_object.rotation_euler.z
102 # Align Scale
103 def ScaleAll(context):
104 for i in bpy.context.selected_objects:
105 i.scale = bpy.context.active_object.scale
108 def ScaleX(context):
109 for i in bpy.context.selected_objects:
110 i.scale.x = bpy.context.active_object.scale.x
113 def ScaleY(context):
114 for i in bpy.context.selected_objects:
115 i.scale.y = bpy.context.active_object.scale.y
118 def ScaleZ(context):
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
133 global sel_max
134 global sel_min
135 global sel_center
136 global ref2_co
138 def get_reference_points(obj, space):
140 me = obj.data
141 co_list = []
142 # let's get all the points coordinates
143 if space == "global":
144 ok = False
145 obj_mtx = obj.matrix_world
146 if obj.type == 'MESH' and len(me.vertices) > 0:
147 ok = True
148 for p in me.vertices:
149 co_list.append((obj_mtx @ p.co))
151 elif obj.type == 'SURFACE' and len(me.splines) > 0:
152 ok = True
153 for s in me.splines:
154 for p in s.points:
155 co_list.append((obj_mtx @ p.co))
156 elif obj.type == 'FONT' and len(me.splines) > 0:
157 ok = True
158 for s in me.splines:
159 for p in s.bezier_points:
160 co_list.append((obj_mtx @ p.co))
162 elif space == "local":
163 ok = False
164 if obj.type == 'MESH' and len(me.vertices) > 0:
165 ok = True
166 for p in me.vertices:
167 co_list.append(p.co)
169 elif obj.type == 'SURFACE' and len(me.splines) > 0:
170 ok = True
171 for s in me.splines:
172 for p in s.points:
173 co_list.append(p.co)
174 elif obj.type == 'FONT' and len(obj.data.splines) > 0:
175 ok = True
176 for s in me.splines:
177 for p in s.bezier_points:
178 co_list.append(p.co)
180 # if a valid point found
181 # proceed to calculate the extremes
182 if ok:
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]
190 for v in co_list:
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
193 act_x = v[0]
194 if act_x > max_x:
195 max_x = act_x
196 if act_x < min_x:
197 min_x = act_x
199 act_y = v[1]
200 if act_y > max_y:
201 max_y = act_y
202 if act_y < min_y:
203 min_y = act_y
205 act_z = v[2]
206 if act_z > max_z:
207 max_z = act_z
208 if act_z < min_z:
209 min_z = act_z
211 else:
212 # otherwise use the pivot object
213 a = obj.matrix_world.translation
214 min_x = a[0]
215 max_x = a[0]
216 min_y = a[1]
217 max_y = a[1]
218 min_z = a[2]
219 max_z = a[2]
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()
233 for obj in sel_obj:
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
257 if ref2 == "0":
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)
261 elif ref2 == "1":
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)
265 elif ref2 == "2":
266 ref2_co = act_obj.location
267 ref2_co = Vector(ref2_co)
268 elif ref2 == "3":
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)
272 elif ref2 == "4":
273 ref2_co = bpy.context.scene.cursor.location
275 return ref2_co
277 def find_new_coord(obj):
279 ref_points = get_reference_points(obj, "global")
281 if loc_x is True:
282 if ref1 == "0":
283 min_x = ref_points[0]
284 new_x = ref2_co[0] + (obj.location[0] - min_x) + loc_offset[0]
285 elif ref1 == "1":
286 center_x = ref_points[1]
287 new_x = ref2_co[0] + (obj.location[0] - center_x) + loc_offset[0]
288 elif ref1 == "2":
289 new_x = ref2_co[0] + loc_offset[0]
290 elif ref1 == "3":
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
294 if loc_y is True:
295 if ref1 == "0":
296 min_y = ref_points[3]
297 new_y = ref2_co[1] + (obj.location[1] - min_y) + loc_offset[1]
298 elif ref1 == "1":
299 center_y = ref_points[4]
300 new_y = ref2_co[1] + (obj.location[1] - center_y) + loc_offset[1]
301 elif ref1 == "2":
302 new_y = ref2_co[1] + loc_offset[1]
303 elif ref1 == "3":
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
307 if loc_z is True:
308 if ref1 == "0":
309 min_z = ref_points[6]
310 new_z = ref2_co[2] + (obj.location[2] - min_z) + loc_offset[2]
311 elif ref1 == "1":
312 center_z = ref_points[7]
313 new_z = ref2_co[2] + (obj.location[2] - center_z) + loc_offset[2]
314 elif ref1 == "2":
315 new_z = ref2_co[2] + loc_offset[2]
316 elif ref1 == "3":
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):
322 if rot_x is True:
323 obj.rotation_euler[0] = act_obj.rotation_euler[0] + rot_offset[0]
324 if rot_y is True:
325 obj.rotation_euler[1] = act_obj.rotation_euler[1] + rot_offset[1]
326 if rot_z is True:
327 obj.rotation_euler[2] = act_obj.rotation_euler[2] + rot_offset[2]
329 def find_new_scale(obj):
330 if scale_x is True:
331 obj.scale[0] = act_obj.scale[0] + scale_offset[0]
332 if scale_y is True:
333 obj.scale[1] = act_obj.scale[1] + scale_offset[1]
334 if scale_z is True:
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")
339 if fit_x:
340 dim = ref_points[2] - ref_points[0]
341 obj.scale[0] = (ref_dim[0] / dim) * act_obj.scale[0]
342 if fit_y:
343 dim = ref_points[5] - ref_points[3]
344 obj.scale[1] = (ref_dim[1] / dim) * act_obj.scale[1]
345 if fit_z:
346 dim = ref_points[8] - ref_points[6]
347 obj.scale[2] = (ref_dim[2] / dim) * act_obj.scale[2]
349 def move_pivot(obj):
350 me = obj.data
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]]
357 def movement(vec):
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)
363 pivot = obj.location
364 pivot += move_pivot
366 nm = obj_mtx.inverted() @ Matrix.Translation(-move_pivot) @ obj_mtx
368 # Transform the mesh now
369 me.transform(nm)
371 if loc_x:
372 movement(offset_x)
373 if loc_y:
374 movement(offset_y)
375 if loc_z:
376 movement(offset_z)
378 def point_in_selection(act_obj, sel_obj):
379 ok = False
380 for o in sel_obj:
381 if o != act_obj:
382 ref_ob = o
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
387 ok = True
388 break
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
392 ok = True
393 break
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
397 ok = True
398 break
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
402 ok = True
403 break
404 # if no object had data, use the position of an object that was not active as an internal
405 # point of selection
406 if ok is False:
407 ref_co = ref_ob.matrix_world.translation
409 return ref_co
411 if subject == "0":
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)
415 else:
416 if ref2 == "4":
417 ref2_co = bpy.context.scene.cursor.location
418 else:
419 ref2_co = act_obj.matrix_world.translation
421 # in the case of substantial selection
422 if consistent:
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
432 if ref1 == "0":
433 translate = ref2_co - sel_min + loc_offset
434 elif ref1 == "1":
435 translate = ref2_co - sel_center + loc_offset
436 elif ref1 == "3":
437 translate = ref2_co - sel_max + loc_offset
439 # Move the various objects
440 for obj in sel_obj:
442 if obj != act_obj or (active_too and obj == act_obj):
444 if loc_x:
445 obj.location[0] += translate[0]
446 if loc_y:
447 obj.location[1] += translate[1]
448 if loc_z:
449 obj.location[2] += translate[2]
450 else: # not consistent
451 for obj in sel_obj:
452 if obj != act_obj:
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:
457 dim = [0, 0, 0]
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:
465 find_new_scale(obj)
467 if loc_x or loc_y or loc_z:
468 # print("ehy", ref2_co)
469 find_new_coord(obj)
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
480 elif subject == "1":
481 if self_or_active == "1":
482 if act_obj.type == 'MESH':
483 ref2_co = find_ref2_co(act_obj)
484 for obj in sel_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':
489 move_pivot(obj)
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)
496 move_pivot(act_obj)
498 elif subject == "2":
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")
503 else:
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]]
509 if ref2 == "0":
510 if loc_x is True:
511 bpy.context.scene.cursor.location[0] = ref_points[0] + loc_offset[0]
512 if loc_y is True:
513 bpy.context.scene.cursor.location[1] = ref_points[3] + loc_offset[1]
514 if loc_z is True:
515 bpy.context.scene.cursor.location[2] = ref_points[6] + loc_offset[2]
516 elif ref2 == "1":
517 if loc_x is True:
518 bpy.context.scene.cursor.location[0] = ref_points[1] + loc_offset[0]
519 if loc_y is True:
520 bpy.context.scene.cursor.location[1] = ref_points[4] + loc_offset[1]
521 if loc_z is True:
522 bpy.context.scene.cursor.location[2] = ref_points[7] + loc_offset[2]
523 elif ref2 == "2":
524 if loc_x is True:
525 bpy.context.scene.cursor.location[0] = act_obj.location[0] + loc_offset[0]
526 if loc_y is True:
527 bpy.context.scene.cursor.location[1] = act_obj.location[1] + loc_offset[1]
528 if loc_z is True:
529 bpy.context.scene.cursor.location[2] = act_obj.location[2] + loc_offset[2]
530 elif ref2 == "3":
531 if loc_x is True:
532 bpy.context.scene.cursor.location[0] = ref_points[2] + loc_offset[0]
533 if loc_y is True:
534 bpy.context.scene.cursor.location[1] = ref_points[5] + loc_offset[1]
535 if loc_z is True:
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)
543 if ref2 == "0":
544 if loc_x is True:
545 bpy.context.scene.cursor.location[0] = sel_min[0] + loc_offset[0]
546 if loc_y is True:
547 bpy.context.scene.cursor.location[1] = sel_min[1] + loc_offset[1]
548 if loc_z is True:
549 bpy.context.scene.cursor.location[2] = sel_min[2] + loc_offset[2]
550 elif ref2 == "1":
551 if loc_x is True:
552 bpy.context.scene.cursor.location[0] = sel_center[0] + loc_offset[0]
553 if loc_y is True:
554 bpy.context.scene.cursor.location[1] = sel_center[1] + loc_offset[1]
555 if loc_z is True:
556 bpy.context.scene.cursor.location[2] = sel_center[2] + loc_offset[2]
557 elif ref2 == "3":
558 if loc_x is True:
559 bpy.context.scene.cursor.location[0] = sel_max[0] + loc_offset[0]
560 if loc_y is True:
561 bpy.context.scene.cursor.location[1] = sel_max[1] + loc_offset[1]
562 if loc_z is True:
563 bpy.context.scene.cursor.location[2] = sel_max[2] + loc_offset[2]
566 # Classes #
568 # Advanced Align
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")),
582 name="Align To",
583 description="What will be moved"
585 # Move active Too:
586 active_too: BoolProperty(
587 name="Active too",
588 default=False,
589 description="Move the active object too"
591 # advanced options
592 advanced: BoolProperty(
593 name="Advanced Options",
594 default=False,
595 description="Show advanced options"
597 consistent: BoolProperty(
598 name="Consistent Selection",
599 default=False,
600 description="Use consistent selection"
602 # Align Location:
603 loc_x: BoolProperty(
604 name="Align to X axis",
605 default=False,
606 description="Enable X axis alignment"
608 loc_y: BoolProperty(
609 name="Align to Y axis",
610 default=False,
611 description="Enable Y axis alignment"
613 loc_z: BoolProperty(
614 name="Align to Z axis",
615 default=False,
616 description="Enable Z axis alignment"
618 # Selection Option:
619 ref1: EnumProperty(
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:
628 ref2: EnumProperty(
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")),
641 name="Relation",
642 default="1",
643 description="To what the pivot will be aligned"
645 # Location Offset
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
652 # Rotation Offset
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
659 # Scale Offset
660 scale_offset: FloatVectorProperty(
661 name="Scale Offset",
662 description="Offset for scale match",
663 default=(0.0, 0.0, 0.0),
664 subtype='XYZ', size=3
666 # Fit Dimension Prop:
667 fit_x: BoolProperty(
668 name="Fit Dimension to X axis",
669 default=False,
670 description=""
672 fit_y: BoolProperty(
673 name="Fit Dimension to Y axis",
674 default=False,
675 description=""
677 fit_z: BoolProperty(
678 name="Fit Dimension to Z axis",
679 default=False,
680 description=""
682 # Apply Fit Dimension:
683 apply_dim: BoolProperty(
684 name="Apply Dimension",
685 default=False,
686 description=""
688 # Align Rot Prop:
689 rot_x: BoolProperty(
690 name="Align Rotation to X axis",
691 default=False,
692 description=""
694 rot_y: BoolProperty(
695 name="Align Rotation to Y axis",
696 default=False,
697 description=""
699 rot_z: BoolProperty(
700 name="Align Rotation to Z axis",
701 default=False,
702 description=""
704 # Apply Rot:
705 apply_rot: BoolProperty(
706 name="Apply Rotation",
707 default=False,
708 description=""
710 # Align Scale:
711 scale_x: BoolProperty(
712 name="Match Scale to X axis",
713 default=False,
714 description=""
716 scale_y: BoolProperty(
717 name="Match Scale to Y axis",
718 default=False,
719 description=""
721 scale_z: BoolProperty(
722 name="match Scale to Z axis",
723 default=False,
724 description=""
726 # Apply Scale:
727 apply_scale: BoolProperty(
728 name="Apply Scale",
729 default=False,
730 description=""
733 def draw(self, context):
734 layout = self.layout
735 obj = context.object
736 row = layout.row()
737 row.label(text="Active object is: ", icon='OBJECT_DATA')
738 box = layout.box()
739 box.label(text=obj.name, icon='EDITMODE_HLT')
740 # Object-Pivot-Cursor:
741 row0 = layout.row()
742 row0.prop(self, 'subject', expand=True)
744 # Move active Too:
745 row1 = layout.row()
746 row1.prop(self, 'active_too')
747 row1.prop(self, 'advanced')
748 if self.advanced:
749 row1b = layout.row()
750 row1b.prop(self, 'consistent')
752 row2 = layout.row()
753 row2.label(text="Align Location:")
755 # Align Location:
756 row3 = layout.row()
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)
761 # Offset:
762 if self.advanced is True:
763 # row8 = col.row()
764 # row8.label(text='Location Offset')
765 row9 = layout.row()
766 row9.prop(self, 'loc_offset', text='')
768 # Selection Options
769 if self.advanced is True:
770 sel = bpy.context.selected_objects
771 sel_obs = len(sel)
772 if sel_obs != 0:
773 row4 = layout.row()
774 row4.label(text="Selected: " + str(sel_obs) + " Objects", icon='OBJECT_DATA')
775 if self.subject == "1" or self.subject == "2":
776 row5b = layout.row()
777 row5b.prop(self, 'self_or_active', expand=True)
778 else:
779 row5 = layout.row()
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:
786 if act:
787 row6 = layout.row()
788 row6.label(text="Active: " + act.name, icon='OBJECT_DATA')
789 row7 = layout.row()
790 row7.prop(self, 'ref2', expand=True)
792 if self.subject == "0":
793 row12 = layout.row()
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='')
804 row14 = layout.row()
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='')
815 row10 = layout.row()
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):
824 align_function(
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
833 return {'FINISHED'}
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"
844 @classmethod
845 def poll(cls, context):
846 return context.active_object is not None
848 def execute(self, context):
849 main(context)
850 return {'FINISHED'}
853 # Align Location All
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"
859 @classmethod
860 def poll(cls, context):
861 return context.active_object is not None
863 def execute(self, context):
864 LocAll(context)
865 return {'FINISHED'}
868 # Align Location X
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"
874 @classmethod
875 def poll(cls, context):
876 return context.active_object is not None
878 def execute(self, context):
879 LocX(context)
880 return {'FINISHED'}
883 # Align Location Y
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"
889 @classmethod
890 def poll(cls, context):
891 return context.active_object is not None
893 def execute(self, context):
894 LocY(context)
895 return {'FINISHED'}
898 # Align LocationZ
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"
904 @classmethod
905 def poll(cls, context):
906 return context.active_object is not None
908 def execute(self, context):
909 LocZ(context)
910 return {'FINISHED'}
913 # Align Rotation All
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"
919 @classmethod
920 def poll(cls, context):
921 return context.active_object is not None
923 def execute(self, context):
924 RotAll(context)
925 return {'FINISHED'}
928 # Align Rotation X
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"
934 @classmethod
935 def poll(cls, context):
936 return context.active_object is not None
938 def execute(self, context):
939 RotX(context)
940 return {'FINISHED'}
943 # Align Rotation Y
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"
949 @classmethod
950 def poll(cls, context):
951 return context.active_object is not None
953 def execute(self, context):
954 RotY(context)
955 return {'FINISHED'}
958 # Align Rotation Z
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"
964 @classmethod
965 def poll(cls, context):
966 return context.active_object is not None
968 def execute(self, context):
969 RotZ(context)
970 return {'FINISHED'}
973 # Scale All
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"
979 @classmethod
980 def poll(cls, context):
981 return context.active_object is not None
983 def execute(self, context):
984 ScaleAll(context)
985 return {'FINISHED'}
988 # Align Scale X
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"
994 @classmethod
995 def poll(cls, context):
996 return context.active_object is not None
998 def execute(self, context):
999 ScaleX(context)
1000 return {'FINISHED'}
1003 # Align Scale Y
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"
1009 @classmethod
1010 def poll(cls, context):
1011 return context.active_object is not None
1013 def execute(self, context):
1014 ScaleY(context)
1015 return {'FINISHED'}
1018 # Align Scale Z
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"
1024 @classmethod
1025 def poll(cls, context):
1026 return context.active_object is not None
1028 def execute(self, context):
1029 ScaleZ(context)
1030 return {'FINISHED'}
1033 # Interface Panel
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
1047 if obj is not None:
1048 row = layout.row()
1049 row.label(text="Active object is: ", icon='OBJECT_DATA')
1050 box = layout.box()
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")
1086 if obj is not None:
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
1096 panels = (
1097 VIEW3D_PT_AlignUi,
1101 def update_panel(self, context):
1102 message = "Align Tools: Updating Panel locations has failed"
1103 try:
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))
1114 pass
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",
1125 default="Item",
1126 update=update_panel
1129 def draw(self, context):
1130 layout = self.layout
1132 row = layout.row()
1133 col = row.column()
1134 col.label(text="Tab Category:")
1135 col.prop(self, "category", text="")
1138 # Class List
1139 classes = (
1140 VIEW3D_PT_AlignUi,
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
1160 def register():
1161 for cls in classes:
1162 bpy.utils.register_class(cls)
1165 def unregister():
1166 for cls in classes:
1167 bpy.utils.unregister_class(cls)
1170 if __name__ == "__main__":
1171 register()