From 0767cdd4a00e217320a2b9e090373efb0d41209e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 27 Dec 2009 11:14:06 +0000 Subject: [PATCH] Transfer Shape Key Useful if you have 2 different characters with the same base mesh (matching indicies), and want to copy a facial expression for eg, from one to another. Durian request to re-use shapes between characters. * Copies the active shape to other selected objects * Different methods to apply the shape * * OFFSET, simple translation offset * * RELATIVE (EDGE/FACE), Use Barycentric transformation to copy the shape. This means the target mesh can be a different orientation and scale and the shape should still apply since the surrounding geometry is used as a basis for the offset. bug: barycentric transform's depth was inverted. Note: * This isnt added into a menu yet, * This cant be redone since adding a shape key messes up the redo stack. needs fixing for other scripts too. --- release/scripts/modules/rna_info.py | 2 +- release/scripts/op/object.py | 164 ++++++++++++++++++++++ source/blender/blenlib/intern/math_geom.c | 2 +- 3 files changed, 166 insertions(+), 2 deletions(-) diff --git a/release/scripts/modules/rna_info.py b/release/scripts/modules/rna_info.py index 0a1b02d5759..86da0b56943 100644 --- a/release/scripts/modules/rna_info.py +++ b/release/scripts/modules/rna_info.py @@ -232,7 +232,7 @@ class InfoPropertyRNA: if self.is_never_none: type_str += ", (never None)" - + return type_str def __repr__(self): diff --git a/release/scripts/op/object.py b/release/scripts/op/object.py index 08fac5d4d3c..c510981ebc2 100644 --- a/release/scripts/op/object.py +++ b/release/scripts/op/object.py @@ -149,6 +149,170 @@ class Retopo(bpy.types.Operator): return {'FINISHED'} +class ShapeTransfer(bpy.types.Operator): + '''Copy the active objects current shape to other selected objects with the same number of verts''' + + bl_idname = "object.shape_key_transfer" + bl_label = "Transfer Shape Key" + bl_register = True + bl_undo = True + + mode = EnumProperty(items=( + ('OFFSET', "Offset", "Apply the relative positional offset"), + ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."), + ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")), + name="Transformation Mode", + description="Method to apply relative shape positions to the new shape", + default='OFFSET') + + use_clamp = BoolProperty(name="Clamp Offset", + description="Clamp the transformation to the distance each vertex moves in the original shape.", + default=False) + + def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False): + + def me_nos(verts): + return [v.normal.copy() for v in verts] + + def me_cos(verts): + return [v.co.copy() for v in verts] + + def ob_add_shape(ob): + C_tmp = {"object": ob} + me = ob.data + if me.shape_keys is None: # add basis + bpy.ops.object.shape_key_add(C_tmp) + bpy.ops.object.shape_key_add(C_tmp) + ob.active_shape_key_index = len(me.shape_keys.keys) - 1 + ob.shape_key_lock = True + + from Geometry import BarycentricTransform + from Mathutils import Vector + + if use_clamp and mode == 'OFFSET': + use_clamp = False + + me = ob_act.data + + orig_shape_coords = me_cos(ob_act.active_shape_key.data) + + orig_normals = me_nos(me.verts) + orig_coords = me_cos(me.verts) + + for ob_other in objects: + me_other = ob_other.data + if len(me_other.verts) != len(me.verts): + self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name) + continue + + target_normals = me_nos(me_other.verts) + target_coords = me_cos(me_other.verts) + + ob_add_shape(ob_other) + + # editing the final coords, only list that stores wrapped coords + target_shape_coords = [v.co for v in ob_other.active_shape_key.data] + + median_coords = [[] for i in range(len(me.verts))] + + # Method 1, edge + if mode == 'OFFSET': + for i, vert_cos in enumerate(median_coords): + vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i])) + + elif mode == 'RELATIVE_FACE': + for face in me.faces: + i1, i2, i3, i4 = face.verts_raw + if i4 != 0: + pt = BarycentricTransform(orig_shape_coords[i1], + orig_coords[i4], orig_coords[i1], orig_coords[i2], + target_coords[i4], target_coords[i1], target_coords[i2]) + median_coords[i1].append(pt) + + pt = BarycentricTransform(orig_shape_coords[i2], + orig_coords[i1], orig_coords[i2], orig_coords[i3], + target_coords[i1], target_coords[i2], target_coords[i3]) + median_coords[i2].append(pt) + + pt = BarycentricTransform(orig_shape_coords[i3], + orig_coords[i2], orig_coords[i3], orig_coords[i4], + target_coords[i2], target_coords[i3], target_coords[i4]) + median_coords[i3].append(pt) + + pt = BarycentricTransform(orig_shape_coords[i4], + orig_coords[i3], orig_coords[i4], orig_coords[i1], + target_coords[i3], target_coords[i4], target_coords[i1]) + median_coords[i4].append(pt) + + else: + pt = BarycentricTransform(orig_shape_coords[i1], + orig_coords[i3], orig_coords[i1], orig_coords[i2], + target_coords[i3], target_coords[i1], target_coords[i2]) + median_coords[i1].append(pt) + + pt = BarycentricTransform(orig_shape_coords[i2], + orig_coords[i1], orig_coords[i2], orig_coords[i3], + target_coords[i1], target_coords[i2], target_coords[i3]) + median_coords[i2].append(pt) + + pt = BarycentricTransform(orig_shape_coords[i3], + orig_coords[i2], orig_coords[i3], orig_coords[i1], + target_coords[i2], target_coords[i3], target_coords[i1]) + median_coords[i3].append(pt) + + elif mode == 'RELATIVE_EDGE': + for ed in me.edges: + i1, i2 = ed.verts + v1, v2 = orig_coords[i1], orig_coords[i2] + edge_length = (v1 - v2).length + n1loc = v1 + orig_normals[i1] * edge_length + n2loc = v2 + orig_normals[i2] * edge_length + + + # now get the target nloc's + v1_to, v2_to = target_coords[i1], target_coords[i2] + edlen_to = (v1_to - v2_to).length + n1loc_to = v1_to + target_normals[i1] * edlen_to + n2loc_to = v2_to + target_normals[i2] * edlen_to + + pt = BarycentricTransform(orig_shape_coords[i1], + v2, v1, n1loc, + v2_to, v1_to, n1loc_to) + median_coords[i1].append(pt) + + pt = BarycentricTransform(orig_shape_coords[i2], + v1, v2, n2loc, + v1_to, v2_to, n2loc_to) + median_coords[i2].append(pt) + + # apply the offsets to the new shape + from functools import reduce + VectorAdd = Vector.__add__ + + for i, vert_cos in enumerate(median_coords): + if vert_cos: + co = reduce(VectorAdd, vert_cos) / len(vert_cos) + + if use_clamp: + # clamp to the same movement as the original + # breaks copy between different scaled meshes. + len_from = (orig_shape_coords[i] - orig_coords[i]).length + ofs = co - target_coords[i] + ofs.length = len_from + co = target_coords[i] + ofs + + target_shape_coords[i][:] = co + + return {'FINISHED'} + + def execute(self, context): + C = bpy.context + ob_act = C.active_object + objects = [ob for ob in C.selected_editable_objects if ob != ob_act] + return self._main(ob_act, objects, self.properties.mode, self.properties.use_clamp) + + bpy.types.register(SelectPattern) bpy.types.register(SubdivisionSet) bpy.types.register(Retopo) +bpy.types.register(ShapeTransfer) diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c index dd773cb00c1..470e5d71a45 100644 --- a/source/blender/blenlib/intern/math_geom.c +++ b/source/blender/blenlib/intern/math_geom.c @@ -1404,7 +1404,7 @@ void barycentric_transform(float pt_tar[3], float const pt_src[3], area_tar= sqrtf(area_tri_v3(tri_tar_p1, tri_tar_p2, tri_tar_p3)); area_src= sqrtf(area_tri_v2(tri_xy_src[0], tri_xy_src[1], tri_xy_src[2])); - z_ofs_src= tri_xy_src[0][2] - pt_src_xy[2]; + z_ofs_src= pt_src_xy[2] - tri_xy_src[0][2]; madd_v3_v3v3fl(pt_tar, pt_tar, no_tar, (z_ofs_src / area_src) * area_tar); }