Skip to content
Snippets Groups Projects
space_view3d_copy_attributes.py 30.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### 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, Adam Wiseman",
    
        "blender": (2, 80, 0),
    
        "location": "View3D > Ctrl-C",
        "description": "Copy Attributes Menu from Blender 2.4",
    
        "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
    
                    "Scripts/3D_interaction/Copy_Attributes_Menu",
    
    Campbell Barton's avatar
    Campbell Barton committed
    from mathutils import Matrix
    
    Campbell Barton's avatar
    Campbell Barton committed
        Operator,
        Menu,
    )
    
    Campbell Barton's avatar
    Campbell Barton committed
        BoolVectorProperty,
        StringProperty,
    )
    
    
    # First part of the operator Info message
    INFO_MESSAGE = "Copy Attributes: "
    
        """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"""
    
            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],
    
    Campbell Barton's avatar
    Campbell Barton committed
                               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
    
        obj_act = context.active_object
        data_bone = obj_act.data.bones[bone.name]
    
        # all matrices are in armature space unless commented otherwise
    
        otherloc = active.matrix  # final 4x4 mat of target, location.
    
        bonemat_local = data_bone.matrix_local.copy()  # self rest matrix
    
            parentposemat = obj_act.pose.bones[data_bone.parent.name].matrix.copy()
            parentbonemat = data_bone.parent.matrix_local.copy()
    
            parentposemat = parentbonemat = Matrix()
    
        if parentbonemat == parentposemat or ignoreparent:
    
            newmat = bonemat_local.inverted() @ otherloc
    
            bonemat = parentbonemat.inverted() @ bonemat_local
    
            newmat = bonemat.inverted() @ parentposemat.inverted() @ otherloc
    
        """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':
    
            rot = mat.to_3x3().to_quaternion().to_axis_angle()    # returns (Vector((x, y, z)), w)
    
            axis_angle = rot[1], rot[0][0], rot[0][1], rot[0][2]  # convert to w, x, y, z
            item.rotation_axis_angle = axis_angle
    
            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,
    
    Campbell Barton's avatar
    Campbell Barton committed
                             context, not context.active_object.data.bones[bone.name].use_inherit_rotation))
    
    
    
    def pVisScaExec(bone, active, context):
        bone.scale = getmat(bone, active, context,
    
    Campbell Barton's avatar
    Campbell Barton committed
                            not context.active_object.data.bones[bone.name].use_inherit_scale)\
            .to_scale()
    
    
    
    def pDrwExec(bone, active, context):
        bone.custom_shape = active.custom_shape
    
    ZanQdo's avatar
    ZanQdo committed
        bone.use_custom_shape_bone_size = active.use_custom_shape_bone_size
        bone.custom_shape_scale = active.custom_shape_scale
    
        bone.bone.show_wire = active.bone.show_wire
    
    
    
    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_")
    
    
    
    def pBBonesExec(bone, active, context):
        object = active.id_data
        generic_copy(
    
    ZanQdo's avatar
    ZanQdo committed
            object.data.bones[active.name],
    
            object.data.bones[bone.name],
            "bbone_")
    
    
    Campbell Barton's avatar
    Campbell Barton committed
        ('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),
        ('bbone_settings', "BBone Settings",
         "Copy BBone Settings from Active to Selected", pBBonesExec),
    )
    
    
    
    @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(Operator):
    
        """Copy Chosen constraints from active to selected"""
    
        bl_idname = "pose.copy_selected_constraints"
        bl_label = "Copy Selected Constraints"
    
        selection: BoolVectorProperty(
    
    Campbell Barton's avatar
    Campbell Barton committed
            size=32,
            options={'SKIP_SAVE'}
        )
    
    
        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,
    
    Campbell Barton's avatar
    Campbell Barton committed
                            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(
    
    Campbell Barton's avatar
    Campbell Barton committed
                            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(Menu):
    
        bl_label = "Copy Attributes"
    
        def draw(self, context):
            layout = self.layout
            layout.operator_context = 'INVOKE_REGION_WIN'
    
            layout.operator("view3d.copybuffer", icon="COPY_ID")
    
            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]}, INFO_MESSAGE + 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
    
    # 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()
    
        return('INFO', "Object location copied")
    
    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())
    
        return('INFO', "Object rotation copied")
    
    
    
    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()
    
        return('INFO', "Object scale copied")
    
    def obDrw(ob, active, context):
    
        ob.display_type = active.display_type
    
        ob.show_axis = active.show_axis
        ob.show_bounds = active.show_bounds
    
        ob.display_bounds_type = active.display_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_in_front = active.show_in_front
        ob.empty_display_type = active.empty_display_type
        ob.empty_display_size = active.empty_display_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 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
    
        ob.lock_rotations_4d = active.lock_rotations_4d
    
        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")
    
        # 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,
    
    Campbell Barton's avatar
    Campbell Barton committed
                                            type=old_modifier.type)
    
            generic_copy(old_modifier, new_modifier)
    
        return('INFO', "Modifiers copied")
    
    def obGrp(ob, active, context):
        for grp in bpy.data.groups:
            if active.name in grp.objects and ob.name not in grp.objects:
                grp.objects.link(ob)
    
        return('INFO', "Groups copied")
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    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,),
    
    Campbell Barton's avatar
    Campbell Barton committed
                                                    vgroupIndex_weight[i][1], "REPLACE")
    
    Campbell Barton's avatar
    Campbell Barton committed
    object_copies = (
    
    Campbell Barton's avatar
    Campbell Barton committed
        # ('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', "Location",
         "Copy Location from Active to Selected", obVisLoc),
        ('obj_vis_rot', "Rotation",
         "Copy Rotation from Active to Selected", obVisRot),
        ('obj_vis_sca', "Scale",
         "Copy 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_dmp', "Damping",
        # "Copy Damping from Active to Selected"),
        # ('obj_all', "All Physical Attributes",
        # "Copy Physical Attributes 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),
        ('obj_grp', "Group Links",
         "Copy selected into active object's groups", obGrp)
    )
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    @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(Operator):
    
        """Copy Chosen constraints from active to selected"""
    
        bl_idname = "object.copy_selected_constraints"
        bl_label = "Copy Selected Constraints"
    
    
        selection: BoolVectorProperty(
    
    Campbell Barton's avatar
    Campbell Barton committed
            size=32,
            options={'SKIP_SAVE'}
        )
    
        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,
    
    Campbell Barton's avatar
    Campbell Barton committed
                            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(
    
    Campbell Barton's avatar
    Campbell Barton committed
                            active.constraints[index].type
                        )
    
                        generic_copy(old_constraint, new_constraint)
            return{'FINISHED'}
    
    
    
    class CopySelectedObjectModifiers(Operator):
    
        """Copy Chosen modifiers from active to selected"""
    
        bl_idname = "object.copy_selected_modifiers"
        bl_label = "Copy Selected Modifiers"
    
    
        selection: BoolVectorProperty(
    
    Campbell Barton's avatar
    Campbell Barton committed
            size=32,
            options={'SKIP_SAVE'}
        )
    
        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,
    
    Campbell Barton's avatar
    Campbell Barton committed
                            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(
    
    Campbell Barton's avatar
    Campbell Barton committed
                            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(Menu):
    
        bl_label = "Copy Attributes"
    
        def draw(self, context):
            layout = self.layout
    
            layout.operator_context = 'INVOKE_REGION_WIN'
    
            layout.operator("view3d.copybuffer", icon="COPY_ID")
    
    
            if (len(context.selected_objects) <= 1):
                layout.separator()
                layout.label(text="Please select at least two objects", icon="INFO")
                layout.separator()
    
            for entry, op in enumerate(object_copies):
                if entry and entry % 4 == 0:
                    layout.separator()
    
                layout.operator("object.copy_" + op[0])
            layout.operator("object.copy_selected_constraints")
            layout.operator("object.copy_selected_modifiers")
    
    
    
    class MESH_MT_CopyFaceSettings(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.operator("view3d.copybuffer", icon="COPY_ID")
            layout.operator("view3d.pastebuffer", icon="COPY_ID")
    
            op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
    
    Campbell Barton's avatar
    Campbell Barton committed
                                 text="Copy Material")
    
            op['layer'] = ''
            op['mode'] = 'MAT'
    
            if mesh.uv_textures.active:
    
                op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
    
    Campbell Barton's avatar
    Campbell Barton committed
                                     text="Copy Active UV Image")
    
                op['layer'] = ''
                op['mode'] = 'IMAGE'
                op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
    
    Campbell Barton's avatar
    Campbell Barton committed
                                     text="Copy Active UV Coords")
    
                op['layer'] = ''
                op['mode'] = 'UV'
    
            if mesh.vertex_colors.active:
    
                op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
    
    Campbell Barton's avatar
    Campbell Barton committed
                                     text="Copy Active Vertex Colors")
    
                op['layer'] = ''
                op['mode'] = 'VCOL'
    
            if uv or vc:
                layout.separator()
                if uv:
                    layout.menu("MESH_MT_CopyImagesFromLayer")
                    layout.menu("MESH_MT_CopyUVCoordsFromLayer")
                if vc:
                    layout.menu("MESH_MT_CopyVertexColorsFromLayer")
    
    
    
    # Data (UV map, Image and Vertex color) menus calling MESH_OT_CopyFaceSettings
    # Explicitly defined as using the generator code was broken in case of Menus
    # causing issues with access and registration
    
    class MESH_MT_CopyImagesFromLayer(Menu):
        bl_label = "Copy Other UV Image Layers"
    
        @classmethod
        def poll(cls, context):
            obj = context.active_object
            return obj and obj.mode == "EDIT_MESH" and len(
    
    Campbell Barton's avatar
    Campbell Barton committed
                obj.data.uv_layers) > 1
    
    
        def draw(self, context):
            mesh = context.active_object.data
            _buildmenu(self, mesh, 'IMAGE', "IMAGE_COL")
    
    
    class MESH_MT_CopyUVCoordsFromLayer(Menu):
        bl_label = "Copy Other UV Coord Layers"
    
        @classmethod
        def poll(cls, context):
            obj = context.active_object
            return obj and obj.mode == "EDIT_MESH" and len(
    
    Campbell Barton's avatar
    Campbell Barton committed
                obj.data.uv_layers) > 1
    
    
        def draw(self, context):
            mesh = context.active_object.data
            _buildmenu(self, mesh, 'UV', "GROUP_UVS")
    
    
    class MESH_MT_CopyVertexColorsFromLayer(Menu):
        bl_label = "Copy Other Vertex Colors Layers"
    
        @classmethod
        def poll(cls, context):
            obj = context.active_object
            return obj and obj.mode == "EDIT_MESH" and len(
    
    Campbell Barton's avatar
    Campbell Barton committed
                obj.data.vertex_colors) > 1
    
    
        def draw(self, context):
            mesh = context.active_object.data
            _buildmenu(self, mesh, 'VCOL', "GROUP_VCOL")
    
    
    def _buildmenu(self, mesh, mode, icon):
    
        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,
    
                op['layer'] = layer.name
                op['mode'] = mode
    
    
    
    class MESH_OT_CopyFaceSettings(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'}
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            name="Mode",
            options={"HIDDEN"},
        )
    
        layer: StringProperty(
    
    Campbell Barton's avatar
    Campbell Barton committed
            name="Layer",
            options={"HIDDEN"},
        )
    
    
        @classmethod
        def poll(cls, context):
            return context.mode == 'EDIT_MESH'
    
        def execute(self, context):
    
            mode = getattr(self, 'mode', '')
    
            if mode not in {'MAT', 'VCOL', 'IMAGE', 'UV'}:
                self.report({'ERROR'}, "No mode specified or invalid mode")
    
                return self._end(context, {'CANCELLED'})
            layername = getattr(self, 'layer', '')
    
            mesh = context.object.data
    
            # 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()
    
    
            polys = mesh.polygons
    
                to_data = from_data = polys
    
            else:
                if mode == 'VCOL':
                    layers = mesh.vertex_colors
                    act_layer = mesh.vertex_colors.active
    
                elif mode == 'IMAGE':
    
                    layers = mesh.uv_textures
                    act_layer = mesh.uv_textures.active
    
                elif mode == 'UV':
                    layers = mesh.uv_layers
                    act_layer = mesh.uv_layers.active
    
                if not layers or (layername and layername not in layers):
                    self.report({'ERROR'}, "Invalid UV or color layer. Operation Cancelled")
    
                    return self._end(context, {'CANCELLED'})
    
                from_data = layers[layername or act_layer.name].data
                to_data = act_layer.data
    
            from_index = polys.active
    
            for f in polys:
    
                if f.select:
                    if to_data != from_data:
    
                        # Copying from another layer.
                        # from_face is to_face's counterpart from other layer.
                        from_index = f.index
                    elif f.index == from_index:
                        # Otherwise skip copying a face to itself.
                        continue
    
                        f.material_index = polys[from_index].material_index
    
                    elif mode == 'IMAGE':
                        to_data[f.index].image = from_data[from_index].image
    
                    if len(f.loop_indices) != len(polys[from_index].loop_indices):
                        self.report({'WARNING'}, "Different number of vertices.")
                    for i in range(len(f.loop_indices)):
                        to_vertex = f.loop_indices[i]
                        from_vertex = polys[from_index].loop_indices[i]
                        if mode == 'VCOL':
                            to_data[to_vertex].color = from_data[from_vertex].color
                        elif mode == 'UV':
                            to_data[to_vertex].uv = from_data[from_vertex].uv
    
            return self._end(context, {'FINISHED'})
    
        def _end(self, context, retval):
            if context.mode != 'EDIT_MESH':
                # Clean up by returning to edit mode like it was before.
                bpy.ops.object.editmode_toggle()
            return(retval)
    
    classes = (
        CopySelectedPoseConstraints,
        VIEW3D_MT_posecopypopup,
        CopySelectedObjectConstraints,
        CopySelectedObjectModifiers,
        VIEW3D_MT_copypopup,
        MESH_MT_CopyFaceSettings,
        MESH_MT_CopyImagesFromLayer,
        MESH_MT_CopyUVCoordsFromLayer,
        MESH_MT_CopyVertexColorsFromLayer,
        MESH_OT_CopyFaceSettings,
        *pose_ops,
        *object_ops,
    )
    
    
        from bpy.utils import register_class
        for cls in classes:
            register_class(cls)
    
        # mostly to get the keymap working
    
        kc = bpy.context.window_manager.keyconfigs.addon
    
        if kc:
            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.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'
    
            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'
    
        # mostly to remove the keymap
    
        kc = bpy.context.window_manager.keyconfigs.addon
        if kc:
    
            kms = kc.keymaps.get('Pose')
            if kms is not None:
                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
    
            km = kc.keymaps.get('Mesh')
            if km is not None:
                for kmi in km.keymap_items:
                    if kmi.idname == 'wm.call_menu':
                        if kmi.properties.name == 'MESH_MT_CopyFaceSettings':
                            km.keymap_items.remove(kmi)
    
            km = kc.keymaps.get('Object Mode')
            if km is not None:
                for kmi in km.keymap_items:
                    if kmi.idname == 'wm.call_menu':
                        if kmi.properties.name == 'VIEW3D_MT_copypopup':
                            km.keymap_items.remove(kmi)
    
        from bpy.utils import unregister_class
        for cls in classes:
            unregister_class(cls)
    
    if __name__ == "__main__":
    
    Guillermo S. Romero's avatar
    Guillermo S. Romero committed
        register()