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