I found a workaround that works for me.
I wrote a Python script that generates a new mesh object "SK_Cubes" by copying the final transformed mesh from the object "Cube" and individually applying the attributes.
The first time the operation is executed, it will create a copy of the selected object with the prefix "SK_". If the object has already been created it will simply update it.
The cloned object may have the armature modifier.
import bpy
import bmesh
from bpy.types import (
Panel,
PropertyGroup,
)
from bpy.props import (
PointerProperty,
StringProperty,
)
def create_object(src, name):
""" Create a new mesh object with the specified name, and link to the same collections of src object """
mesh = bpy.data.meshes.new(f"{name}_mesh")
obj = bpy.data.objects.new(name, mesh)
obj.location = src.location
obj.rotation_euler = src.rotation_euler
obj.scale = src.scale
for col in src.users_collection:
col.objects.link(obj)
return obj
def find_or_create_object(src):
object_prefix = bpy.context.scene.vgutils_props.object_prefix
target_name = f"{object_prefix}{src.name}"
if target_name in bpy.data.objects:
return bpy.data.objects[target_name]
else:
return create_object(src, target_name)
def transfer_evaluated_mesh(src, dest):
print(f"Transfer mesh from {src.name} to {dest.name}")
group_prefix = bpy.context.scene.vgutils_props.group_prefix
# Generate a bmesh from the final transformed mesh
bm = bmesh.new()
bm.from_object(src, bpy.context.evaluated_depsgraph_get())
bm.verts.ensure_lookup_table()
bm.to_mesh(dest.data)
# All attribute that starts with "b_" are converted to vertex group in the new mesh
for attr_name, group in bm.verts.layers.float.items():
if not attr_name.startswith(group_prefix):
continue
group_name = attr_name[len(group_prefix):]
print(f" - Copy group {attr_name} to {group_name}")
if group_name not in dest.vertex_groups:
target_group = dest.vertex_groups.new(name=group_name)
else:
target_group = dest.vertex_groups[group_name]
for v in bm.verts:
value = v[group]
if value > 0:
target_group.add((v.index,), value, 'REPLACE')
else:
target_group.remove((v.index,))
# Remove all the groups with the "b_" prefix
bm = bmesh.new()
bm.from_mesh(dest.data)
found = True
while found:
found = False
for attr_name, group in list(bm.verts.layers.float.items()):
if attr_name.startswith(group_prefix):
bm.verts.layers.float.remove(group)
found = True
break
bm.to_mesh(dest.data)
print(" + Done!")
# Properties
class VGUtils_Props(PropertyGroup):
""" Properties for the AutoMirror addon """
object_prefix: StringProperty(
name="Object Prefix",
default="SK_",
)
group_prefix: StringProperty(
name="Group Prefix",
default="b_",
)
class VGUtils(bpy.types.Operator):
bl_idname = "object.vgutils"
bl_label = "Bake Vertex Groups"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return all(o.type == "MESH" for o in context.selected_objects)
def execute(self, context):
print()
print("=== Bake Vertex Groups ===")
object_prefix = bpy.context.scene.vgutils_props.object_prefix
for obj in bpy.context.selected_objects:
if obj.type != "MESH":
continue
# Find the source and target objects
if obj.name.startswith(object_prefix):
src_name = obj.name[len(object_prefix):]
if src_name not in bpy.data.objects:
raise ValueError(f"Source object {src_name} not found")
src = bpy.data.objects[src_name]
dest = obj
else:
src = obj
dest = find_or_create_object(obj)
transfer_evaluated_mesh(src, dest)
return {'FINISHED'}
class VIEW3D_PT_VGUtils(Panel):
""" Shows a panel in the "Edit" tab of the 3D View """
bl_label = "Vertex Group Utils"
bl_idname = "VIEW3D_PT_VGUtils"
bl_category = 'Edit'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_options = set()
@classmethod
def poll(cls, context):
return all(o.type == "MESH" for o in context.selected_objects)
def draw(self, context):
layout = self.layout
layout.column(align=True)
row = layout.row()
row.operator("object.vgutils")
vgutils_props = context.scene.vgutils_props
layout.prop(vgutils_props, "object_prefix")
layout.prop(vgutils_props, "group_prefix")
def register():
bpy.utils.register_class(VGUtils)
bpy.utils.register_class(VIEW3D_PT_VGUtils)
bpy.utils.register_class(VGUtils_Props)
bpy.types.Scene.vgutils_props = PointerProperty(type=VGUtils_Props)
def unregister():
bpy.utils.unregister_class(VGUtils)
bpy.utils.unregister_class(VIEW3D_PT_VGUtils)
bpy.utils.unregister_class(VGUtils_Props)
if __name__ == "__main__":
register()
PROS:
CONS:
- it requires a manual action.
- it retains a copy of the mesh in the blend file, making it larger.