io_mesh_uv_layout: count no longer a keyword only arg
[blender-addons.git] / space_view3d_copy_attributes.py
blob3fca5d111f5f92030c903e9bfed17e9c166ff8a8
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 bl_info = {
22 "name": "Copy Attributes Menu",
23 "author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman",
24 "version": (0, 4, 8),
25 "blender": (2, 80, 0),
26 "location": "View3D > Ctrl-C",
27 "description": "Copy Attributes Menu from Blender 2.4",
28 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
29 "Scripts/3D_interaction/Copy_Attributes_Menu",
30 "category": "3D View",
33 import bpy
34 from mathutils import Matrix
35 from bpy.types import (
36 Operator,
37 Menu,
39 from bpy.props import (
40 BoolVectorProperty,
41 StringProperty,
44 # First part of the operator Info message
45 INFO_MESSAGE = "Copy Attributes: "
48 def build_exec(loopfunc, func):
49 """Generator function that returns exec functions for operators """
51 def exec_func(self, context):
52 loopfunc(self, context, func)
53 return {'FINISHED'}
54 return exec_func
57 def build_invoke(loopfunc, func):
58 """Generator function that returns invoke functions for operators"""
60 def invoke_func(self, context, event):
61 loopfunc(self, context, func)
62 return {'FINISHED'}
63 return invoke_func
66 def build_op(idname, label, description, fpoll, fexec, finvoke):
67 """Generator function that returns the basic operator"""
69 class myopic(Operator):
70 bl_idname = idname
71 bl_label = label
72 bl_description = description
73 execute = fexec
74 poll = fpoll
75 invoke = finvoke
76 return myopic
79 def genops(copylist, oplist, prefix, poll_func, loopfunc):
80 """Generate ops from the copy list and its associated functions"""
81 for op in copylist:
82 exec_func = build_exec(loopfunc, op[3])
83 invoke_func = build_invoke(loopfunc, op[3])
84 opclass = build_op(prefix + op[0], "Copy " + op[1], op[2],
85 poll_func, exec_func, invoke_func)
86 oplist.append(opclass)
89 def generic_copy(source, target, string=""):
90 """Copy attributes from source to target that have string in them"""
91 for attr in dir(source):
92 if attr.find(string) > -1:
93 try:
94 setattr(target, attr, getattr(source, attr))
95 except:
96 pass
97 return
100 def getmat(bone, active, context, ignoreparent):
101 """Helper function for visual transform copy,
102 gets the active transform in bone space
104 obj_bone = bone.id_data
105 obj_active = active.id_data
106 data_bone = obj_bone.data.bones[bone.name]
107 # all matrices are in armature space unless commented otherwise
108 active_to_selected = obj_bone.matrix_world.inverted() @ obj_active.matrix_world
109 active_matrix = active_to_selected @ active.matrix
110 otherloc = active_matrix # final 4x4 mat of target, location.
111 bonemat_local = data_bone.matrix_local.copy() # self rest matrix
112 if data_bone.parent:
113 parentposemat = obj_bone.pose.bones[data_bone.parent.name].matrix.copy()
114 parentbonemat = data_bone.parent.matrix_local.copy()
115 else:
116 parentposemat = parentbonemat = Matrix()
117 if parentbonemat == parentposemat or ignoreparent:
118 newmat = bonemat_local.inverted() @ otherloc
119 else:
120 bonemat = parentbonemat.inverted() @ bonemat_local
122 newmat = bonemat.inverted() @ parentposemat.inverted() @ otherloc
123 return newmat
126 def rotcopy(item, mat):
127 """Copy rotation to item from matrix mat depending on item.rotation_mode"""
128 if item.rotation_mode == 'QUATERNION':
129 item.rotation_quaternion = mat.to_3x3().to_quaternion()
130 elif item.rotation_mode == 'AXIS_ANGLE':
131 rot = mat.to_3x3().to_quaternion().to_axis_angle() # returns (Vector((x, y, z)), w)
132 axis_angle = rot[1], rot[0][0], rot[0][1], rot[0][2] # convert to w, x, y, z
133 item.rotation_axis_angle = axis_angle
134 else:
135 item.rotation_euler = mat.to_3x3().to_euler(item.rotation_mode)
138 def pLoopExec(self, context, funk):
139 """Loop over selected bones and execute funk on them"""
140 active = context.active_pose_bone
141 selected = context.selected_pose_bones
142 selected.remove(active)
143 for bone in selected:
144 funk(bone, active, context)
147 # The following functions are used o copy attributes frome active to bone
149 def pLocLocExec(bone, active, context):
150 bone.location = active.location
153 def pLocRotExec(bone, active, context):
154 rotcopy(bone, active.matrix_basis.to_3x3())
157 def pLocScaExec(bone, active, context):
158 bone.scale = active.scale
161 def pVisLocExec(bone, active, context):
162 bone.location = getmat(bone, active, context, False).to_translation()
165 def pVisRotExec(bone, active, context):
166 obj_bone = bone.id_data
167 rotcopy(bone, getmat(bone, active,
168 context, not obj_bone.data.bones[bone.name].use_inherit_rotation))
171 def pVisScaExec(bone, active, context):
172 obj_bone = bone.id_data
173 bone.scale = getmat(bone, active, context,
174 not obj_bone.data.bones[bone.name].use_inherit_scale)\
175 .to_scale()
178 def pDrwExec(bone, active, context):
179 bone.custom_shape = active.custom_shape
180 bone.use_custom_shape_bone_size = active.use_custom_shape_bone_size
181 bone.custom_shape_scale = active.custom_shape_scale
182 bone.bone.show_wire = active.bone.show_wire
185 def pLokExec(bone, active, context):
186 for index, state in enumerate(active.lock_location):
187 bone.lock_location[index] = state
188 for index, state in enumerate(active.lock_rotation):
189 bone.lock_rotation[index] = state
190 bone.lock_rotations_4d = active.lock_rotations_4d
191 bone.lock_rotation_w = active.lock_rotation_w
192 for index, state in enumerate(active.lock_scale):
193 bone.lock_scale[index] = state
196 def pConExec(bone, active, context):
197 for old_constraint in active.constraints.values():
198 new_constraint = bone.constraints.new(old_constraint.type)
199 generic_copy(old_constraint, new_constraint)
202 def pIKsExec(bone, active, context):
203 generic_copy(active, bone, "ik_")
206 def pBBonesExec(bone, active, context):
207 object = active.id_data
208 generic_copy(
209 object.data.bones[active.name],
210 object.data.bones[bone.name],
211 "bbone_")
214 pose_copies = (
215 ('pose_loc_loc', "Local Location",
216 "Copy Location from Active to Selected", pLocLocExec),
217 ('pose_loc_rot', "Local Rotation",
218 "Copy Rotation from Active to Selected", pLocRotExec),
219 ('pose_loc_sca', "Local Scale",
220 "Copy Scale from Active to Selected", pLocScaExec),
221 ('pose_vis_loc', "Visual Location",
222 "Copy Location from Active to Selected", pVisLocExec),
223 ('pose_vis_rot', "Visual Rotation",
224 "Copy Rotation from Active to Selected", pVisRotExec),
225 ('pose_vis_sca', "Visual Scale",
226 "Copy Scale from Active to Selected", pVisScaExec),
227 ('pose_drw', "Bone Shape",
228 "Copy Bone Shape from Active to Selected", pDrwExec),
229 ('pose_lok', "Protected Transform",
230 "Copy Protected Tranforms from Active to Selected", pLokExec),
231 ('pose_con', "Bone Constraints",
232 "Copy Object Constraints from Active to Selected", pConExec),
233 ('pose_iks', "IK Limits",
234 "Copy IK Limits from Active to Selected", pIKsExec),
235 ('bbone_settings', "BBone Settings",
236 "Copy BBone Settings from Active to Selected", pBBonesExec),
240 @classmethod
241 def pose_poll_func(cls, context):
242 return(context.mode == 'POSE')
245 def pose_invoke_func(self, context, event):
246 wm = context.window_manager
247 wm.invoke_props_dialog(self)
248 return {'RUNNING_MODAL'}
251 class CopySelectedPoseConstraints(Operator):
252 """Copy Chosen constraints from active to selected"""
253 bl_idname = "pose.copy_selected_constraints"
254 bl_label = "Copy Selected Constraints"
256 selection: BoolVectorProperty(
257 size=32,
258 options={'SKIP_SAVE'}
261 poll = pose_poll_func
262 invoke = pose_invoke_func
264 def draw(self, context):
265 layout = self.layout
266 for idx, const in enumerate(context.active_pose_bone.constraints):
267 layout.prop(self, "selection", index=idx, text=const.name,
268 toggle=True)
270 def execute(self, context):
271 active = context.active_pose_bone
272 selected = context.selected_pose_bones[:]
273 selected.remove(active)
274 for bone in selected:
275 for index, flag in enumerate(self.selection):
276 if flag:
277 old_constraint = active.constraints[index]
278 new_constraint = bone.constraints.new(
279 active.constraints[index].type
281 generic_copy(old_constraint, new_constraint)
282 return {'FINISHED'}
285 pose_ops = [] # list of pose mode copy operators
286 genops(pose_copies, pose_ops, "pose.copy_", pose_poll_func, pLoopExec)
289 class VIEW3D_MT_posecopypopup(Menu):
290 bl_label = "Copy Attributes"
292 def draw(self, context):
293 layout = self.layout
294 layout.operator_context = 'INVOKE_REGION_WIN'
295 layout.operator("view3d.copybuffer", icon="COPY_ID")
296 for op in pose_copies:
297 layout.operator("pose.copy_" + op[0])
298 layout.operator("pose.copy_selected_constraints")
299 layout.operator("pose.copy", text="copy pose")
302 def obLoopExec(self, context, funk):
303 """Loop over selected objects and execute funk on them"""
304 active = context.active_object
305 selected = context.selected_objects[:]
306 selected.remove(active)
307 for obj in selected:
308 msg = funk(obj, active, context)
309 if msg:
310 self.report({msg[0]}, INFO_MESSAGE + msg[1])
313 def world_to_basis(active, ob, context):
314 """put world coords of active as basis coords of ob"""
315 local = ob.parent.matrix_world.inverted() @ active.matrix_world
316 P = ob.matrix_basis @ ob.matrix_local.inverted()
317 mat = P @ local
318 return(mat)
321 # The following functions are used o copy attributes from
322 # active to selected object
324 def obLoc(ob, active, context):
325 ob.location = active.location
328 def obRot(ob, active, context):
329 rotcopy(ob, active.matrix_local.to_3x3())
332 def obSca(ob, active, context):
333 ob.scale = active.scale
336 def obVisLoc(ob, active, context):
337 if ob.parent:
338 mat = world_to_basis(active, ob, context)
339 ob.location = mat.to_translation()
340 else:
341 ob.location = active.matrix_world.to_translation()
342 return('INFO', "Object location copied")
345 def obVisRot(ob, active, context):
346 if ob.parent:
347 mat = world_to_basis(active, ob, context)
348 rotcopy(ob, mat.to_3x3())
349 else:
350 rotcopy(ob, active.matrix_world.to_3x3())
351 return('INFO', "Object rotation copied")
354 def obVisSca(ob, active, context):
355 if ob.parent:
356 mat = world_to_basis(active, ob, context)
357 ob.scale = mat.to_scale()
358 else:
359 ob.scale = active.matrix_world.to_scale()
360 return('INFO', "Object scale copied")
363 def obDrw(ob, active, context):
364 ob.display_type = active.display_type
365 ob.show_axis = active.show_axis
366 ob.show_bounds = active.show_bounds
367 ob.display_bounds_type = active.display_bounds_type
368 ob.show_name = active.show_name
369 ob.show_texture_space = active.show_texture_space
370 ob.show_transparent = active.show_transparent
371 ob.show_wire = active.show_wire
372 ob.show_in_front = active.show_in_front
373 ob.empty_display_type = active.empty_display_type
374 ob.empty_display_size = active.empty_display_size
377 def obOfs(ob, active, context):
378 ob.time_offset = active.time_offset
379 return('INFO', "Time offset copied")
382 def obDup(ob, active, context):
383 generic_copy(active, ob, "dupli")
384 return('INFO', "Duplication method copied")
387 def obCol(ob, active, context):
388 ob.color = active.color
391 def obLok(ob, active, context):
392 for index, state in enumerate(active.lock_location):
393 ob.lock_location[index] = state
394 for index, state in enumerate(active.lock_rotation):
395 ob.lock_rotation[index] = state
396 ob.lock_rotations_4d = active.lock_rotations_4d
397 ob.lock_rotation_w = active.lock_rotation_w
398 for index, state in enumerate(active.lock_scale):
399 ob.lock_scale[index] = state
400 return('INFO', "Transform locks copied")
403 def obCon(ob, active, context):
404 # for consistency with 2.49, delete old constraints first
405 for removeconst in ob.constraints:
406 ob.constraints.remove(removeconst)
407 for old_constraint in active.constraints.values():
408 new_constraint = ob.constraints.new(old_constraint.type)
409 generic_copy(old_constraint, new_constraint)
410 return('INFO', "Constraints copied")
413 def obTex(ob, active, context):
414 if 'texspace_location' in dir(ob.data) and 'texspace_location' in dir(
415 active.data):
416 ob.data.texspace_location[:] = active.data.texspace_location[:]
417 if 'texspace_size' in dir(ob.data) and 'texspace_size' in dir(active.data):
418 ob.data.texspace_size[:] = active.data.texspace_size[:]
419 return('INFO', "Texture space copied")
422 def obIdx(ob, active, context):
423 ob.pass_index = active.pass_index
424 return('INFO', "Pass index copied")
427 def obMod(ob, active, context):
428 for modifier in ob.modifiers:
429 # remove existing before adding new:
430 ob.modifiers.remove(modifier)
431 for old_modifier in active.modifiers.values():
432 new_modifier = ob.modifiers.new(name=old_modifier.name,
433 type=old_modifier.type)
434 generic_copy(old_modifier, new_modifier)
435 return('INFO', "Modifiers copied")
438 def obGrp(ob, active, context):
439 for grp in bpy.data.groups:
440 if active.name in grp.objects and ob.name not in grp.objects:
441 grp.objects.link(ob)
442 return('INFO', "Groups copied")
445 def obWei(ob, active, context):
446 me_source = active.data
447 me_target = ob.data
448 # sanity check: do source and target have the same amount of verts?
449 if len(me_source.vertices) != len(me_target.vertices):
450 return('ERROR', "objects have different vertex counts, doing nothing")
451 vgroups_IndexName = {}
452 for i in range(0, len(active.vertex_groups)):
453 groups = active.vertex_groups[i]
454 vgroups_IndexName[groups.index] = groups.name
455 data = {} # vert_indices, [(vgroup_index, weights)]
456 for v in me_source.vertices:
457 vg = v.groups
458 vi = v.index
459 if len(vg) > 0:
460 vgroup_collect = []
461 for i in range(0, len(vg)):
462 vgroup_collect.append((vg[i].group, vg[i].weight))
463 data[vi] = vgroup_collect
464 # write data to target
465 if ob != active:
466 # add missing vertex groups
467 for vgroup_name in vgroups_IndexName.values():
468 # check if group already exists...
469 already_present = 0
470 for i in range(0, len(ob.vertex_groups)):
471 if ob.vertex_groups[i].name == vgroup_name:
472 already_present = 1
473 # ... if not, then add
474 if already_present == 0:
475 ob.vertex_groups.new(name=vgroup_name)
476 # write weights
477 for v in me_target.vertices:
478 for vi_source, vgroupIndex_weight in data.items():
479 if v.index == vi_source:
481 for i in range(0, len(vgroupIndex_weight)):
482 groupName = vgroups_IndexName[vgroupIndex_weight[i][0]]
483 groups = ob.vertex_groups
484 for vgs in range(0, len(groups)):
485 if groups[vgs].name == groupName:
486 groups[vgs].add((v.index,),
487 vgroupIndex_weight[i][1], "REPLACE")
488 return('INFO', "Weights copied")
491 object_copies = (
492 # ('obj_loc', "Location",
493 # "Copy Location from Active to Selected", obLoc),
494 # ('obj_rot', "Rotation",
495 # "Copy Rotation from Active to Selected", obRot),
496 # ('obj_sca', "Scale",
497 # "Copy Scale from Active to Selected", obSca),
498 ('obj_vis_loc', "Location",
499 "Copy Location from Active to Selected", obVisLoc),
500 ('obj_vis_rot', "Rotation",
501 "Copy Rotation from Active to Selected", obVisRot),
502 ('obj_vis_sca', "Scale",
503 "Copy Scale from Active to Selected", obVisSca),
504 ('obj_drw', "Draw Options",
505 "Copy Draw Options from Active to Selected", obDrw),
506 ('obj_ofs', "Time Offset",
507 "Copy Time Offset from Active to Selected", obOfs),
508 ('obj_dup', "Dupli",
509 "Copy Dupli from Active to Selected", obDup),
510 ('obj_col', "Object Color",
511 "Copy Object Color from Active to Selected", obCol),
512 # ('obj_dmp', "Damping",
513 # "Copy Damping from Active to Selected"),
514 # ('obj_all', "All Physical Attributes",
515 # "Copy Physical Attributes from Active to Selected"),
516 # ('obj_prp', "Properties",
517 # "Copy Properties from Active to Selected"),
518 # ('obj_log', "Logic Bricks",
519 # "Copy Logic Bricks from Active to Selected"),
520 ('obj_lok', "Protected Transform",
521 "Copy Protected Tranforms from Active to Selected", obLok),
522 ('obj_con', "Object Constraints",
523 "Copy Object Constraints from Active to Selected", obCon),
524 # ('obj_nla', "NLA Strips",
525 # "Copy NLA Strips from Active to Selected"),
526 # ('obj_tex', "Texture Space",
527 # "Copy Texture Space from Active to Selected", obTex),
528 # ('obj_sub', "Subsurf Settings",
529 # "Copy Subsurf Setings from Active to Selected"),
530 # ('obj_smo', "AutoSmooth",
531 # "Copy AutoSmooth from Active to Selected"),
532 ('obj_idx', "Pass Index",
533 "Copy Pass Index from Active to Selected", obIdx),
534 ('obj_mod', "Modifiers",
535 "Copy Modifiers from Active to Selected", obMod),
536 ('obj_wei', "Vertex Weights",
537 "Copy vertex weights based on indices", obWei),
538 ('obj_grp', "Group Links",
539 "Copy selected into active object's groups", obGrp)
543 @classmethod
544 def object_poll_func(cls, context):
545 return (len(context.selected_objects) > 1)
548 def object_invoke_func(self, context, event):
549 wm = context.window_manager
550 wm.invoke_props_dialog(self)
551 return {'RUNNING_MODAL'}
554 class CopySelectedObjectConstraints(Operator):
555 """Copy Chosen constraints from active to selected"""
556 bl_idname = "object.copy_selected_constraints"
557 bl_label = "Copy Selected Constraints"
559 selection: BoolVectorProperty(
560 size=32,
561 options={'SKIP_SAVE'}
564 poll = object_poll_func
565 invoke = object_invoke_func
567 def draw(self, context):
568 layout = self.layout
569 for idx, const in enumerate(context.active_object.constraints):
570 layout.prop(self, "selection", index=idx, text=const.name,
571 toggle=True)
573 def execute(self, context):
574 active = context.active_object
575 selected = context.selected_objects[:]
576 selected.remove(active)
577 for obj in selected:
578 for index, flag in enumerate(self.selection):
579 if flag:
580 old_constraint = active.constraints[index]
581 new_constraint = obj.constraints.new(
582 active.constraints[index].type
584 generic_copy(old_constraint, new_constraint)
585 return{'FINISHED'}
588 class CopySelectedObjectModifiers(Operator):
589 """Copy Chosen modifiers from active to selected"""
590 bl_idname = "object.copy_selected_modifiers"
591 bl_label = "Copy Selected Modifiers"
593 selection: BoolVectorProperty(
594 size=32,
595 options={'SKIP_SAVE'}
598 poll = object_poll_func
599 invoke = object_invoke_func
601 def draw(self, context):
602 layout = self.layout
603 for idx, const in enumerate(context.active_object.modifiers):
604 layout.prop(self, 'selection', index=idx, text=const.name,
605 toggle=True)
607 def execute(self, context):
608 active = context.active_object
609 selected = context.selected_objects[:]
610 selected.remove(active)
611 for obj in selected:
612 for index, flag in enumerate(self.selection):
613 if flag:
614 old_modifier = active.modifiers[index]
615 new_modifier = obj.modifiers.new(
616 type=active.modifiers[index].type,
617 name=active.modifiers[index].name
619 generic_copy(old_modifier, new_modifier)
620 return{'FINISHED'}
623 object_ops = []
624 genops(object_copies, object_ops, "object.copy_", object_poll_func, obLoopExec)
627 class VIEW3D_MT_copypopup(Menu):
628 bl_label = "Copy Attributes"
630 def draw(self, context):
631 layout = self.layout
633 layout.operator_context = 'INVOKE_REGION_WIN'
634 layout.operator("view3d.copybuffer", icon="COPY_ID")
636 if (len(context.selected_objects) <= 1):
637 layout.separator()
638 layout.label(text="Please select at least two objects", icon="INFO")
639 layout.separator()
641 for entry, op in enumerate(object_copies):
642 if entry and entry % 4 == 0:
643 layout.separator()
644 layout.operator("object.copy_" + op[0])
645 layout.operator("object.copy_selected_constraints")
646 layout.operator("object.copy_selected_modifiers")
649 # Begin Mesh copy settings:
651 class MESH_MT_CopyFaceSettings(Menu):
652 bl_label = "Copy Face Settings"
654 @classmethod
655 def poll(cls, context):
656 return context.mode == 'EDIT_MESH'
658 def draw(self, context):
659 mesh = context.object.data
660 uv = len(mesh.uv_textures) > 1
661 vc = len(mesh.vertex_colors) > 1
663 layout = self.layout
664 layout.operator("view3d.copybuffer", icon="COPY_ID")
665 layout.operator("view3d.pastebuffer", icon="COPY_ID")
667 layout.separator()
669 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
670 text="Copy Material")
671 op['layer'] = ''
672 op['mode'] = 'MAT'
674 if mesh.uv_textures.active:
675 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
676 text="Copy Active UV Image")
677 op['layer'] = ''
678 op['mode'] = 'IMAGE'
679 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
680 text="Copy Active UV Coords")
681 op['layer'] = ''
682 op['mode'] = 'UV'
684 if mesh.vertex_colors.active:
685 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
686 text="Copy Active Vertex Colors")
687 op['layer'] = ''
688 op['mode'] = 'VCOL'
690 if uv or vc:
691 layout.separator()
692 if uv:
693 layout.menu("MESH_MT_CopyImagesFromLayer")
694 layout.menu("MESH_MT_CopyUVCoordsFromLayer")
695 if vc:
696 layout.menu("MESH_MT_CopyVertexColorsFromLayer")
699 # Data (UV map, Image and Vertex color) menus calling MESH_OT_CopyFaceSettings
700 # Explicitly defined as using the generator code was broken in case of Menus
701 # causing issues with access and registration
703 class MESH_MT_CopyImagesFromLayer(Menu):
704 bl_label = "Copy Other UV Image Layers"
706 @classmethod
707 def poll(cls, context):
708 obj = context.active_object
709 return obj and obj.mode == "EDIT_MESH" and len(
710 obj.data.uv_layers) > 1
712 def draw(self, context):
713 mesh = context.active_object.data
714 _buildmenu(self, mesh, 'IMAGE', "IMAGE_COL")
717 class MESH_MT_CopyUVCoordsFromLayer(Menu):
718 bl_label = "Copy Other UV Coord Layers"
720 @classmethod
721 def poll(cls, context):
722 obj = context.active_object
723 return obj and obj.mode == "EDIT_MESH" and len(
724 obj.data.uv_layers) > 1
726 def draw(self, context):
727 mesh = context.active_object.data
728 _buildmenu(self, mesh, 'UV', "GROUP_UVS")
731 class MESH_MT_CopyVertexColorsFromLayer(Menu):
732 bl_label = "Copy Other Vertex Colors Layers"
734 @classmethod
735 def poll(cls, context):
736 obj = context.active_object
737 return obj and obj.mode == "EDIT_MESH" and len(
738 obj.data.vertex_colors) > 1
740 def draw(self, context):
741 mesh = context.active_object.data
742 _buildmenu(self, mesh, 'VCOL', "GROUP_VCOL")
745 def _buildmenu(self, mesh, mode, icon):
746 layout = self.layout
747 if mode == 'VCOL':
748 layers = mesh.vertex_colors
749 else:
750 layers = mesh.uv_textures
751 for layer in layers:
752 if not layer.active:
753 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
754 text=layer.name, icon=icon)
755 op['layer'] = layer.name
756 op['mode'] = mode
759 class MESH_OT_CopyFaceSettings(Operator):
760 """Copy settings from active face to all selected faces"""
761 bl_idname = 'mesh.copy_face_settings'
762 bl_label = "Copy Face Settings"
763 bl_options = {'REGISTER', 'UNDO'}
765 mode: StringProperty(
766 name="Mode",
767 options={"HIDDEN"},
769 layer: StringProperty(
770 name="Layer",
771 options={"HIDDEN"},
774 @classmethod
775 def poll(cls, context):
776 return context.mode == 'EDIT_MESH'
778 def execute(self, context):
779 mode = getattr(self, 'mode', '')
780 if mode not in {'MAT', 'VCOL', 'IMAGE', 'UV'}:
781 self.report({'ERROR'}, "No mode specified or invalid mode")
782 return self._end(context, {'CANCELLED'})
783 layername = getattr(self, 'layer', '')
784 mesh = context.object.data
786 # Switching out of edit mode updates the selected state of faces and
787 # makes the data from the uv texture and vertex color layers available.
788 bpy.ops.object.editmode_toggle()
790 polys = mesh.polygons
791 if mode == 'MAT':
792 to_data = from_data = polys
793 else:
794 if mode == 'VCOL':
795 layers = mesh.vertex_colors
796 act_layer = mesh.vertex_colors.active
797 elif mode == 'IMAGE':
798 layers = mesh.uv_textures
799 act_layer = mesh.uv_textures.active
800 elif mode == 'UV':
801 layers = mesh.uv_layers
802 act_layer = mesh.uv_layers.active
803 if not layers or (layername and layername not in layers):
804 self.report({'ERROR'}, "Invalid UV or color layer. Operation Cancelled")
805 return self._end(context, {'CANCELLED'})
806 from_data = layers[layername or act_layer.name].data
807 to_data = act_layer.data
808 from_index = polys.active
810 for f in polys:
811 if f.select:
812 if to_data != from_data:
813 # Copying from another layer.
814 # from_face is to_face's counterpart from other layer.
815 from_index = f.index
816 elif f.index == from_index:
817 # Otherwise skip copying a face to itself.
818 continue
819 if mode == 'MAT':
820 f.material_index = polys[from_index].material_index
821 continue
822 elif mode == 'IMAGE':
823 to_data[f.index].image = from_data[from_index].image
824 continue
825 if len(f.loop_indices) != len(polys[from_index].loop_indices):
826 self.report({'WARNING'}, "Different number of vertices.")
827 for i in range(len(f.loop_indices)):
828 to_vertex = f.loop_indices[i]
829 from_vertex = polys[from_index].loop_indices[i]
830 if mode == 'VCOL':
831 to_data[to_vertex].color = from_data[from_vertex].color
832 elif mode == 'UV':
833 to_data[to_vertex].uv = from_data[from_vertex].uv
835 return self._end(context, {'FINISHED'})
837 def _end(self, context, retval):
838 if context.mode != 'EDIT_MESH':
839 # Clean up by returning to edit mode like it was before.
840 bpy.ops.object.editmode_toggle()
841 return(retval)
844 classes = (
845 CopySelectedPoseConstraints,
846 VIEW3D_MT_posecopypopup,
847 CopySelectedObjectConstraints,
848 CopySelectedObjectModifiers,
849 VIEW3D_MT_copypopup,
850 MESH_MT_CopyFaceSettings,
851 MESH_MT_CopyImagesFromLayer,
852 MESH_MT_CopyUVCoordsFromLayer,
853 MESH_MT_CopyVertexColorsFromLayer,
854 MESH_OT_CopyFaceSettings,
855 *pose_ops,
856 *object_ops,
859 def register():
860 from bpy.utils import register_class
861 for cls in classes:
862 register_class(cls)
864 # mostly to get the keymap working
865 kc = bpy.context.window_manager.keyconfigs.addon
866 if kc:
867 km = kc.keymaps.new(name="Object Mode")
868 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
869 kmi.properties.name = 'VIEW3D_MT_copypopup'
871 km = kc.keymaps.new(name="Pose")
872 kmi = km.keymap_items.get("pose.copy")
873 if kmi is not None:
874 kmi.idname = 'wm.call_menu'
875 else:
876 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
877 kmi.properties.name = 'VIEW3D_MT_posecopypopup'
879 km = kc.keymaps.new(name="Mesh")
880 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS')
881 kmi.ctrl = True
882 kmi.properties.name = 'MESH_MT_CopyFaceSettings'
885 def unregister():
886 # mostly to remove the keymap
887 kc = bpy.context.window_manager.keyconfigs.addon
888 if kc:
889 kms = kc.keymaps.get('Pose')
890 if kms is not None:
891 for item in kms.keymap_items:
892 if item.name == 'Call Menu' and item.idname == 'wm.call_menu' and \
893 item.properties.name == 'VIEW3D_MT_posecopypopup':
894 item.idname = 'pose.copy'
895 break
897 km = kc.keymaps.get('Mesh')
898 if km is not None:
899 for kmi in km.keymap_items:
900 if kmi.idname == 'wm.call_menu':
901 if kmi.properties.name == 'MESH_MT_CopyFaceSettings':
902 km.keymap_items.remove(kmi)
904 km = kc.keymaps.get('Object Mode')
905 if km is not None:
906 for kmi in km.keymap_items:
907 if kmi.idname == 'wm.call_menu':
908 if kmi.properties.name == 'VIEW3D_MT_copypopup':
909 km.keymap_items.remove(kmi)
911 from bpy.utils import unregister_class
912 for cls in classes:
913 unregister_class(cls)
916 if __name__ == "__main__":
917 register()