Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
"author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman, Demeter Dzadik",
"version": (0, 6, 0),
"blender": (3, 4, 0),
"description": "Copy Attributes Menu",
"doc_url": "{BLENDER_MANUAL_URL}/addons/interface/copy_attributes.html",
import bpy
from bpy.types import (
from bpy.props import (
# First part of the operator Info message
INFO_MESSAGE = "Copy Attributes: "
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(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],
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_bone = bone.id_data
obj_active = active.id_data
data_bone = obj_bone.data.bones[bone.name]
# all matrices are in armature space unless commented otherwise
active_to_selected = obj_bone.matrix_world.inverted() @ obj_active.matrix_world
active_matrix = active_to_selected @ active.matrix
otherloc = active_matrix # final 4x4 mat of target, location.
bonemat_local = data_bone.matrix_local.copy() # self rest matrix
if data_bone.parent:
parentposemat = obj_bone.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
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':
rot = mat.to_3x3().to_quaternion().to_axis_angle() # returns (Vector((x, y, z)), w)
Bartek Skorupa
committed
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 to copy attributes from 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):
obj_bone = bone.id_data
rotcopy(bone, getmat(bone, active,
context, not obj_bone.data.bones[bone.name].use_inherit_rotation))
def pVisScaExec(bone, active, context):
obj_bone = bone.id_data
bone.scale = getmat(bone, active, context,
not obj_bone.data.bones[bone.name].use_inherit_scale)\
def pDrwExec(bone, active, context):
bone.custom_shape = active.custom_shape
bone.use_custom_shape_bone_size = active.use_custom_shape_bone_size
bone.custom_shape_translation = active.custom_shape_translation
bone.custom_shape_rotation_euler = active.custom_shape_rotation_euler
bone.custom_shape_scale_xyz = active.custom_shape_scale_xyz
Daniel Salazar
committed
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(
object.data.bones[bone.name],
"bbone_")
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 Transforms 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'}
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
CustomPropSelectionBoolsProperty = BoolVectorProperty(
size=32,
options={'SKIP_SAVE'}
)
class CopySelection:
"""Base class for copying properties from active to selected based on a selection."""
selection: CustomPropSelectionBoolsProperty
def draw_bools(self, button_names):
"""Draws the boolean toggle list with a list of strings for the button texts."""
layout = self.layout
for idx, name in enumerate(button_names):
layout.prop(self, "selection", index=idx, text=name,
toggle=True)
def copy_custom_property(source, destination, prop_name):
"""Copy a custom property called prop_name, from source to destination.
source and destination must be a Blender data type that can hold custom properties.
For a list of such data types, see:
https://docs.blender.org/manual/en/latest/files/data_blocks.html#files-data-blocks-custom-properties
"""
# Create the property.
destination[prop_name] = source[prop_name]
# Copy the settings of the property.
try:
dst_prop_manager = destination.id_properties_ui(prop_name)
except TypeError:
# Python values like lists or dictionaries don't have any settings to copy.
# They just consist of a value and nothing else.
return
src_prop_manager = source.id_properties_ui(prop_name)
assert src_prop_manager, f'Property "{prop_name}" not found in {source}'
dst_prop_manager.update_from(src_prop_manager)
# Copy the Library Overridable flag, which is stored elsewhere.
prop_rna_path = f'["{prop_name}"]'
is_lib_overridable = source.is_property_overridable_library(prop_rna_path)
destination.property_overridable_library_set(prop_rna_path, is_lib_overridable)
class CopyCustomProperties(CopySelection):
"""Base class for copying a selection of custom properties."""
def copy_selected_custom_props(self, active, selected):
keys = list(active.keys())
for item in selected:
if item == active:
continue
for index, is_selected in enumerate(self.selection):
if is_selected:
copy_custom_property(active, item, keys[index])
class CopySelectedBoneCustomProperties(CopyCustomProperties, Operator):
"""Copy Chosen custom properties from active to selected"""
bl_idname = "pose.copy_selected_custom_props"
bl_label = "Copy Selected Custom Properties"
bl_options = {'REGISTER', 'UNDO'}
poll = pose_poll_func
invoke = pose_invoke_func
def draw(self, context):
self.draw_bools(context.active_pose_bone.keys())
def execute(self, context):
self.copy_selected_custom_props(context.active_pose_bone, context.selected_pose_bones)
return {'FINISHED'}
class CopySelectedPoseConstraints(Operator):
"""Copy Chosen constraints from active to selected"""
bl_idname = "pose.copy_selected_constraints"
bl_label = "Copy Selected Constraints"
selection: BoolVectorProperty(
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):
Campbell Barton
committed
layout.prop(self, "selection", index=idx, text=const.name,
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:
Demeter Dzadik
committed
bone.constraints.copy(active.constraints[index])
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'
for op in pose_copies:
layout.operator("pose.copy_" + op[0])
layout.operator("pose.copy_selected_constraints")
layout.operator("pose.copy_selected_custom_props")
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
return(mat)
# The following functions are used to 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 obDup(ob, active, context):
generic_copy(active, ob, "instance_type")
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
Bassam Kurdali
committed
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")
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,
generic_copy(old_modifier, new_modifier)
return('INFO', "Modifiers copied")
def obCollections(ob, active, context):
for collection in bpy.data.collections:
if active.name in collection.objects and ob.name not in collection.objects:
collection.objects.link(ob)
return('INFO', "Collections copied")
def obWei(ob, active, context):
# sanity check: are source and target both mesh objects?
if ob.type != 'MESH' or active.type != 'MESH':
return('ERROR', "objects have to be of mesh type, doing nothing")
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,),
return('INFO', "Weights copied")
# ('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_dup', "Instancing",
"Copy instancing properties 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_lok', "Protected Transform",
"Copy Protected Transforms 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', "Subdivision Surface Settings",
# "Copy Subdivision Surface Settings 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', "Collection Links",
"Copy selected into active object's collection", obCollections)
@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(
poll = object_poll_func
invoke = object_invoke_func
def draw(self, context):
layout = self.layout
for idx, const in enumerate(context.active_object.constraints):
Campbell Barton
committed
layout.prop(self, "selection", index=idx, text=const.name,
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:
obj.constraints.copy(active.constraints[index])
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(
poll = object_poll_func
invoke = object_invoke_func
def draw(self, context):
layout = self.layout
for idx, const in enumerate(context.active_object.modifiers):
Campbell Barton
committed
layout.prop(self, 'selection', index=idx, text=const.name,
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'}
class CopySelectedObjectCustomProperties(CopyCustomProperties, Operator):
"""Copy Chosen custom properties from active to selected objects"""
bl_idname = "object.copy_selected_custom_props"
bl_label = "Copy Selected Custom Properties"
bl_options = {'REGISTER', 'UNDO'}
poll = object_poll_func
invoke = object_invoke_func
def draw(self, context):
self.draw_bools(context.object.keys())
def execute(self, context):
self.copy_selected_custom_props(context.object, context.selected_objects)
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")
layout.operator("object.copy_selected_custom_props")
# Begin Mesh copy settings:
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_layers) > 1
vc = len(mesh.vertex_colors) > 1
layout = self.layout
op = layout.operator(mesh.copy_face_settings, text="Copy Material")
if mesh.uv_layers.active:
op = layout.operator(mesh.copy_face_settings, text="Copy Active UV Coords")
if mesh.vertex_colors.active:
op = layout.operator(mesh.copy_face_settings, text="Copy Active Vertex Colors")
op['layer'] = ''
op['mode'] = 'VCOL'
if uv or vc:
layout.separator()
if uv:
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_CopyUVCoordsFromLayer(Menu):
bl_label = "Copy UV Coordinates from Layer"
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == "EDIT_MESH" and len(
def draw(self, context):
mesh = context.active_object.data
_buildmenu(self, mesh, 'UV', "GROUP_UVS")
class MESH_MT_CopyVertexColorsFromLayer(Menu):
bl_label = "Copy Vertex Colors from Layer"
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == "EDIT_MESH" and len(
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_layers
for layer in layers:
if not layer.active:
op = layout.operator(mesh.copy_face_settings,
text=layer.name, icon=icon)
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'}
mode: StringProperty(
layer: StringProperty(
@classmethod
def poll(cls, context):
return context.mode == 'EDIT_MESH'
def execute(self, context):
if mode not in {'MAT', 'VCOL', '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()
if mode == 'MAT':
else:
if mode == 'VCOL':
layers = mesh.vertex_colors
act_layer = mesh.vertex_colors.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
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
if mode == 'MAT':
f.material_index = polys[from_index].material_index
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,
CopySelectedBoneCustomProperties,
VIEW3D_MT_posecopypopup,
CopySelectedObjectConstraints,
CopySelectedObjectModifiers,
CopySelectedObjectCustomProperties,
VIEW3D_MT_copypopup,
MESH_MT_CopyFaceSettings,
MESH_MT_CopyUVCoordsFromLayer,
MESH_MT_CopyVertexColorsFromLayer,
MESH_OT_CopyFaceSettings,
*pose_ops,
*object_ops,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
Campbell Barton
committed
# mostly to get the keymap working
kc = bpy.context.window_manager.keyconfigs.addon
if kc:
km = kc.keymaps.new(name="Object Mode")
Campbell Barton
committed
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'
def unregister():
# 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__":