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