Skip to content
Snippets Groups Projects
unfold_transition.py 11.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • # gpl: authors Liero, Atom
    
    
    bl_info = {
        "name": "Unfold transition",
    
        "author": "Liero, Atom",
    
        "location": "3D View > Toolshelf > Create > Unfold Transition",
    
        "description": "Simple unfold transition / animation, will "
                       "separate faces and set up an armature",
    
        "category": "Animation"}
    
    
    # Note the properties are moved to __init__
    # search for patterns advanced_objects, adv_obj
    
    
    import bpy
    from bpy.types import (
            Operator,
            Panel,
            )
    from random import (
            randint,
            uniform,
            )
    from mathutils import Vector
    from mathutils.geometry import intersect_point_line
    
    
    class Set_Up_Fold(Operator):
        bl_idname = "object.set_up_fold"
        bl_label = "Set Up Unfold"
    
        bl_description = ("Set up Faces and Bones for animation\n"
                          "Needs an existing Active Mesh Object")
    
        bl_options = {"REGISTER", "UNDO"}
    
        @classmethod
        def poll(cls, context):
    
            obj = context.active_object
            return (obj is not None and obj.type == "MESH")
    
    
        def execute(self, context):
            bpy.ops.object.mode_set()
            scn = bpy.context.scene
    
            adv_obj = scn.advanced_objects1
    
            obj = bpy.context.object
            dat = obj.data
            fac = dat.polygons
            ver = dat.vertices
    
            # try to cleanup traces of previous actions
            bpy.ops.object.mode_set(mode="EDIT")
            bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=True)
            bpy.ops.object.mode_set()
            old_vg = [vg for vg in obj.vertex_groups if vg.name.startswith("bone.")]
            for vg in old_vg:
                obj.vertex_groups.remove(vg)
    
            if "UnFold" in obj.modifiers:
                arm = obj.modifiers["UnFold"].object
                rig = arm.data
                try:
                    scn.objects.unlink(arm)
                    bpy.data.objects.remove(arm)
                    bpy.data.armatures.remove(rig)
                except:
                    pass
                obj.modifiers.remove(obj.modifiers["UnFold"])
    
            # try to obtain the face sequence from the vertex weights
    
            if adv_obj.unfold_modo == "weight":
    
                if len(obj.vertex_groups):
                    i = obj.vertex_groups.active.index
                    W = []
                    for f in fac:
                        v_data = []
                        for v in f.vertices:
                            try:
                                w = ver[v].groups[i].weight
                                v_data.append((w, v))
                            except:
                                v_data.append((0, v))
                        v_data.sort(reverse=True)
                        v1 = ver[v_data[0][1]].co
                        v2 = ver[v_data[1][1]].co
                        cen = Vector(f.center)
                        its = intersect_point_line(cen, v2, v1)
                        head = v2.lerp(v1, its[1])
                        peso = sum([x[0] for x in v_data])
                        W.append((peso, f.index, cen, head))
                    W.sort(reverse=True)
                    S = [x[1:] for x in W]
                else:
                    self.report({"INFO"}, "First paint a Weight Map for this object")
    
                    return {"FINISHED"}
    
            # separate the faces and sort them
            bpy.ops.object.mode_set(mode="EDIT")
            bpy.ops.mesh.select_all(action="SELECT")
            bpy.ops.mesh.edge_split()
            bpy.ops.mesh.select_all(action="SELECT")
    
    
            if adv_obj.unfold_modo == "cursor":
    
                bpy.context.tool_settings.mesh_select_mode = [True, True, True]
    
                bpy.ops.mesh.sort_elements(
                        type="CURSOR_DISTANCE", elements={"VERT", "EDGE", "FACE"}
                        )
    
            bpy.context.tool_settings.mesh_select_mode = [False, False, True]
            bpy.ops.object.mode_set()
    
            # Get sequence of faces and edges from the face / vertex indices
    
            if adv_obj.unfold_modo != "weight":
    
                S = []
                for f in fac:
                    E = list(f.edge_keys)
                    E.sort()
                    v1 = ver[E[0][0]].co
                    v2 = ver[E[0][1]].co
                    cen = Vector(f.center)
                    its = intersect_point_line(cen, v2, v1)
                    head = v2.lerp(v1, its[1])
                    S.append((f.index, f.center, head))
    
            # create the armature and the modifier
            arm = bpy.data.armatures.new("arm")
            rig = bpy.data.objects.new("rig_" + obj.name, arm)
    
    
            # store the name for checking the right rig
            adv_obj.unfold_arm_name = rig.name
    
            rig.matrix_world = obj.matrix_world
            scn.objects.link(rig)
            scn.objects.active = rig
            bpy.ops.object.mode_set(mode="EDIT")
            arm.draw_type = "WIRE"
            rig.show_x_ray = True
            mod = obj.modifiers.new("UnFold", "ARMATURE")
            mod.show_in_editmode = True
            mod.object = rig
    
            # create bones and vertex groups
            root = arm.edit_bones.new("bone.000")
            root.tail = (0, 0, 0)
            root.head = (0, 0, 1)
            root.select = True
            vis = [False, True] + [False] * 30
    
            for fb in S:
                f = fac[fb[0]]
                b = arm.edit_bones.new("bone.000")
    
                if adv_obj.unfold_flip:
    
                    b.tail, b.head = fb[2], fb[1]
                else:
                    b.tail, b.head = fb[1], fb[2]
    
                b.align_roll(f.normal)
                b.select = False
                b.layers = vis
                b.parent = root
                vg = obj.vertex_groups.new(b.name)
                vg.add(f.vertices, 1, "ADD")
    
            bpy.ops.object.mode_set()
    
    
            if adv_obj.unfold_modo == "weight":
    
                obj.vertex_groups.active_index = 0
            scn.objects.active = rig
            obj.select = False
    
            return {"FINISHED"}
    
    
    class Animate_Fold(Operator):
        bl_idname = "object.animate_fold"
        bl_label = "Animate Unfold"
    
        bl_description = ("Animate bones to simulate unfold. Starts on current frame\n"
                          "Needs an existing Active Armature Object created in the previous step")
    
        bl_options = {"REGISTER", "UNDO"}
    
    
        @classmethod
        def poll(cls, context):
    
            obj = context.active_object
            return (obj is not None and obj.type == "ARMATURE" and obj.is_visible(bpy.context.scene))
    
        def draw(self, context):
            layout = self.layout
    
            adv_obj = context.scene.advanced_objects1
    
    
            if self.is_not_undo is True:
                layout.label(text="Warning:", icon="INFO")
                layout.label(text="The generated Armature was not selected or it was renamed")
                layout.label(text="The animation can fail if it is not generated by the previous step")
                layout.separator()
                layout.label(text="Expected Armature name:", icon="BONE_DATA")
                layout.label(text=str(adv_obj.unfold_arm_name), icon="TRIA_RIGHT")
                layout.label(text="To Continue press OK, to Cancel click Outside the Pop-up")
                layout.separator()
            else:
                return
    
        def invoke(self, context, event):
    
            obj = bpy.context.object
    
            scn = bpy.context.scene
    
            adv_obj = scn.advanced_objects1
    
    
            if obj.name != adv_obj.unfold_arm_name:
                self.is_not_undo = True
                return context.window_manager.invoke_props_dialog(self, width=400)
            else:
                return self.execute(context)
    
    
        def execute(self, context):
            obj = bpy.context.object
            scn = bpy.context.scene
    
            adv_obj = scn.advanced_objects1
    
            fra = scn.frame_current
    
            if obj.name != adv_obj.unfold_arm_name:
                self.report({"INFO"},
                            "The generated rig was not selected or renamed. The animation can fail")
    
            # clear the animation and get the list of bones
            if obj.animation_data:
                obj.animation_data_clear()
            bpy.ops.object.mode_set(mode="POSE")
            bones = obj.pose.bones[0].children_recursive
    
                rot = adv_obj.unfold_rot_max / 57.3
    
            extra = adv_obj.unfold_rot_time * adv_obj.unfold_bounce
            ruido = max(adv_obj.unfold_rot_time + extra,
                        adv_obj.unfold_sca_time) + adv_obj.unfold_fold_noise
    
            len_bones = len(bones) if len(bones) != 0 else 1  # possible division by zero
            vel = (adv_obj.unfold_fold_duration - ruido) / len_bones
    
    
            # introduce scale and rotation keyframes
            for a, b in enumerate(bones):
    
                t = fra + a * vel + randint(0, adv_obj.unfold_fold_noise)
    
                if adv_obj.unfold_flip:
    
                elif adv_obj.unfold_from_point:
    
                    b.scale = (0, 0, 0)
                else:
                    b.scale = (1, 0, 0)
    
    
                if not adv_obj.unfold_flip:
    
                    b.keyframe_insert("scale", frame=t)
                    b.scale = (1, 1, 1)
    
                    b.keyframe_insert("scale", frame=t + adv_obj.unfold_sca_time)
    
                if adv_obj.unfold_rot_max:
    
                    b.rotation_mode = "XYZ"
    
                    if adv_obj.unfold_wiggle_rot:
    
                        euler = (uniform(-rot, rot), uniform(-rot, rot), uniform(-rot, rot))
                    else:
                        euler = (rot, 0, 0)
    
                    b.rotation_euler = euler
                    b.keyframe_insert("rotation_euler", frame=t)
    
    
                if adv_obj.unfold_bounce:
                    val = adv_obj.unfold_bounce * -.10
    
                    b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
    
                    b.keyframe_insert(
                            "rotation_euler", frame=t + adv_obj.unfold_rot_time + .25 * extra
                            )
    
                    val = adv_obj.unfold_bounce * .05
    
                    b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
    
                    b.keyframe_insert(
                            "rotation_euler", frame=t + adv_obj.unfold_rot_time + .50 * extra
                            )
    
                    val = adv_obj.unfold_bounce * -.025
    
                    b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
    
                    b.keyframe_insert(
                            "rotation_euler", frame=t + adv_obj.unfold_rot_time + .75 * extra
                            )
    
    
                b.rotation_euler = (0, 0, 0)
    
                b.keyframe_insert(
                            "rotation_euler", frame=t + adv_obj.unfold_rot_time + extra
                            )
            self.is_not_undo = False
    
    
            return {"FINISHED"}
    
    
    class PanelFOLD(Panel):
        bl_label = "Unfold Transition"
        bl_space_type = "VIEW_3D"
        bl_region_type = "TOOLS"
        bl_category = "Create"
        bl_context = "objectmode"
        bl_options = {"DEFAULT_CLOSED"}
    
        def draw(self, context):
            layout = self.layout
    
            adv_obj = context.scene.advanced_objects1
    
    
            box = layout.box()
            col = box.column()
            col.operator("object.set_up_fold", text="1. Set Up Unfold")
            col.separator()
            col.label("Unfold Mode:")
            col.prop(adv_obj, "unfold_modo")
            col.prop(adv_obj, "unfold_flip")
    
            box = layout.box()
            col = box.column(align=True)
            col.operator("object.animate_fold", text="2. Animate Unfold")
            col.separator()
            col.prop(adv_obj, "unfold_fold_duration")
            col.prop(adv_obj, "unfold_sca_time")
            col.prop(adv_obj, "unfold_rot_time")
            col.prop(adv_obj, "unfold_rot_max")
    
            row = col.row(align=True)
            row.prop(adv_obj, "unfold_fold_noise")
            row.prop(adv_obj, "unfold_bounce")
            row = col.row(align=True)
            row.prop(adv_obj, "unfold_wiggle_rot")
    
            if not adv_obj.unfold_flip:
                row.prop(adv_obj, "unfold_from_point")
    
    classes = (
        Set_Up_Fold,
        Animate_Fold,
        PanelFOLD,
        )
    
        for cls in classes:
            bpy.utils.register_class(cls)
    
        for cls in classes:
            bpy.utils.unregister_class(cls)
    
    
    
    if __name__ == "__main__":
        register()