# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

bl_info = {
    'name': 'Copy Attributes Menu',
    'author': 'Bassam Kurdali, Fabian Fricke, wiseman303',
    'version': (0, 4, 4),
    "blender": (2, 5, 7),
    "api": 36695,
    'location': 'View3D > Ctrl-C',
    'description': 'Copy Attributes Menu from Blender 2.4',
    'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
        'Scripts/3D_interaction/Copy_Attributes_Menu',
    'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
        'func=detail&aid=22588',
    'category': '3D View'}

import bpy
from mathutils import Matrix, Vector


def build_exec(loopfunc, func):
    '''Generator function that returns exec functions for operators '''

    def exec_func(self, context):
        loopfunc(self, context, func)
        return {'FINISHED'}
    return exec_func


def build_invoke(loopfunc, func):
    '''Generator function that returns invoke functions for operators'''

    def invoke_func(self, context, event):
        loopfunc(self, context, func)
        return {'FINISHED'}
    return invoke_func


def build_op(idname, label, description, fpoll, fexec, finvoke):
    '''Generator function that returns the basic operator'''

    class myopic(bpy.types.Operator):
        bl_idname = idname
        bl_label = label
        bl_description = description
        execute = fexec
        poll = fpoll
        invoke = finvoke
    return myopic


def genops(copylist, oplist, prefix, poll_func, loopfunc):
    '''Generate ops from the copy list and its associated functions '''
    for op in copylist:
        exec_func = build_exec(loopfunc, op[3])
        invoke_func = build_invoke(loopfunc, op[3])
        opclass = build_op(prefix + op[0], "Copy " + op[1], op[2],
           poll_func, exec_func, invoke_func)
        oplist.append(opclass)


def generic_copy(source, target, string=""):
    ''' copy attributes from source to target that have string in them '''
    for attr in dir(source):
        if attr.find(string) > -1:
            try:
                setattr(target, attr, getattr(source, attr))
            except:
                pass
    return


def getmat(bone, active, context, ignoreparent):
    '''Helper function for visual transform copy,
       gets the active transform in bone space
    '''
    data_bone = context.active_object.data.bones[bone.name]
    #all matrices are in armature space unless commented otherwise
    otherloc = active.matrix  # final 4x4 mat of target, location.
    bonemat_local = Matrix(data_bone.matrix_local)  # self rest matrix
    if data_bone.parent:
        parentposemat = Matrix(
           context.active_object.pose.bones[data_bone.parent.name].matrix)
        parentbonemat = Matrix(data_bone.parent.matrix_local)
    else:
        parentposemat = bonemat_local.copy()
        parentbonemat = bonemat_local.copy()

        # FIXME! why copy from the parent if setting identity ?, Campbell
        parentposemat.identity()
        parentbonemat.identity()

    if parentbonemat == parentposemat or ignoreparent:
        newmat = bonemat_local.inverted() * otherloc
    else:
        bonemat = parentbonemat.inverted() * bonemat_local

        newmat = bonemat.inverted() * parentposemat.inverted() * otherloc
    return newmat


def rotcopy(item, mat):
    '''copy rotation to item from matrix mat depending on item.rotation_mode'''
    if item.rotation_mode == 'QUATERNION':
        item.rotation_quaternion = mat.to_3x3().to_quaternion()
    elif item.rotation_mode == 'AXIS_ANGLE':
        quat = mat.to_3x3().to_quaternion()
        item.rotation_axis_angle = Vector([quat.axis[0],
           quat.axis[1], quat.axis[2], quat.angle])
    else:
        item.rotation_euler = mat.to_3x3().to_euler(item.rotation_mode)


def pLoopExec(self, context, funk):
    '''Loop over selected bones and execute funk on them'''
    active = context.active_pose_bone
    selected = context.selected_pose_bones
    selected.remove(active)
    for bone in selected:
        funk(bone, active, context)

#The following functions are used o copy attributes frome active to bone


def pLocLocExec(bone, active, context):
    bone.location = active.location


def pLocRotExec(bone, active, context):
    rotcopy(bone, active.matrix_basis.to_3x3())


def pLocScaExec(bone, active, context):
    bone.scale = active.scale


def pVisLocExec(bone, active, context):
    bone.location = getmat(bone, active, context, False).to_translation()


def pVisRotExec(bone, active, context):
    rotcopy(bone, getmat(bone, active,
      context, not context.active_object.data.bones[bone.name].use_inherit_rotation))


def pVisScaExec(bone, active, context):
    bone.scale = getmat(bone, active, context,
       not context.active_object.data.bones[bone.name].use_inherit_scale)\
          .to_scale()


def pDrwExec(bone, active, context):
    bone.custom_shape = active.custom_shape


def pLokExec(bone, active, context):
    for index, state in enumerate(active.lock_location):
        bone.lock_location[index] = state
    for index, state in enumerate(active.lock_rotation):
        bone.lock_rotation[index] = state
    bone.lock_rotations_4d = active.lock_rotations_4d
    bone.lock_rotation_w = active.lock_rotation_w
    for index, state in enumerate(active.lock_scale):
        bone.lock_scale[index] = state


def pConExec(bone, active, context):
    for old_constraint in  active.constraints.values():
        new_constraint = bone.constraints.new(old_constraint.type)
        generic_copy(old_constraint, new_constraint)


def pIKsExec(bone, active, context):
    generic_copy(active, bone, "ik_")

pose_copies = (('pose_loc_loc', "Local Location",
                "Copy Location from Active to Selected", pLocLocExec),
                ('pose_loc_rot', "Local Rotation",
                "Copy Rotation from Active to Selected", pLocRotExec),
                ('pose_loc_sca', "Local Scale",
                "Copy Scale from Active to Selected", pLocScaExec),
                ('pose_vis_loc', "Visual Location",
                "Copy Location from Active to Selected", pVisLocExec),
                ('pose_vis_rot', "Visual Rotation",
                "Copy Rotation from Active to Selected", pVisRotExec),
                ('pose_vis_sca', "Visual Scale",
                "Copy Scale from Active to Selected", pVisScaExec),
                ('pose_drw', "Bone Shape",
                "Copy Bone Shape from Active to Selected", pDrwExec),
                ('pose_lok', "Protected Transform",
                "Copy Protected Tranforms from Active to Selected", pLokExec),
                ('pose_con', "Bone Constraints",
                "Copy Object Constraints from Active to Selected", pConExec),
                ('pose_iks', "IK Limits",
                "Copy IK Limits from Active to Selected", pIKsExec))


@classmethod
def pose_poll_func(cls, context):
    return(context.mode == 'POSE')


def pose_invoke_func(self, context, event):
    wm = context.window_manager
    wm.invoke_props_dialog(self)
    return {'RUNNING_MODAL'}


class CopySelectedPoseConstraints(bpy.types.Operator):
    ''' Copy Chosen constraints from active to selected'''
    bl_idname = "pose.copy_selected_constraints"
    bl_label = "Copy Selected Constraints"
    selection = bpy.props.BoolVectorProperty(size=32)

    poll = pose_poll_func
    invoke = pose_invoke_func

    def draw(self, context):
        layout = self.layout
        for idx, const in enumerate(context.active_pose_bone.constraints):
            layout.prop(self, "selection", index=idx, text=const.name,
               toggle=True)

    def execute(self, context):
        active = context.active_pose_bone
        selected = context.selected_pose_bones[:]
        selected.remove(active)
        for bone in selected:
            for index, flag in enumerate(self.selection):
                if flag:
                    old_constraint = active.constraints[index]
                    new_constraint = bone.constraints.new(\
                       active.constraints[index].type)
                    generic_copy(old_constraint, new_constraint)
        return {'FINISHED'}

pose_ops = []  # list of pose mode copy operators

genops(pose_copies, pose_ops, "pose.copy_", pose_poll_func, pLoopExec)


class VIEW3D_MT_posecopypopup(bpy.types.Menu):
    bl_label = "Copy Attributes"

    def draw(self, context):
        layout = self.layout
        layout.operator_context = 'INVOKE_REGION_WIN'
        for op in pose_copies:
            layout.operator("pose.copy_" + op[0])
        layout.operator("pose.copy_selected_constraints")
        layout.operator("pose.copy", text="copy pose")


def obLoopExec(self, context, funk):
    '''Loop over selected objects and execute funk on them'''
    active = context.active_object
    selected = context.selected_objects[:]
    selected.remove(active)
    for obj in selected:
        msg = funk(obj, active, context)
    if msg:
        self.report({msg[0]}, msg[1])


def world_to_basis(active, ob, context):
    '''put world coords of active as basis coords of ob'''
    local = ob.parent.matrix_world.inverted() * active.matrix_world
    P = ob.matrix_basis * ob.matrix_local.inverted()
    mat = P * local
    return(mat)

#The following functions are used o copy attributes from
#active to selected object


def obLoc(ob, active, context):
    ob.location = active.location


def obRot(ob, active, context):
    rotcopy(ob, active.matrix_local.to_3x3())


def obSca(ob, active, context):
    ob.scale = active.scale

def obVisLoc(ob, active, context):
    if ob.parent:
        mat = world_to_basis(active, ob, context)
        ob.location = mat.to_translation()
    else:
        ob.location = active.matrix_world.to_translation()


def obVisRot(ob, active, context):
    if ob.parent:
        mat = world_to_basis(active, ob, context)
        rotcopy(ob, mat.to_3x3())
    else:
        rotcopy(ob, active.matrix_world.to_3x3())


def obVisSca(ob, active, context):
    if ob.parent:
        mat = world_to_basis(active, ob, context)
        ob.scale = mat.to_scale()
    else:
        ob.scale = active.matrix_world.to_scale()
    
def obDrw(ob, active, context):
    ob.draw_type = active.draw_type
    ob.show_axis = active.show_axis
    ob.show_bounds = active.show_bounds
    ob.draw_bounds_type = active.draw_bounds_type
    ob.show_name = active.show_name
    ob.show_texture_space = active.show_texture_space
    ob.show_transparent = active.show_transparent
    ob.show_wire = active.show_wire
    ob.show_x_ray = active.show_x_ray
    ob.empty_draw_type = active.empty_draw_type
    ob.empty_draw_size = active.empty_draw_size


def obOfs(ob, active, context):
    ob.time_offset = active.time_offset
    return('INFO', "time offset copied")


def obDup(ob, active, context):
    generic_copy(active, ob, "dupli")
    return('INFO', "duplication method copied")


def obCol(ob, active, context):
    ob.color = active.color


def obMas(ob, active, context):
    ob.game.mass = active.game.mass
    return('INFO', "mass copied")


def obLok(ob, active, context):
    for index, state in enumerate(active.lock_location):
        ob.lock_location[index] = state
    for index, state in enumerate(active.lock_rotation):
        ob.lock_rotation[index] = state
    for index, state in enumerate(active.lock_rotations_4d):
        ob.lock_rotations_4d[index] = state
    ob.lock_rotation_w = active.lock_rotation_w
    for index, state in enumerate(active.lock_scale):
        ob.lock_scale[index] = state
    return('INFO', "transform locks copied")


def obCon(ob, active, context):
    #for consistency with 2.49, delete old constraints first
    for removeconst in ob.constraints:
        ob.constraints.remove(removeconst)
    for old_constraint in  active.constraints.values():
        new_constraint = ob.constraints.new(old_constraint.type)
        generic_copy(old_constraint, new_constraint)
    return('INFO', "constraints copied")


def obTex(ob, active, context):
    if 'texspace_location' in dir(ob.data) and 'texspace_location' in dir(
       active.data):
        ob.data.texspace_location[:] = active.data.texspace_location[:]
    if 'texspace_size' in dir(ob.data) and 'texspace_size' in dir(active.data):
        ob.data.texspace_size[:] = active.data.texspace_size[:]
    return('INFO', "texture space copied")


def obIdx(ob, active, context):
    ob.pass_index = active.pass_index
    return('INFO', "pass index copied")


def obMod(ob, active, context):
    for modifier in ob.modifiers:
        #remove existing before adding new:
        ob.modifiers.remove(modifier)
    for old_modifier in active.modifiers.values():
        new_modifier = ob.modifiers.new(name=old_modifier.name,
           type=old_modifier.type)
        generic_copy(old_modifier, new_modifier)
    return('INFO', "modifiers copied")


def obWei(ob, active, context):
    me_source = active.data
    me_target = ob.data
    # sanity check: do source and target have the same amount of verts?
    if len(me_source.vertices) != len(me_target.vertices):
        return('ERROR', "objects have different vertex counts, doing nothing")
    vgroups_IndexName = {}
    for i in range(0, len(active.vertex_groups)):
        groups = active.vertex_groups[i]
        vgroups_IndexName[groups.index] = groups.name
    data = {}  # vert_indices, [(vgroup_index, weights)]
    for v in me_source.vertices:
        vg = v.groups
        vi = v.index
        if len(vg) > 0:
            vgroup_collect = []
            for i in range(0, len(vg)):
                vgroup_collect.append((vg[i].group, vg[i].weight))
            data[vi] = vgroup_collect
    # write data to target
    if ob != active:
        # add missing vertex groups
        for vgroup_name in vgroups_IndexName.values():
            #check if group already exists...
            already_present = 0
            for i in range(0, len(ob.vertex_groups)):
                if ob.vertex_groups[i].name == vgroup_name:
                    already_present = 1
            # ... if not, then add
            if already_present == 0:
                ob.vertex_groups.new(name=vgroup_name)
        # write weights
        for v in me_target.vertices:
            for vi_source, vgroupIndex_weight in data.items():
                if v.index == vi_source:

                    for i in range(0, len(vgroupIndex_weight)):
                        groupName = vgroups_IndexName[vgroupIndex_weight[i][0]]
                        groups = ob.vertex_groups
                        for vgs in range(0, len(groups)):
                            if groups[vgs].name == groupName:
                                groups[vgs].add((v.index,),
                                   vgroupIndex_weight[i][1], "REPLACE")
    return('INFO', "weights copied")

object_copies = (('obj_loc', "Location",
                "Copy Location from Active to Selected", obLoc),
                ('obj_rot', "Rotation",
                "Copy Rotation from Active to Selected", obRot),
                ('obj_sca', "Scale",
                "Copy Scale from Active to Selected", obSca),
                ('obj_vis_loc', "Visual Location",
                "Copy Visual Location from Active to Selected", obVisLoc),
                ('obj_vis_rot', "Visual Rotation",
                "Copy Visual Rotation from Active to Selected", obVisRot),
                ('obj_vis_sca', "Visual Scale",
                "Copy Visual Scale from Active to Selected", obVisSca),
                ('obj_drw', "Draw Options",
                "Copy Draw Options from Active to Selected", obDrw),
                ('obj_ofs', "Time Offset",
                "Copy Time Offset from Active to Selected", obOfs),
                ('obj_dup', "Dupli",
                "Copy Dupli from Active to Selected", obDup),
                ('obj_col', "Object Color",
                "Copy Object Color from Active to Selected", obCol),
                ('obj_mas', "Mass",
                "Copy Mass from Active to Selected", obMas),
                #('obj_dmp', "Damping",
                #"Copy Damping from Active to Selected"),
                #('obj_all', "All Physical Attributes",
                #"Copy Physical Atributes from Active to Selected"),
                #('obj_prp', "Properties",
                #"Copy Properties from Active to Selected"),
                #('obj_log', "Logic Bricks",
                #"Copy Logic Bricks from Active to Selected"),
                ('obj_lok', "Protected Transform",
                "Copy Protected Tranforms from Active to Selected", obLok),
                ('obj_con', "Object Constraints",
                "Copy Object Constraints from Active to Selected", obCon),
                #('obj_nla', "NLA Strips",
                #"Copy NLA Strips from Active to Selected"),
                #('obj_tex', "Texture Space",
                #"Copy Texture Space from Active to Selected", obTex),
                #('obj_sub', "Subsurf Settings",
                #"Copy Subsurf Setings from Active to Selected"),
                #('obj_smo', "AutoSmooth",
                #"Copy AutoSmooth from Active to Selected"),
                ('obj_idx', "Pass Index",
                "Copy Pass Index from Active to Selected", obIdx),
                ('obj_mod', "Modifiers",
                "Copy Modifiers from Active to Selected", obMod),
                ('obj_wei', "Vertex Weights",
                "Copy vertex weights based on indices", obWei))


@classmethod
def object_poll_func(cls, context):
    return(len(context.selected_objects) > 1)


def object_invoke_func(self, context, event):
    wm = context.window_manager
    wm.invoke_props_dialog(self)
    return {'RUNNING_MODAL'}


class CopySelectedObjectConstraints(bpy.types.Operator):
    ''' Copy Chosen constraints from active to selected'''
    bl_idname = "object.copy_selected_constraints"
    bl_label = "Copy Selected Constraints"
    selection = bpy.props.BoolVectorProperty(size=32)

    poll = object_poll_func

    invoke = object_invoke_func

    def draw(self, context):
        layout = self.layout
        for idx, const in enumerate(context.active_object.constraints):
            layout.prop(self, "selection", index=idx, text=const.name,
               toggle=True)

    def execute(self, context):
        active = context.active_object
        selected = context.selected_objects[:]
        selected.remove(active)
        for obj in selected:
            for index, flag in enumerate(self.selection):
                if flag:
                    old_constraint = active.constraints[index]
                    new_constraint = obj.constraints.new(\
                       active.constraints[index].type)
                    generic_copy(old_constraint, new_constraint)
        return{'FINISHED'}


class CopySelectedObjectModifiers(bpy.types.Operator):
    ''' Copy Chosen modifiers from active to selected'''
    bl_idname = "object.copy_selected_modifiers"
    bl_label = "Copy Selected Modifiers"
    selection = bpy.props.BoolVectorProperty(size=32)

    poll = object_poll_func

    invoke = object_invoke_func

    def draw(self, context):
        layout = self.layout
        for idx, const in enumerate(context.active_object.modifiers):
            layout.prop(self, 'selection', index=idx, text=const.name,
               toggle=True)

    def execute(self, context):
        active = context.active_object
        selected = context.selected_objects[:]
        selected.remove(active)
        for obj in selected:
            for index, flag in enumerate(self.selection):
                if flag:
                    old_modifier = active.modifiers[index]
                    new_modifier = obj.modifiers.new(\
                       type=active.modifiers[index].type,
                       name=active.modifiers[index].name)
                    generic_copy(old_modifier, new_modifier)
        return{'FINISHED'}

object_ops = []
genops(object_copies, object_ops, "object.copy_", object_poll_func, obLoopExec)


class VIEW3D_MT_copypopup(bpy.types.Menu):
    bl_label = "Copy Attributes"

    def draw(self, context):
        layout = self.layout
        layout.operator_context = 'INVOKE_REGION_WIN'
        for op in object_copies:
            layout.operator("object.copy_" + op[0])
        layout.operator("object.copy_selected_constraints")
        layout.operator("object.copy_selected_modifiers")

#Begin Mesh copy settings:


class MESH_MT_CopyFaceSettings(bpy.types.Menu):
    bl_label = "Copy Face Settings"

    @classmethod
    def poll(cls, context):
        return context.mode == 'EDIT_MESH'

    def draw(self, context):
        mesh = context.object.data
        uv = len(mesh.uv_textures) > 1
        vc = len(mesh.vertex_colors) > 1
        layout = self.layout

        layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                        text="Copy Material")['mode'] = 'MAT'
        if mesh.uv_textures.active:
            layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                            text="Copy Mode")['mode'] = 'MODE'
            layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                            text="Copy Transp")['mode'] = 'TRANSP'
            layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                            text="Copy Image")['mode'] = 'IMAGE'
            layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                            text="Copy UV Coords")['mode'] = 'UV'
        if mesh.vertex_colors.active:
            layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                            text="Copy Vertex Colors")['mode'] = 'VCOL'
        if uv or vc:
            layout.separator()
            if uv:
                layout.menu("MESH_MT_CopyModeFromLayer")
                layout.menu("MESH_MT_CopyTranspFromLayer")
                layout.menu("MESH_MT_CopyImagesFromLayer")
                layout.menu("MESH_MT_CopyUVCoordsFromLayer")
            if vc:
                layout.menu("MESH_MT_CopyVertexColorsFromLayer")


def _buildmenu(self, mesh, mode):
    layout = self.layout
    if mode == 'VCOL':
        layers = mesh.vertex_colors
    else:
        layers = mesh.uv_textures
    for layer in layers:
        if not layer.active:
            op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
                                 text=layer.name)
            op['layer'] = layer.name
            op['mode'] = mode


@classmethod
def _poll_layer_uvs(cls, context):
    return context.mode == "EDIT_MESH" and len(
       context.object.data.uv_layers) > 1


@classmethod
def _poll_layer_vcols(cls, context):
    return context.mode == "EDIT_MESH" and len(
       context.object.data.vertex_colors) > 1


def _build_draw(mode):
    return (lambda self, context: _buildmenu(self, context.object.data, mode))

_layer_menu_data = (("UV Coords", _build_draw("UV"), _poll_layer_uvs),
                    ("Images", _build_draw("IMAGE"), _poll_layer_uvs),
                    ("Mode", _build_draw("MODE"), _poll_layer_uvs),
                    ("Transp", _build_draw("TRANSP"), _poll_layer_uvs),
                    ("Vertex Colors", _build_draw("VCOL"), _poll_layer_vcols))
_layer_menus = []
for name, draw_func, poll_func in _layer_menu_data:
    classname = "MESH_MT_Copy" + "".join(name.split()) + "FromLayer"
    menuclass = type(classname, (bpy.types.Menu,),
                     dict(bl_label="Copy " + name + " from layer",
                          bl_idname=classname,
                          draw=draw_func,
                          poll=poll_func))
    _layer_menus.append(menuclass)


class MESH_OT_CopyFaceSettings(bpy.types.Operator):
    """Copy settings from active face to all selected faces."""
    bl_idname = 'mesh.copy_face_settings'
    bl_label = "Copy Face Settings"
    bl_options = {'REGISTER', 'UNDO'}

    mode = bpy.props.StringProperty(name="mode")
    layer = bpy.props.StringProperty(name="layer")

    @classmethod
    def poll(cls, context):
        return context.mode == 'EDIT_MESH'

    def execute(self, context):
        mesh = context.object.data
        mode = getattr(self, 'mode', 'MODE')
        layername = getattr(self, 'layer', None)

        # Switching out of edit mode updates the selected state of faces and
        # makes the data from the uv texture and vertex color layers available.
        bpy.ops.object.editmode_toggle()

        if mode == 'MAT':
            from_data = mesh.faces
            to_data = from_data
        else:
            if mode == 'VCOL':
                layers = mesh.vertex_colors
                act_layer = mesh.vertex_colors.active
            else:
                layers = mesh.uv_textures
                act_layer = mesh.uv_textures.active
            if not layers or (layername and not layername in layers):
                    return _end({'CANCELLED'})
            from_data = layers[layername or act_layer.name].data
            to_data = act_layer.data
        from_face = from_data[mesh.faces.active]

        for f in mesh.faces:
            if f.select:
                if to_data != from_data:
                    from_face = from_data[f.index]
                if mode == 'MAT':
                    f.material_index = from_face.material_index
                    continue
                to_face = to_data[f.index]
                if to_face is from_face:
                    continue
                if mode == 'VCOL':
                    to_face.color1 = from_face.color1
                    to_face.color2 = from_face.color2
                    to_face.color3 = from_face.color3
                    to_face.color4 = from_face.color4
                elif mode == 'MODE':
                    to_face.use_alpha_sort = from_face.use_alpha_sort
                    to_face.use_billboard = from_face.use_billboard
                    to_face.use_collision = from_face.use_collision
                    to_face.use_halo = from_face.use_halo
                    to_face.hide = from_face.hide
                    to_face.use_light = from_face.use_light
                    to_face.use_object_color = from_face.use_object_color
                    to_face.use_shadow_cast = from_face.use_shadow_cast
                    to_face.use_blend_shared = from_face.use_blend_shared
                    to_face.use_image = from_face.use_image
                    to_face.use_bitmap_text = from_face.use_bitmap_text
                    to_face.use_twoside = from_face.use_twoside
                elif mode == 'TRANSP':
                    to_face.blend_type = from_face.blend_type
                elif mode in ('UV', 'IMAGE'):
                    attr = mode.lower()
                    setattr(to_face, attr, getattr(from_face, attr))
        return _end({'FINISHED'})


def _end(retval):
    # Clean up by returning to edit mode like it was before.
    bpy.ops.object.editmode_toggle()
    return(retval)


def _add_tface_buttons(self, context):
    row = self.layout.row()
    row.operator(MESH_OT_CopyFaceSettings.bl_idname,
                 text="Copy Mode")['mode'] = 'MODE'
    row.operator(MESH_OT_CopyFaceSettings.bl_idname,
                 text="Copy Transp")['mode'] = 'TRANSP'


def register():
    bpy.utils.register_module(__name__)

    ''' mostly to get the keymap working '''
    kc = bpy.context.window_manager.keyconfigs['Blender']
    km = kc.keymaps.get("Object Mode")
    if km is None:
        km = kc.keymaps.new(name="Object Mode")
    kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
    kmi.properties.name = 'VIEW3D_MT_copypopup'
    km = kc.keymaps.get("Pose")
    if km is None:
        km = kc.keymaps.new(name="Pose")

    kmi = km.keymap_items.get("pose.copy")
    if kmi is not None:
        kmi.idname = 'wm.call_menu'
    else:
        kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
    kmi.properties.name = 'VIEW3D_MT_posecopypopup'
    for menu in _layer_menus:
        bpy.utils.register_class(menu)
    bpy.types.DATA_PT_texface.append(_add_tface_buttons)
    km = kc.keymaps.get("Mesh")
    if km is None:
        km = kc.keymaps.new(name="Mesh")
    kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS')
    kmi.ctrl = True
    kmi.properties.name = 'MESH_MT_CopyFaceSettings'


def unregister():
    bpy.utils.unregister_module(__name__)

    ''' mostly to remove the keymap '''
    kms = bpy.context.window_manager.keyconfigs['Blender'].keymaps['Pose']
    for item in kms.keymap_items:
        if item.name == 'Call Menu' and item.idname == 'wm.call_menu' and \
           item.properties.name == 'VIEW3D_MT_posecopypopup':
            item.idname = 'pose.copy'
            break
    for menu in _layer_menus:
        bpy.utils.unregister_class(menu)
    bpy.types.DATA_PT_texface.remove(_add_tface_buttons)
    km = bpy.context.window_manager.keyconfigs.active.keymaps['Mesh']
    for kmi in km.keymap_items:
        if kmi.idname == 'wm.call_menu':
            if kmi.properties.name == 'MESH_MT_CopyFaceSettings':
                km.keymap_items.remove(kmi)

if __name__ == "__main__":
    register()
