Skip to content
This repository was archived by the owner on Apr 24, 2025. It is now read-only.

Commit ecfa7f1

Browse files
committed
store vertex offsets between a meshes shape keys in it's UV layers
1 parent ee76ad5 commit ecfa7f1

1 file changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
bl_info = {
2+
"name": "Mesh Morpher",
3+
"author": "ibra-kdbra",
4+
"version": (1, 0),
5+
"blender": (4, 0, 1),
6+
"location": "View3D > Sidebar > Unreal Tools Tab",
7+
"description": "A tool for storing shape key data for use in a vertex shader.",
8+
"warning": "",
9+
"doc_url": "",
10+
"category": "Unreal Tools",
11+
}
12+
13+
14+
import bpy
15+
16+
17+
def pack_normals(me):
18+
"""Stores normals in a given mesh's vertex colors"""
19+
if not me.vertex_colors:
20+
me.vertex_colors.new()
21+
col = me.vertex_colors[0]
22+
col.name = "normals"
23+
key = me.shape_keys.key_blocks[1]
24+
normals = list(zip(*[iter(key.normals_vertex_get())]*3))
25+
for loop in me.loops:
26+
r, g, b = normals[loop.vertex_index]
27+
col.data[loop.index].color = ((r + 1) * 0.5, (-g + 1) * 0.5, (b + 1) * 0.5, 1)
28+
29+
30+
def get_shape_key_offsets(shape_keys, two_shape_keys=False):
31+
"""Return a list of vertex offsets between shape keys"""
32+
keys = shape_keys.key_blocks
33+
offsets = []
34+
original = keys[0].data
35+
target = keys[1].data
36+
offset = [v1.co - v2.co for v1, v2 in zip(target, original)]
37+
offsets.append(offset)
38+
if two_shape_keys:
39+
target = keys[2].data
40+
offset = [v1.co - v2.co for v1, v2 in zip(target, original)]
41+
offsets.append(offset)
42+
return offsets
43+
44+
45+
def pack_offsets(ob, offsets):
46+
"""Stores shape key vertex offsets in mesh's UVs"""
47+
me = ob.data
48+
while len(me.uv_layers) < 4:
49+
me.uv_layers.new()
50+
for loop in me.loops:
51+
x1, y1, z1 = offsets[0][loop.vertex_index]
52+
if len(offsets) > 1:
53+
offset = offsets[1][loop.vertex_index]
54+
else:
55+
offset = ob.location
56+
x2, y2, z2 = offset
57+
me.uv_layers[1].data[loop.index].uv = (x2, 1 - (-y2))
58+
me.uv_layers[2].data[loop.index].uv = (z2, 1 - x1)
59+
me.uv_layers[3].data[loop.index].uv = (-y1, 1 - z1)
60+
61+
62+
class MeshMorpherSettings(bpy.types.PropertyGroup):
63+
store_shape_key1_normals: bpy.props.BoolProperty(
64+
name="First Shape Key Normals",
65+
description="Store first shape key's vertex normals in vertex colors",
66+
default=True
67+
)
68+
two_shape_keys: bpy.props.BoolProperty(
69+
name="Two Shape Keys",
70+
description="Store vertex offsets for first and second shape keys",
71+
default=False
72+
)
73+
74+
75+
class OBJECT_OT_ProcessShapeKeys(bpy.types.Operator):
76+
"""Store object's shape key offsets in it's UV layers"""
77+
bl_idname = "object.process_shape_keys"
78+
bl_label = "Process Shape Keys"
79+
80+
store_shape_key1_normals: bpy.props.BoolProperty(
81+
name="First Shape Key Normals",
82+
default=True
83+
)
84+
two_shape_keys: bpy.props.BoolProperty(
85+
name="Two Shape Keys",
86+
default=False
87+
)
88+
89+
@classmethod
90+
def poll(cls, context):
91+
ob = context.active_object
92+
return ob and ob.type == 'MESH' and ob.mode == 'OBJECT'
93+
94+
95+
def execute(self, context):
96+
units = context.scene.unit_settings
97+
ob = context.object
98+
shape_keys = ob.data.shape_keys
99+
if units.system != 'METRIC' or round(units.scale_length, 2) != 0.01:
100+
self.report(
101+
{'ERROR'},
102+
"Scene Units must be Metric with a Unit Scale of 0.01!"
103+
)
104+
return {'CANCELLED'}
105+
if not shape_keys:
106+
self.report({'ERROR'}, "Object has no shape keys!")
107+
return {'CANCELLED'}
108+
if len(shape_keys.key_blocks) < 2 + self.two_shape_keys:
109+
self.report({'ERROR'}, "Object needs additional shape keys!")
110+
return {'CANCELLED'}
111+
if self.store_shape_key1_normals:
112+
pack_normals(ob.data)
113+
offsets = get_shape_key_offsets(shape_keys, self.two_shape_keys)
114+
pack_offsets(ob, offsets)
115+
return {'FINISHED'}
116+
117+
118+
class VIEW3D_PT_MeshMorpher(bpy.types.Panel):
119+
"""Creates a Panel in 3D Viewport"""
120+
bl_label = "Mesh Morpher"
121+
bl_idname = "VIEW3D_PT_mesh_morpher"
122+
bl_space_type = 'VIEW_3D'
123+
bl_region_type = 'UI'
124+
bl_category = "Unreal Tools"
125+
126+
def draw(self, context):
127+
layout = self.layout
128+
col = layout.column()
129+
props = context.scene.mesh_morpher_settings
130+
col.prop(props, "store_shape_key1_normals")
131+
col.prop(props, "two_shape_keys")
132+
op = col.operator("object.process_shape_keys")
133+
op.store_shape_key1_normals = props.store_shape_key1_normals
134+
op.two_shape_keys = props.two_shape_keys
135+
136+
137+
def register():
138+
bpy.utils.register_class(MeshMorpherSettings)
139+
bpy.utils.register_class(OBJECT_OT_ProcessShapeKeys)
140+
bpy.utils.register_class(VIEW3D_PT_MeshMorpher)
141+
bpy.types.Scene.mesh_morpher_settings = bpy.props.PointerProperty(
142+
type=MeshMorpherSettings
143+
)
144+
145+
146+
def unregister():
147+
bpy.utils.unregister_class(MeshMorpherSettings)
148+
bpy.utils.unregister_class(OBJECT_OT_ProcessShapeKeys)
149+
bpy.utils.unregister_class(VIEW3D_PT_MeshMorpher)
150+
del bpy.types.Scene.mesh_morpher_settings
151+
152+
153+
if __name__ == "__main__":
154+
register()

0 commit comments

Comments
 (0)