# ##### 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 ##### # bl_info = { 'name': 'Copy Attributes Menu', 'author': 'Bassam Kurdali, Fabian Fricke, wiseman303', 'version': (0, 4, 3), "blender": (2, 5, 7), "api": 36200, '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]) #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_world.to_3x3()) def obSca(ob, active, context): ob.scale = active.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_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()