animation_animall: workaround for data refresh: T68332 T68666
[blender-addons.git] / space_view3d_align_tools.py
blobb8c02968bfea0bce1c897f1f97158d9ee0c377c9
1 # -*- coding: utf-8 -*-
2 # ##### BEGIN GPL LICENSE BLOCK #####
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ##### END GPL LICENSE BLOCK #####
19 # Contributed to by gabhead, Lell, Anfeo, meta-androcto
21 bl_info = {
22 "name": "Align Tools",
23 "author": "gabhead, Lell, Anfeo",
24 "version": (0, 3, 4),
25 "blender": (2, 80, 0),
26 "location": "View3D > Sidebar > Item Tab",
27 "description": "Align Selected Objects to Active Object",
28 "warning": "",
29 "wiki_url": "https://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",
35 import bpy
36 from bpy.types import (
37 Operator,
38 Panel,
39 AddonPreferences,
41 from bpy.props import (
42 EnumProperty,
43 BoolProperty,
44 FloatVectorProperty,
45 StringProperty,
47 from mathutils import (
48 Vector,
49 Matrix,
53 # Simple Align Defs #
55 # Align all
56 def main(context):
57 for i in bpy.context.selected_objects:
58 i.matrix_world.translation = bpy.context.active_object.matrix_world.translation.copy()
59 i.rotation_euler = bpy.context.active_object.rotation_euler
62 # Align Location
63 def LocAll(context):
64 for i in bpy.context.selected_objects:
65 i.matrix_world.translation = bpy.context.active_object.matrix_world.translation.copy()
68 def LocX(context):
69 for i in bpy.context.selected_objects:
70 i.matrix_world.translation.x = bpy.context.active_object.matrix_world.translation.x
73 def LocY(context):
74 for i in bpy.context.selected_objects:
75 i.matrix_world.translation.y = bpy.context.active_object.matrix_world.translation.y
78 def LocZ(context):
79 for i in bpy.context.selected_objects:
80 i.matrix_world.translation.z = bpy.context.active_object.matrix_world.translation.z
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
89 def RotX(context):
90 for i in bpy.context.selected_objects:
91 i.rotation_euler.x = bpy.context.active_object.rotation_euler.x
94 def RotY(context):
95 for i in bpy.context.selected_objects:
96 i.rotation_euler.y = bpy.context.active_object.rotation_euler.y
99 def RotZ(context):
100 for i in bpy.context.selected_objects:
101 i.rotation_euler.z = bpy.context.active_object.rotation_euler.z
104 # Align Scale
105 def ScaleAll(context):
106 for i in bpy.context.selected_objects:
107 i.scale = bpy.context.active_object.scale
110 def ScaleX(context):
111 for i in bpy.context.selected_objects:
112 i.scale.x = bpy.context.active_object.scale.x
115 def ScaleY(context):
116 for i in bpy.context.selected_objects:
117 i.scale.y = bpy.context.active_object.scale.y
120 def ScaleZ(context):
121 for i in bpy.context.selected_objects:
122 i.scale.z = bpy.context.active_object.scale.z
125 # Advanced Align Defs #
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):
132 sel_obj = bpy.context.selected_objects
133 act_obj = bpy.context.active_object
135 global sel_max
136 global sel_min
137 global sel_center
138 global ref2_co
140 def get_reference_points(obj, space):
142 me = obj.data
143 co_list = []
144 # let's get all the points coordinates
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))
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))
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)
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)
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]
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
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
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
213 else:
214 # otherwise use the pivot object
215 a = obj.matrix_world.translation
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]
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)
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
230 def get_sel_ref(ref_co, sel_obj): # I look for the selection end points
232 sel_min = ref_co.copy()
233 sel_max = ref_co.copy()
235 for obj in sel_obj:
236 if obj != act_obj or (active_too and obj == act_obj):
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]])
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]
255 return sel_min, sel_max
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
277 return ref2_co
279 def find_new_coord(obj):
281 ref_points = get_reference_points(obj, "global")
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.matrix_world.translation[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.matrix_world.translation[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.matrix_world.translation[2] = new_z
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]
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]
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]
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]]
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)
364 # Move the pivot point (which is the object's location)
365 pivot = obj.location
366 pivot += move_pivot
368 nm = obj_mtx.inverted() @ Matrix.Translation(-move_pivot) @ obj_mtx
370 # Transform the mesh now
371 me.transform(nm)
373 if loc_x:
374 movement(offset_x)
375 if loc_y:
376 movement(offset_y)
377 if loc_z:
378 movement(offset_z)
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.matrix_world.translation
411 return ref_co
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.matrix_world.translation
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)
428 sel_min, sel_max = get_sel_ref(ref_co, sel_obj)
430 sel_center = sel_min + ((sel_max - sel_min) / 2)
431 translate = [0, 0, 0]
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
441 # Move the various objects
442 for obj in sel_obj:
444 if obj != act_obj or (active_too and obj == act_obj):
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)
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)
466 if scale_x or scale_y or scale_z:
467 find_new_scale(obj)
469 if loc_x or loc_y or loc_z:
470 # print("ehy", ref2_co)
471 find_new_coord(obj)
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
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)
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)
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.matrix_world.translation
507 ref_points = [ref2_co[0], ref2_co[0], ref2_co[0],
508 ref2_co[1], ref2_co[1], ref2_co[1],
509 ref2_co[2], ref2_co[2], ref2_co[2]]
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)
542 sel_min, sel_max = get_sel_ref(ref_co, sel_obj)
543 sel_center = sel_min + ((sel_max - sel_min) / 2)
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]
568 # Classes #
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'}
577 # property definitions
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"
587 # Move active Too:
588 active_too: BoolProperty(
589 name="Active too",
590 default=False,
591 description="Move the active object too"
593 # advanced options
594 advanced: BoolProperty(
595 name="Advanced Options",
596 default=False,
597 description="Show advanced options"
599 consistent: BoolProperty(
600 name="Consistent Selection",
601 default=False,
602 description="Use consistent selection"
604 # Align Location:
605 loc_x: BoolProperty(
606 name="Align to X axis",
607 default=False,
608 description="Enable X axis alignment"
610 loc_y: BoolProperty(
611 name="Align to Y axis",
612 default=False,
613 description="Enable Y axis alignment"
615 loc_z: BoolProperty(
616 name="Align to Z axis",
617 default=False,
618 description="Enable Z axis alignment"
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"
629 # Active Object 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"
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"
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
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
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
668 # Fit Dimension Prop:
669 fit_x: BoolProperty(
670 name="Fit Dimension to X axis",
671 default=False,
672 description=""
674 fit_y: BoolProperty(
675 name="Fit Dimension to Y axis",
676 default=False,
677 description=""
679 fit_z: BoolProperty(
680 name="Fit Dimension to Z axis",
681 default=False,
682 description=""
684 # Apply Fit Dimension:
685 apply_dim: BoolProperty(
686 name="Apply Dimension",
687 default=False,
688 description=""
690 # Align Rot Prop:
691 rot_x: BoolProperty(
692 name="Align Rotation to X axis",
693 default=False,
694 description=""
696 rot_y: BoolProperty(
697 name="Align Rotation to Y axis",
698 default=False,
699 description=""
701 rot_z: BoolProperty(
702 name="Align Rotation to Z axis",
703 default=False,
704 description=""
706 # Apply Rot:
707 apply_rot: BoolProperty(
708 name="Apply Rotation",
709 default=False,
710 description=""
712 # Align Scale:
713 scale_x: BoolProperty(
714 name="Match Scale to X axis",
715 default=False,
716 description=""
718 scale_y: BoolProperty(
719 name="Match Scale to Y axis",
720 default=False,
721 description=""
723 scale_z: BoolProperty(
724 name="match Scale to Z axis",
725 default=False,
726 description=""
728 # Apply Scale:
729 apply_scale: BoolProperty(
730 name="Apply Scale",
731 default=False,
732 description=""
735 def draw(self, context):
736 layout = self.layout
737 obj = context.object
738 row = layout.row()
739 row.label(text="Active object is: ", icon='OBJECT_DATA')
740 box = layout.box()
741 box.label(text=obj.name, icon='EDITMODE_HLT')
742 # Object-Pivot-Cursor:
743 row0 = layout.row()
744 row0.prop(self, 'subject', expand=True)
746 # Move active Too:
747 row1 = layout.row()
748 row1.prop(self, 'active_too')
749 row1.prop(self, 'advanced')
750 if self.advanced:
751 row1b = layout.row()
752 row1b.prop(self, 'consistent')
754 row2 = layout.row()
755 row2.label(text="Align Location:")
757 # Align Location:
758 row3 = layout.row()
759 row3.prop(self, "loc_x", text="X", toggle=True)
760 row3.prop(self, "loc_y", text="Y", toggle=True)
761 row3.prop(self, "loc_z", text="Z", toggle=True)
763 # Offset:
764 if self.advanced is True:
765 # row8 = col.row()
766 # row8.label(text='Location Offset')
767 row9 = layout.row()
768 row9.prop(self, 'loc_offset', text='')
770 # Selection Options
771 if self.advanced is True:
772 sel = bpy.context.selected_objects
773 sel_obs = len(sel)
774 if sel_obs != 0:
775 row4 = layout.row()
776 row4.label(text="Selected: " + str(sel_obs) + " Objects", icon='OBJECT_DATA')
777 if self.subject == "1" or self.subject == "2":
778 row5b = layout.row()
779 row5b.prop(self, 'self_or_active', expand=True)
780 else:
781 row5 = layout.row()
782 row5.prop(self, 'ref1', expand=True)
784 # Active Object Options: Number of select objects
785 act = bpy.context.active_object
787 if self.advanced is True:
788 if act:
789 row6 = layout.row()
790 row6.label(text="Active: " + act.name, icon='OBJECT_DATA')
791 row7 = layout.row()
792 row7.prop(self, 'ref2', expand=True)
794 if self.subject == "0":
795 row12 = layout.row()
796 row12.label(text='Align Rotation:')
797 row13 = layout.row(align=True)
798 row13.prop(self, 'rot_x', text='X', toggle=True)
799 row13.prop(self, 'rot_y', text='Y', toggle=True)
800 row13.prop(self, 'rot_z', text='Z', toggle=True)
801 row13.prop(self, 'apply_rot', text='Apply', toggle=True)
802 if self.advanced is True:
803 row13b = layout.row()
804 row13b.prop(self, 'rot_offset', text='')
806 row14 = layout.row()
807 row14.label(text='Match Scale:')
808 row15 = layout.row(align=True)
809 row15.prop(self, 'scale_x', text='X', toggle=True)
810 row15.prop(self, 'scale_y', text='Y', toggle=True)
811 row15.prop(self, 'scale_z', text='Z', toggle=True)
812 row15.prop(self, 'apply_scale', text='Apply', toggle=True)
813 if self.advanced is True:
814 row15b = layout.row()
815 row15b.prop(self, 'scale_offset', text='')
817 row10 = layout.row()
818 row10.label(text='Fit Dimensions:')
819 row11 = layout.row(align=True)
820 row11.prop(self, 'fit_x', text='X', toggle=True)
821 row11.prop(self, 'fit_y', text='Y', toggle=True)
822 row11.prop(self, 'fit_z', text='Z', toggle=True)
823 row11.prop(self, 'apply_dim', text='Apply', toggle=True)
825 def execute(self, context):
826 align_function(
827 self.subject, self.active_too, self.consistent,
828 self.self_or_active, self.loc_x, self.loc_y, self.loc_z,
829 self.ref1, self.ref2, self.loc_offset,
830 self.rot_x, self.rot_y, self.rot_z, self.rot_offset,
831 self.scale_x, self.scale_y, self.scale_z, self.scale_offset,
832 self.fit_x, self.fit_y, self.fit_z
835 return {'FINISHED'}
838 # Simple Align Classes #
840 # Align All Rotation And Location
841 class OBJECT_OT_AlignOperator(Operator):
842 bl_idname = "object.align"
843 bl_label = "Align Selected To Active"
844 bl_description = "Align Selected To Active"
846 @classmethod
847 def poll(cls, context):
848 return context.active_object is not None
850 def execute(self, context):
851 main(context)
852 return {'FINISHED'}
855 # Align Location All
856 class OBJECT_OT_AlignLocationOperator(Operator):
857 bl_idname = "object.align_location_all"
858 bl_label = "Align Selected Location To Active"
859 bl_description = "Align Selected Location To Active"
861 @classmethod
862 def poll(cls, context):
863 return context.active_object is not None
865 def execute(self, context):
866 LocAll(context)
867 return {'FINISHED'}
870 # Align Location X
871 class OBJECT_OT_AlignLocationXOperator(Operator):
872 bl_idname = "object.align_location_x"
873 bl_label = "Align Selected Location X To Active"
874 bl_description = "Align Selected Location X To Active"
876 @classmethod
877 def poll(cls, context):
878 return context.active_object is not None
880 def execute(self, context):
881 LocX(context)
882 return {'FINISHED'}
885 # Align Location Y
886 class OBJECT_OT_AlignLocationYOperator(Operator):
887 bl_idname = "object.align_location_y"
888 bl_label = "Align Selected Location Y To Active"
889 bl_description = "Align Selected Location Y To Active"
891 @classmethod
892 def poll(cls, context):
893 return context.active_object is not None
895 def execute(self, context):
896 LocY(context)
897 return {'FINISHED'}
900 # Align LocationZ
901 class OBJECT_OT_AlignLocationZOperator(Operator):
902 bl_idname = "object.align_location_z"
903 bl_label = "Align Selected Location Z To Active"
904 bl_description = "Align Selected Location Z To Active"
906 @classmethod
907 def poll(cls, context):
908 return context.active_object is not None
910 def execute(self, context):
911 LocZ(context)
912 return {'FINISHED'}
915 # Align Rotation All
916 class OBJECT_OT_AlignRotationOperator(Operator):
917 bl_idname = "object.align_rotation_all"
918 bl_label = "Align Selected Rotation To Active"
919 bl_description = "Align Selected Rotation To Active"
921 @classmethod
922 def poll(cls, context):
923 return context.active_object is not None
925 def execute(self, context):
926 RotAll(context)
927 return {'FINISHED'}
930 # Align Rotation X
931 class OBJECT_OT_AlignRotationXOperator(Operator):
932 bl_idname = "object.align_rotation_x"
933 bl_label = "Align Selected Rotation X To Active"
934 bl_description = "Align Selected Rotation X To Active"
936 @classmethod
937 def poll(cls, context):
938 return context.active_object is not None
940 def execute(self, context):
941 RotX(context)
942 return {'FINISHED'}
945 # Align Rotation Y
946 class OBJECT_OT_AlignRotationYOperator(Operator):
947 bl_idname = "object.align_rotation_y"
948 bl_label = "Align Selected Rotation Y To Active"
949 bl_description = "Align Selected Rotation Y To Active"
951 @classmethod
952 def poll(cls, context):
953 return context.active_object is not None
955 def execute(self, context):
956 RotY(context)
957 return {'FINISHED'}
960 # Align Rotation Z
961 class OBJECT_OT_AlignRotationZOperator(Operator):
962 bl_idname = "object.align_rotation_z"
963 bl_label = "Align Selected Rotation Z To Active"
964 bl_description = "Align Selected Rotation Z To Active"
966 @classmethod
967 def poll(cls, context):
968 return context.active_object is not None
970 def execute(self, context):
971 RotZ(context)
972 return {'FINISHED'}
975 # Scale All
976 class OBJECT_OT_AlignScaleOperator(Operator):
977 bl_idname = "object.align_objects_scale_all"
978 bl_label = "Align Selected Scale To Active"
979 bl_description = "Align Selected Scale To Active"
981 @classmethod
982 def poll(cls, context):
983 return context.active_object is not None
985 def execute(self, context):
986 ScaleAll(context)
987 return {'FINISHED'}
990 # Align Scale X
991 class OBJECT_OT_AlignScaleXOperator(Operator):
992 bl_idname = "object.align_objects_scale_x"
993 bl_label = "Align Selected Scale X To Active"
994 bl_description = "Align Selected Scale X To Active"
996 @classmethod
997 def poll(cls, context):
998 return context.active_object is not None
1000 def execute(self, context):
1001 ScaleX(context)
1002 return {'FINISHED'}
1005 # Align Scale Y
1006 class OBJECT_OT_AlignScaleYOperator(Operator):
1007 bl_idname = "object.align_objects_scale_y"
1008 bl_label = "Align Selected Scale Y To Active"
1009 bl_description = "Align Selected Scale Y To Active"
1011 @classmethod
1012 def poll(cls, context):
1013 return context.active_object is not None
1015 def execute(self, context):
1016 ScaleY(context)
1017 return {'FINISHED'}
1020 # Align Scale Z
1021 class OBJECT_OT_AlignScaleZOperator(Operator):
1022 bl_idname = "object.align_objects_scale_z"
1023 bl_label = "Align Selected Scale Z To Active"
1024 bl_description = "Align Selected Scale Z To Active"
1026 @classmethod
1027 def poll(cls, context):
1028 return context.active_object is not None
1030 def execute(self, context):
1031 ScaleZ(context)
1032 return {'FINISHED'}
1035 # Interface Panel
1037 class VIEW3D_PT_AlignUi(Panel):
1038 bl_space_type = 'VIEW_3D'
1039 bl_region_type = 'UI'
1040 bl_label = "Align Tools"
1041 bl_context = "objectmode"
1042 bl_category = 'Item'
1043 bl_options = {'DEFAULT_CLOSED'}
1045 def draw(self, context):
1046 layout = self.layout
1047 obj = context.object
1049 if obj is not None:
1050 row = layout.row()
1051 row.label(text="Active object is: ", icon='OBJECT_DATA')
1052 box = layout.box()
1053 box.label(text=obj.name, icon='EDITMODE_HLT')
1055 col = layout.column()
1056 col.label(text="Align Loc + Rot:")
1058 col = layout.column(align=False)
1059 col.operator("object.align", text="XYZ")
1061 col = layout.column()
1062 col.label(text="Align Location:")
1064 col = layout.column_flow(columns=4, align=True)
1065 col.operator("object.align_location_x", text="X")
1066 col.operator("object.align_location_y", text="Y")
1067 col.operator("object.align_location_z", text="Z")
1068 col.operator("object.align_location_all", text="All")
1070 col = layout.column()
1071 col.label(text="Align Rotation:")
1073 col = layout.column_flow(columns=4, align=True)
1074 col.operator("object.align_rotation_x", text="X")
1075 col.operator("object.align_rotation_y", text="Y")
1076 col.operator("object.align_rotation_z", text="Z")
1077 col.operator("object.align_rotation_all", text="All")
1079 col = layout.column()
1080 col.label(text="Align Scale:")
1082 col = layout.column_flow(columns=4, align=True)
1083 col.operator("object.align_objects_scale_x", text="X")
1084 col.operator("object.align_objects_scale_y", text="Y")
1085 col.operator("object.align_objects_scale_z", text="Z")
1086 col.operator("object.align_objects_scale_all", text="All")
1088 if obj is not None:
1089 col = layout.column()
1090 col.label(text="Advanced Align Operations")
1091 layout = self.layout
1092 self.layout.operator("object.align_tools", text="Advanced")
1095 # Add-ons Preferences Update Panel
1097 # Define Panel classes for updating
1098 panels = (
1099 VIEW3D_PT_AlignUi,
1103 def update_panel(self, context):
1104 message = "Align Tools: Updating Panel locations has failed"
1105 try:
1106 for panel in panels:
1107 if "bl_rna" in panel.__dict__:
1108 bpy.utils.unregister_class(panel)
1110 for panel in panels:
1111 panel.bl_category = context.preferences.addons[__name__].preferences.category
1112 bpy.utils.register_class(panel)
1114 except Exception as e:
1115 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
1116 pass
1119 class AlignAddonPreferences(AddonPreferences):
1120 # this must match the addon name, use '__package__'
1121 # when defining this in a submodule of a python package.
1122 bl_idname = __name__
1124 category: StringProperty(
1125 name="Tab Category",
1126 description="Choose a name for the category of the panel",
1127 default="Item",
1128 update=update_panel
1131 def draw(self, context):
1132 layout = self.layout
1134 row = layout.row()
1135 col = row.column()
1136 col.label(text="Tab Category:")
1137 col.prop(self, "category", text="")
1140 # Class List
1141 classes = (
1142 VIEW3D_PT_AlignUi,
1143 OBJECT_OT_AlignOperator,
1144 OBJECT_OT_AlignLocationOperator,
1145 OBJECT_OT_AlignLocationXOperator,
1146 OBJECT_OT_AlignLocationYOperator,
1147 OBJECT_OT_AlignLocationZOperator,
1148 OBJECT_OT_AlignRotationOperator,
1149 OBJECT_OT_AlignRotationXOperator,
1150 OBJECT_OT_AlignRotationYOperator,
1151 OBJECT_OT_AlignRotationZOperator,
1152 OBJECT_OT_AlignScaleOperator,
1153 OBJECT_OT_AlignScaleXOperator,
1154 OBJECT_OT_AlignScaleYOperator,
1155 OBJECT_OT_AlignScaleZOperator,
1156 OBJECT_OT_align_tools,
1157 AlignAddonPreferences,
1161 # Register all operators and panels
1162 def register():
1163 for cls in classes:
1164 bpy.utils.register_class(cls)
1167 def unregister():
1168 for cls in classes:
1169 bpy.utils.unregister_class(cls)
1172 if __name__ == "__main__":
1173 register()