Fix #104531: DXF importer zero division error
[blender-addons.git] / space_view3d_align_tools.py
blob247e47f883830ad3845a6ee5ee390a7453b7c727
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', 'PRESET'}
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"
826 bl_options = {'REGISTER', 'UNDO'}
828 @classmethod
829 def poll(cls, context):
830 return context.active_object is not None
832 def execute(self, context):
833 main(context)
834 return {'FINISHED'}
837 # Align Location All
838 class OBJECT_OT_AlignLocationOperator(Operator):
839 bl_idname = "object.align_location_all"
840 bl_label = "Align Selected Location To Active"
841 bl_description = "Align Selected Location To Active"
842 bl_options = {'REGISTER', 'UNDO'}
844 @classmethod
845 def poll(cls, context):
846 return context.active_object is not None
848 def execute(self, context):
849 LocAll(context)
850 return {'FINISHED'}
853 # Align Location X
854 class OBJECT_OT_AlignLocationXOperator(Operator):
855 bl_idname = "object.align_location_x"
856 bl_label = "Align Selected Location X To Active"
857 bl_description = "Align Selected Location X To Active"
858 bl_options = {'REGISTER', 'UNDO'}
860 @classmethod
861 def poll(cls, context):
862 return context.active_object is not None
864 def execute(self, context):
865 LocX(context)
866 return {'FINISHED'}
869 # Align Location Y
870 class OBJECT_OT_AlignLocationYOperator(Operator):
871 bl_idname = "object.align_location_y"
872 bl_label = "Align Selected Location Y To Active"
873 bl_description = "Align Selected Location Y To Active"
874 bl_options = {'REGISTER', 'UNDO'}
876 @classmethod
877 def poll(cls, context):
878 return context.active_object is not None
880 def execute(self, context):
881 LocY(context)
882 return {'FINISHED'}
885 # Align LocationZ
886 class OBJECT_OT_AlignLocationZOperator(Operator):
887 bl_idname = "object.align_location_z"
888 bl_label = "Align Selected Location Z To Active"
889 bl_description = "Align Selected Location Z To Active"
890 bl_options = {'REGISTER', 'UNDO'}
892 @classmethod
893 def poll(cls, context):
894 return context.active_object is not None
896 def execute(self, context):
897 LocZ(context)
898 return {'FINISHED'}
901 # Align Rotation All
902 class OBJECT_OT_AlignRotationOperator(Operator):
903 bl_idname = "object.align_rotation_all"
904 bl_label = "Align Selected Rotation To Active"
905 bl_description = "Align Selected Rotation To Active"
906 bl_options = {'REGISTER', 'UNDO'}
908 @classmethod
909 def poll(cls, context):
910 return context.active_object is not None
912 def execute(self, context):
913 RotAll(context)
914 return {'FINISHED'}
917 # Align Rotation X
918 class OBJECT_OT_AlignRotationXOperator(Operator):
919 bl_idname = "object.align_rotation_x"
920 bl_label = "Align Selected Rotation X To Active"
921 bl_description = "Align Selected Rotation X To Active"
922 bl_options = {'REGISTER', 'UNDO'}
924 @classmethod
925 def poll(cls, context):
926 return context.active_object is not None
928 def execute(self, context):
929 RotX(context)
930 return {'FINISHED'}
933 # Align Rotation Y
934 class OBJECT_OT_AlignRotationYOperator(Operator):
935 bl_idname = "object.align_rotation_y"
936 bl_label = "Align Selected Rotation Y To Active"
937 bl_description = "Align Selected Rotation Y To Active"
938 bl_options = {'REGISTER', 'UNDO'}
940 @classmethod
941 def poll(cls, context):
942 return context.active_object is not None
944 def execute(self, context):
945 RotY(context)
946 return {'FINISHED'}
949 # Align Rotation Z
950 class OBJECT_OT_AlignRotationZOperator(Operator):
951 bl_idname = "object.align_rotation_z"
952 bl_label = "Align Selected Rotation Z To Active"
953 bl_description = "Align Selected Rotation Z To Active"
954 bl_options = {'REGISTER', 'UNDO'}
956 @classmethod
957 def poll(cls, context):
958 return context.active_object is not None
960 def execute(self, context):
961 RotZ(context)
962 return {'FINISHED'}
965 # Scale All
966 class OBJECT_OT_AlignScaleOperator(Operator):
967 bl_idname = "object.align_objects_scale_all"
968 bl_label = "Align Selected Scale To Active"
969 bl_description = "Align Selected Scale To Active"
970 bl_options = {'REGISTER', 'UNDO'}
972 @classmethod
973 def poll(cls, context):
974 return context.active_object is not None
976 def execute(self, context):
977 ScaleAll(context)
978 return {'FINISHED'}
981 # Align Scale X
982 class OBJECT_OT_AlignScaleXOperator(Operator):
983 bl_idname = "object.align_objects_scale_x"
984 bl_label = "Align Selected Scale X To Active"
985 bl_description = "Align Selected Scale X To Active"
986 bl_options = {'REGISTER', 'UNDO'}
988 @classmethod
989 def poll(cls, context):
990 return context.active_object is not None
992 def execute(self, context):
993 ScaleX(context)
994 return {'FINISHED'}
997 # Align Scale Y
998 class OBJECT_OT_AlignScaleYOperator(Operator):
999 bl_idname = "object.align_objects_scale_y"
1000 bl_label = "Align Selected Scale Y To Active"
1001 bl_description = "Align Selected Scale Y To Active"
1002 bl_options = {'REGISTER', 'UNDO'}
1004 @classmethod
1005 def poll(cls, context):
1006 return context.active_object is not None
1008 def execute(self, context):
1009 ScaleY(context)
1010 return {'FINISHED'}
1013 # Align Scale Z
1014 class OBJECT_OT_AlignScaleZOperator(Operator):
1015 bl_idname = "object.align_objects_scale_z"
1016 bl_label = "Align Selected Scale Z To Active"
1017 bl_description = "Align Selected Scale Z To Active"
1018 bl_options = {'REGISTER', 'UNDO'}
1020 @classmethod
1021 def poll(cls, context):
1022 return context.active_object is not None
1024 def execute(self, context):
1025 ScaleZ(context)
1026 return {'FINISHED'}
1029 # Interface Panel
1031 class VIEW3D_PT_AlignUi(Panel):
1032 bl_space_type = 'VIEW_3D'
1033 bl_region_type = 'UI'
1034 bl_label = "Align Tools"
1035 bl_context = "objectmode"
1036 bl_category = 'Item'
1037 bl_options = {'DEFAULT_CLOSED'}
1039 def draw(self, context):
1040 layout = self.layout
1041 obj = context.object
1043 if obj is not None:
1044 row = layout.row()
1045 row.label(text="Active object is: ", icon='OBJECT_DATA')
1046 box = layout.box()
1047 box.label(text=obj.name, icon='EDITMODE_HLT')
1049 col = layout.column()
1050 col.label(text="Align Loc + Rot:")
1052 col = layout.column(align=False)
1053 col.operator("object.align", text="XYZ")
1055 col = layout.column()
1056 col.label(text="Align Location:")
1058 col = layout.column_flow(columns=4, align=True)
1059 col.operator("object.align_location_x", text="X")
1060 col.operator("object.align_location_y", text="Y")
1061 col.operator("object.align_location_z", text="Z")
1062 col.operator("object.align_location_all", text="All")
1064 col = layout.column()
1065 col.label(text="Align Rotation:")
1067 col = layout.column_flow(columns=4, align=True)
1068 col.operator("object.align_rotation_x", text="X")
1069 col.operator("object.align_rotation_y", text="Y")
1070 col.operator("object.align_rotation_z", text="Z")
1071 col.operator("object.align_rotation_all", text="All")
1073 col = layout.column()
1074 col.label(text="Align Scale:")
1076 col = layout.column_flow(columns=4, align=True)
1077 col.operator("object.align_objects_scale_x", text="X")
1078 col.operator("object.align_objects_scale_y", text="Y")
1079 col.operator("object.align_objects_scale_z", text="Z")
1080 col.operator("object.align_objects_scale_all", text="All")
1082 if obj is not None:
1083 col = layout.column()
1084 col.label(text="Advanced Align Operations")
1085 layout = self.layout
1086 self.layout.operator("object.align_tools", text="Advanced")
1089 # Add-ons Preferences Update Panel
1091 # Define Panel classes for updating
1092 panels = (
1093 VIEW3D_PT_AlignUi,
1097 def update_panel(self, context):
1098 message = "Align Tools: Updating Panel locations has failed"
1099 try:
1100 for panel in panels:
1101 if "bl_rna" in panel.__dict__:
1102 bpy.utils.unregister_class(panel)
1104 for panel in panels:
1105 panel.bl_category = context.preferences.addons[__name__].preferences.category
1106 bpy.utils.register_class(panel)
1108 except Exception as e:
1109 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
1110 pass
1113 class AlignAddonPreferences(AddonPreferences):
1114 # this must match the addon name, use '__package__'
1115 # when defining this in a submodule of a python package.
1116 bl_idname = __name__
1118 category: StringProperty(
1119 name="Tab Category",
1120 description="Choose a name for the category of the panel",
1121 default="Item",
1122 update=update_panel
1125 def draw(self, context):
1126 layout = self.layout
1128 row = layout.row()
1129 col = row.column()
1130 col.label(text="Tab Category:")
1131 col.prop(self, "category", text="")
1134 # Class List
1135 classes = (
1136 VIEW3D_PT_AlignUi,
1137 OBJECT_OT_AlignOperator,
1138 OBJECT_OT_AlignLocationOperator,
1139 OBJECT_OT_AlignLocationXOperator,
1140 OBJECT_OT_AlignLocationYOperator,
1141 OBJECT_OT_AlignLocationZOperator,
1142 OBJECT_OT_AlignRotationOperator,
1143 OBJECT_OT_AlignRotationXOperator,
1144 OBJECT_OT_AlignRotationYOperator,
1145 OBJECT_OT_AlignRotationZOperator,
1146 OBJECT_OT_AlignScaleOperator,
1147 OBJECT_OT_AlignScaleXOperator,
1148 OBJECT_OT_AlignScaleYOperator,
1149 OBJECT_OT_AlignScaleZOperator,
1150 OBJECT_OT_align_tools,
1151 AlignAddonPreferences,
1155 # Register all operators and panels
1156 def register():
1157 for cls in classes:
1158 bpy.utils.register_class(cls)
1161 def unregister():
1162 for cls in classes:
1163 bpy.utils.unregister_class(cls)
1166 if __name__ == "__main__":
1167 register()