-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathop_auto_shrink.py
164 lines (131 loc) · 5.58 KB
/
op_auto_shrink.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
import bpy
import bmesh
from mathutils import Vector
from bpy.props import EnumProperty, BoolProperty
from bpy.app.translations import pgettext
import time
class MIO3_OT_auto_shrink(bpy.types.Operator):
bl_idname = "mio3.auto_shrink"
bl_label = "Auto Shrink"
bl_description = "DESC Auto Shrink"
bl_options = {"REGISTER", "UNDO"}
volume: BoolProperty(name="Leave the volume", default=True)
selected: BoolProperty(name="Selected only", default=False)
type: EnumProperty(
name="優先",
items=[
("snap", "Type Snap", ""),
("lerp", "Type Tnterpolation", ""),
],
default="snap",
)
@classmethod
def poll(cls, context):
return context.object is not None
def invoke(self, context, event):
obj = context.active_object
obj.update_from_editmode()
if not obj.find_armature():
self.report({"ERROR"}, "Armature modifier not set")
return {"CANCELLED"}
if not obj.active_shape_key:
self.report({"ERROR"}, "Register ShapeKey for Shrink")
return {"CANCELLED"}
return self.execute(context)
def execute(self, context):
start_time = time.time()
if context.active_object.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
obj = context.active_object
armature = obj.find_armature()
obj_world_mat = obj.matrix_world
obj_world_mat_inv = obj_world_mat.inverted()
arm_world_mat = armature.matrix_world
# 対象ボーンを抽出
selected_bones = [bone for bone in armature.data.bones if bone.select and bone.use_deform]
if not selected_bones:
selected_bones = [bone for bone in armature.data.bones if not bone.hide and bone.use_deform]
if not selected_bones:
self.report({"ERROR"}, "No deform bone")
return {"CANCELLED"}
# 事前計算
vertex_weights = {}
for vert in obj.data.vertices:
vert_weights = {}
for group in vert.groups:
if group.group < len(obj.vertex_groups):
group_name = obj.vertex_groups[group.group].name
vert_weights[group_name] = group.weight
vertex_weights[vert.index] = vert_weights
bone_heads = [arm_world_mat @ bone.head_local for bone in selected_bones]
bone_tails = [arm_world_mat @ bone.tail_local for bone in selected_bones]
bone_vecs = [bone_tails[i] - bone_heads[i] for i in range(len(selected_bones))]
bm = bmesh.from_edit_mesh(obj.data)
for i, vert in enumerate(bm.verts):
if self.selected and not vert.select:
continue
vert_world_co = obj_world_mat @ vert.co
total_weighted_pos = Vector((0, 0, 0))
total_weight = 0
for j, bone in enumerate(selected_bones):
if bone.name not in vertex_weights[i]:
continue
weight = vertex_weights[i][bone.name]
if weight > 0:
bone_head = bone_heads[j]
bone_tail = bone_tails[j]
bone_vec = bone_vecs[j]
dist_to_head_sq = (vert_world_co - bone_head).length_squared
dist_to_tail_sq = (vert_world_co - bone_tail).length_squared
if self.type == "lerp":
if len(bone.children) == 0:
snapped_pos = bone_head
else:
vert_to_bone = vert_world_co - bone_head
proj_vec = vert_to_bone.project(bone_vec)
snapped_pos = bone_head + proj_vec
if dist_to_head_sq < dist_to_tail_sq:
snapped_pos = bone_head.lerp(snapped_pos, weight)
else:
snapped_pos = bone_tail.lerp(snapped_pos, weight)
else:
if len(bone.children) == 0:
snapped_pos = bone_head
elif weight < 0.99:
if dist_to_head_sq < dist_to_tail_sq:
snapped_pos = bone_head
else:
snapped_pos = bone_tail
else:
vert_to_bone = vert_world_co - bone_head
proj_vec = vert_to_bone.project(bone_vec)
snapped_pos = bone_head + proj_vec
total_weighted_pos += snapped_pos * weight
total_weight += weight
if total_weight > 0:
factor = 0.97 if self.volume else 1
vert.co = obj_world_mat_inv @ vert_world_co.lerp(
total_weighted_pos / total_weight, factor
)
bmesh.update_edit_mesh(obj.data)
print(f"Time: {time.time() - start_time}")
return {"FINISHED"}
def draw(self, context):
layout = self.layout
layout.prop(self, "volume")
layout.prop(self, "selected")
row = layout.row()
row.prop(self, "type", expand=True)
def menu(self, context):
self.layout.operator(
MIO3_OT_auto_shrink.bl_idname,
text=pgettext(MIO3_OT_auto_shrink.bl_label),
icon="ARMATURE_DATA",
)
classes = [MIO3_OT_auto_shrink]
def register():
for c in classes:
bpy.utils.register_class(c)
def unregister():
for c in classes:
bpy.utils.unregister_class(c)