-
Notifications
You must be signed in to change notification settings - Fork 8
/
Easy_Align_Addon_ver_1_0_4.py
574 lines (485 loc) · 21 KB
/
Easy_Align_Addon_ver_1_0_4.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name" : "Easy Align",
"author":"Georges Dahdouh",
"version":(1,0,4),
"blender":(2,80,0),
"location":"View 3D > Properties",
"description":"Align mesh objects origins and mesh to selected axis",
"warning":"",
"category" : "Align"
}
import bpy
from bpy.props import *
Axis = [('0','X',""),
('1','Y',""),
('2','Z',"")
] # Items for enumerated list
ax_prop = EnumProperty \
(
items = Axis,
name ="",
description = "Set the Axis",
default = 2,
options = {'ANIMATABLE'}
) # Enumerated list to choose axis
snap_items = [('0','To Grid',''),
('1','To Cursor',''),
('2','To Cursor (offset)',''),
('3','To Active','')
] # Items for enumerated list
snap_enum = EnumProperty \
(
items = snap_items,
name ="",
description = "Snap to"
) # Enumerated property to choose a snapping method
invert_ax = BoolProperty \
(name = "Maximum",
description = "Set to Maximum",
default = False
) # Boolean checkbox for Maximum
invert_active_ax = BoolProperty \
(name = "Active Maximum",
description = "Set active to Maximum for Exclusively Align Objects",
default = True
) # Boolean property for Active Object Maximum
median_prop = BoolProperty \
(name = "Median",
description = "Align origin using median vertices coordniates",
default = False
) # Boolean property for Median
local_prop = BoolProperty \
(name = "Local",
description = "Use local coordinates, default is global",
default = False
) # Boolean property for Local
rot_prop = BoolProperty \
(name = "Rotate",
description = "Rotate selected",
default = False
) # Boolean property for Rotate
flex_prop = FloatProperty \
(name = "Flexibility",
description = "The higher the value, the more flexible alignment is in Blender units",
default = 0.01,
step = 1,
min = 0.0
) # Float property for Flexibility
force_ax = 0
class Easy_A_OP(bpy.types.Operator):
"""Set origin to selected axis"""
bl_idname = "object.easy_a"
bl_label = "Easy Align"
bl_options = {'REGISTER','UNDO'}
def execute(self,context):
lt_selected =[]
vlist = []
C = bpy.context
D = bpy.data
O = bpy.ops
area = context.area
areaAnchor = area.type # Assign the current area type to a variable
S = C.selected_objects
active_ob = C.active_object
ax_prop = int(context.object.set_enum_prop) # Define the axis selection enumerated property within function
invert_ax = context.object.set_maximum # Define the Maximum Boolean property within function
median_prop = context.object.set_median # Define the Median Boolean property within function
flex_prop = context.object.flexibility # Define the Flexibility Boolean property within function
local_prop = context.object.set_local # Define the Local Boolean property within function
rot_prop = context.object.set_rotation # Define the Rotate Boolean property within function
original_cursor = list(C.scene.cursor.location) # Assign the current location of 3D Cursor to a variable
if bpy.context.object.type == 'MESH':
current_m = context.object.mode # Assign the current mode of the object to a variable
if current_m == 'EDIT':
context.object.update_from_editmode() #Refresh to avoid error
bpy.ops.object.mode_set(mode = 'OBJECT')
if force_ax == 2:
c_ax_prop = ax_prop
ax_prop = 2
for i in S:
if i.type == "MESH":
lt_selected.append(i) # Assign the selected mesh objects to a list
for act_loop in lt_selected:
vertGlob=[]
O.object.select_all(action="DESELECT")
C.view_layer.objects.active= act_loop
act_loop.select_set(state = True, view_layer = None)
O.object.mode_set(mode = 'EDIT')
O.mesh.select_mode(type='VERT')
O.mesh.select_all(action = 'DESELECT')
O.object.mode_set(mode = 'OBJECT')
mw = C.active_object.matrix_world
vert = C.active_object.data.vertices
vertGlob = [mw @ v.co for v in vert]
if local_prop:
vertGlob = [v.co for v in vert]
if invert_ax:
sel_ax = max([v[ax_prop] for v in vertGlob])
else:
sel_ax = min([v[ax_prop] for v in vertGlob])
for v in vert:
vlist = list(mw @ v.co)
if local_prop:
vlist = list(v.co)
if vlist[ax_prop] >= (sel_ax - flex_prop) and vlist[ax_prop] <= (sel_ax + flex_prop) :
v.select = True
else:
v.select = False
area.type = 'VIEW_3D'
O.object.mode_set(mode = 'EDIT')
O.view3d.snap_cursor_to_selected()
context.object.update_from_editmode()
O.object.mode_set(mode = 'OBJECT')
O.object.origin_set(type='ORIGIN_CURSOR')
if median_prop:
O.object.origin_set(type = 'ORIGIN_GEOMETRY')
O.view3d.snap_cursor_to_active()
C.scene.cursor.location[ax_prop]= sel_ax
O.object.origin_set(type='ORIGIN_CURSOR')
area.type = areaAnchor
C.scene.cursor.location = original_cursor
for i in range(len(S)):
if S[i].name != active_ob:
S[i].select_set(state = True, view_layer = None)
C.view_layer.objects.active = active_ob
O.object.mode_set(mode = current_m)
if force_ax == 2:
ax_prop = c_ax_prop
return {"FINISHED"}
class Easy_A_Object_OP(bpy.types.Operator):
"""Align Selected to Active"""
bl_idname = "object.easy_a_sel_to_active"
bl_label = "Selected to Active"
bl_options = {'REGISTER','UNDO'}
def execute(self,context):
C = bpy.context
D = bpy.data
O = bpy.ops
sel = bpy.context.selected_objects
act = bpy.context.active_object
invert_ax = context.object.set_maximum
rot_prop = context.object.set_rotation
area = context.area
areaAnchor = area.type
current_m = C.object.mode
if bpy.context.object.type == 'MESH':
current_m = context.object.mode
if current_m == 'EDIT':
context.object.update_from_editmode()
bpy.ops.object.mode_set(mode = 'OBJECT')
if len(sel)>=2:
for i in sel:
if i == act:
i.select_set(state = True, view_layer = None)
sel.remove(act)
Easy_A_OP.execute(self,context)
O.object.select_all(action = "DESELECT")
act.select_set(state = True, view_layer = None)
if invert_ax:
C.object.set_maximum = False
Easy_A_OP.execute(self,context)
C.object.set_maximum = True
else:
C.object.set_maximum = True
Easy_A_OP.execute(self,context)
C.object.set_maximum = False
O.object.select_all(action = "DESELECT")
if rot_prop:
try:
act.select_set(state = True, view_layer = None)
act = bpy.context.active_object
act_vert = act.data.vertices
vert_sel=[]
face_sel=[]
for v in act_vert:
if v.select:
vert_sel.append(v)
if len(vert_sel) > 2:
O.object.mode_set(mode='EDIT')
O.mesh.select_mode(type='FACE')
O.mesh.select_mode(type='VERT')
O.object.mode_set(mode='OBJECT')
for f in act_vert:
if f.select:
face_sel.append(f)
if len(face_sel) < 1:
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_mode(type='VERT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for v in act_vert:
for k in range(len(vert_sel)):
if v.co == vert_sel[k].co:
v.select = True
O.object.mode_set(mode='EDIT')
O.mesh.edge_face_add()
O.object.mode_set(mode='OBJECT')
O.object.mode_set(mode = 'EDIT')
bpy.ops.transform.create_orientation(name = "EA_CTrans", use = False) # Create a CTO from selected vertices in active object
O.object.mode_set(mode = 'OBJECT')
if len(face_sel)<1:
O.object.mode_set(mode = 'EDIT')
O.mesh.delete(type='FACE')
O.mesh.select_mode(type='VERT')
O.mesh.select_all(action='DESELECT')
O.object.mode_set(mode='OBJECT')
for v in act_vert:
for k in range(len(vert_sel)):
if v.co == vert_sel[k].co:
v.select = True
vert_sel=[]
face_sel=[]
act.select = False
except:
print("Operation skipped")
for i in sel:
i.select_set(state = True, view_layer = None)
if rot_prop:
O.object.rotation_clear(clear_delta=True)
if 'EA_CTrans' in bpy.context.scene.orientations.keys():
Easy_A_OP.execute(self,context)
''' Set the selected object's transform to CTO'''
custom_matrix = bpy.context.scene.orientations['EA_CTrans'].matrix
custom_matrix_4 = custom_matrix.copy() #Copy the matrix to resize it from 3x3 matrix to 4x4 matrix
custom_matrix_4.resize_4x4()
i.matrix_world = custom_matrix_4 #Set the matrix of the active object to match the resized matrix
O.object.mode_set(mode='OBJECT')
''' Override context to delete CTO '''
name = 'EA_CTrans'
views = [area.spaces.active for area in bpy.context.screen.areas if area.type == 'VIEW_3D']
if views: #Assign the orientation in the view so that it is active and can be removed
views[0].transform_orientation = name
areas = [area for area in bpy.context.window.screen.areas if area.type == 'VIEW_3D']
if areas: #Give the good override context
override = bpy.context.copy()
override['area'] = areas[0]
bpy.ops.transform.delete_orientation( override )
else:
print('Could not Generate CTO')
O.view3d.snap_selected_to_active()
area.type = areaAnchor
act.select_set(state = True, view_layer = None)
O.object.mode_set(mode = current_m)
area.type = areaAnchor
return {"FINISHED"}
class Easy_A_Object_OP_EXCL(bpy.types.Operator):
"""Align Selected to Active exclusively using chosen axis"""
bl_idname = "object.easy_a_sel_to_active_excl"
bl_label = "Selected to Active"
bl_options = {'REGISTER','UNDO'}
def execute(self,context):
C = bpy.context
D = bpy.data
O = bpy.ops
sel = bpy.context.selected_objects
act = bpy.context.active_object
invert_ax = context.object.set_maximum
onlySel = []
area = context.area
areaAnchor = area.type
vlist = []
vertGlob=[]
current_m = C.active_object.mode
ax_prop = int(context.object.set_enum_prop)
median_prop = context.object.set_median
flex_prop = context.object.flexibility
local_prop = context.object.set_local
active_maximum = context.object.set_active_maximum
original_cursor = list(C.scene.cursor.location)
if current_m == 'EDIT':
context.object.update_from_editmode()
bpy.ops.object.mode_set(mode = 'OBJECT')
if len(sel)>=2:
for i in sel:
if i != act:
onlySel.append(i)
for v in onlySel:
O.object.select_all(action="DESELECT")
v.select_set(state = True, view_layer = None)
Easy_A_OP.execute(self,context)
O.object.select_all(action="DESELECT")
act.select_set(state = True, view_layer = None)
O.object.mode_set(mode = 'EDIT')
context.object.update_from_editmode()
O.mesh.select_mode(type='VERT')
O.mesh.select_all(action = 'DESELECT')
O.object.mode_set(mode = 'OBJECT')
mw = C.active_object.matrix_world
vert = C.active_object.data.vertices
vertGlob = [mw @ v.co for v in vert]
if local_prop:
local_prop == False
if not active_maximum:
sel_ax = min([v[ax_prop] for v in vertGlob])
elif active_maximum and not invert_ax:
sel_ax = max([v[ax_prop] for v in vertGlob])
else:
sel_ax = max([v[ax_prop] for v in vertGlob])
for v in vert:
vlist = list(mw @ v.co)
if local_prop:
vlist = list(v.co)
if vlist[ax_prop] >= (sel_ax - flex_prop) and vlist[ax_prop] <= (sel_ax + flex_prop) :
v.select = True
else:
v.select = False
vertGlob = [mw @ v.co for v in vert]
area.type = 'VIEW_3D'
O.object.mode_set(mode = 'EDIT')
O.view3d.snap_cursor_to_selected()
context.object.update_from_editmode()
O.object.mode_set(mode = 'OBJECT')
O.object.mode_set(mode = current_m)
for i in onlySel:
C.view_layer.objects.active = i
i.location[ax_prop]= bpy.context.scene.cursor.location[ax_prop]
for v in sel:
v.select_set(state = True, view_layer = None)
area.type = areaAnchor
C.scene.cursor.location = original_cursor
C.view_layer.objects.active = act
return {"FINISHED"}
class Snap_to_OP(bpy.types.Operator):
bl_idname = "object.snap_to_operator"
bl_label = "Snap to"
bl_options = {'REGISTER','UNDO'}
def execute(self,context):
snap_enum = int(context.object.snap_to)
if snap_enum == 0:
bpy.ops.view3d.snap_selected_to_grid()
if snap_enum == 1:
bpy.ops.view3d.snap_selected_to_cursor(use_offset = False)
if snap_enum == 2:
bpy.ops.view3d.snap_selected_to_cursor(use_offset = True)
if snap_enum == 3:
bpy.ops.view3d.snap_selected_to_active()
return {"FINISHED"}
class To_world_center(bpy.types.Operator):
"""Align Origin to Z then move to world center"""
bl_idname = "object.to_world_center"
bl_label = "To World Center"
bl_options = {'REGISTER','UNDO'}
def execute(self,context):
C = bpy.context
D = bpy.data
O = bpy.ops
sel = bpy.context.selected_objects
area = context.area
areaAnchor = area.type
current_m = C.object.mode
global force_ax # Set selected axis to Z axis
force_ax = 2
for s in sel:
Easy_A_OP.execute(self,context)
s.location = [0.0,0.0,0.0]
return {"FINISHED"}
class To_floor(bpy.types.Operator):
"""Align Origin to Z then move to Z zero"""
bl_idname = "object.to_floor"
bl_label = "To floor"
bl_options = {'REGISTER','UNDO'}
def execute(self,context):
C = bpy.context
D = bpy.data
O = bpy.ops
sel = bpy.context.selected_objects
area = context.area
areaAnchor = area.type
current_m = C.object.mode
global force_ax # Set selected axis to Z axis
force_ax = 2
for s in sel:
Easy_A_OP.execute(self,context)
s.location.z = 0.0
return {"FINISHED"}
class Easy_A_panel(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_label = "Easy Align"
bl_category = "View"
@classmethod
def poll(cls, context):
return bpy.context.active_object is not None and bpy.context.active_object.type == "MESH"
def draw(self, context):
layout = self.layout
#layout.use_property_split = True
box = layout.box()
col = box.row().column()
# FIXED KEYWORDED ARGUMENTS
col.operator(operator = "object.easy_a",text = "Align Origin")
col.label(text = "Default Minimum")
col.prop(context.object, "set_maximum")
col.prop(context.object, "set_median")
col.prop(context.object, "set_local")
col.prop(context.object, "set_rotation")
col.prop(context.object, "set_enum_prop")
col.prop(context.object,"flexibility")
col = box.row().column()
col.label(text = "Selected to Active")
col.operator(operator = "object.easy_a_sel_to_active",text = "Align Objects")
split = col.split()
col.label (text = "Align Objects Exclusively")
col.operator(operator = "object.easy_a_sel_to_active_excl",text = "Exclusively Align Objects")
col.prop(context.object,"set_active_maximum")
split = layout.split()
row = layout.row(align = True)
row.operator(operator = "object.origin_set",text = "Set Origin")
row.operator(operator = "object.origin_set",text = "Snap")
row.prop(context.object,"snap_to")
split = layout.split()
box = layout.box()
row = box.row(align = True)
row.operator(operator = "object.to_world_center",text = "To World Center")
row.operator(operator = "object.to_floor",text = "To Floor")
classes = (
Easy_A_panel,
Easy_A_OP,
Easy_A_Object_OP,
Easy_A_Object_OP_EXCL,
Snap_to_OP,
To_world_center,
To_floor,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
bpy.types.Object.set_enum_prop = ax_prop
bpy.types.Object.set_maximum = invert_ax
bpy.types.Object.set_active_maximum = invert_active_ax
bpy.types.Object.set_median = median_prop
bpy.types.Object.set_local = local_prop
bpy.types.Object.set_rotation = rot_prop
bpy.types.Object.snap_to = snap_enum
bpy.types.Object.flexibility = flex_prop
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
del bpy.types.Object.set_enum_prop
del bpy.types.Object.set_maximum
del bpy.types.Object.set_median
del bpy.types.Object.set_local
del bpy.types.Object.set_rotation
del bpy.types.Object.snap_to
del bpy.types.Object.flexibility
del bpy.types.Object.set_active_maximum
if __name__ == "__main__":
register()