Cleanup: trailing space
[blender-addons.git] / space_view3d_copy_attributes.py
blobda7417375d2288cace4efe60a9846653e1f08e54
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, 9),
25 "blender": (2, 80, 0),
26 "location": "View3D > Ctrl-C",
27 "description": "Copy Attributes Menu from Blender 2.4",
28 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/copy_attributes.html",
29 "category": "Interface",
32 import bpy
33 from mathutils import Matrix
34 from bpy.types import (
35 Operator,
36 Menu,
38 from bpy.props import (
39 BoolVectorProperty,
40 StringProperty,
43 # First part of the operator Info message
44 INFO_MESSAGE = "Copy Attributes: "
47 def build_exec(loopfunc, func):
48 """Generator function that returns exec functions for operators """
50 def exec_func(self, context):
51 loopfunc(self, context, func)
52 return {'FINISHED'}
53 return exec_func
56 def build_invoke(loopfunc, func):
57 """Generator function that returns invoke functions for operators"""
59 def invoke_func(self, context, event):
60 loopfunc(self, context, func)
61 return {'FINISHED'}
62 return invoke_func
65 def build_op(idname, label, description, fpoll, fexec, finvoke):
66 """Generator function that returns the basic operator"""
68 class myopic(Operator):
69 bl_idname = idname
70 bl_label = label
71 bl_description = description
72 execute = fexec
73 poll = fpoll
74 invoke = finvoke
75 return myopic
78 def genops(copylist, oplist, prefix, poll_func, loopfunc):
79 """Generate ops from the copy list and its associated functions"""
80 for op in copylist:
81 exec_func = build_exec(loopfunc, op[3])
82 invoke_func = build_invoke(loopfunc, op[3])
83 opclass = build_op(prefix + op[0], "Copy " + op[1], op[2],
84 poll_func, exec_func, invoke_func)
85 oplist.append(opclass)
88 def generic_copy(source, target, string=""):
89 """Copy attributes from source to target that have string in them"""
90 for attr in dir(source):
91 if attr.find(string) > -1:
92 try:
93 setattr(target, attr, getattr(source, attr))
94 except:
95 pass
96 return
99 def getmat(bone, active, context, ignoreparent):
100 """Helper function for visual transform copy,
101 gets the active transform in bone space
103 obj_bone = bone.id_data
104 obj_active = active.id_data
105 data_bone = obj_bone.data.bones[bone.name]
106 # all matrices are in armature space unless commented otherwise
107 active_to_selected = obj_bone.matrix_world.inverted() @ obj_active.matrix_world
108 active_matrix = active_to_selected @ active.matrix
109 otherloc = active_matrix # final 4x4 mat of target, location.
110 bonemat_local = data_bone.matrix_local.copy() # self rest matrix
111 if data_bone.parent:
112 parentposemat = obj_bone.pose.bones[data_bone.parent.name].matrix.copy()
113 parentbonemat = data_bone.parent.matrix_local.copy()
114 else:
115 parentposemat = parentbonemat = Matrix()
116 if parentbonemat == parentposemat or ignoreparent:
117 newmat = bonemat_local.inverted() @ otherloc
118 else:
119 bonemat = parentbonemat.inverted() @ bonemat_local
121 newmat = bonemat.inverted() @ parentposemat.inverted() @ otherloc
122 return newmat
125 def rotcopy(item, mat):
126 """Copy rotation to item from matrix mat depending on item.rotation_mode"""
127 if item.rotation_mode == 'QUATERNION':
128 item.rotation_quaternion = mat.to_3x3().to_quaternion()
129 elif item.rotation_mode == 'AXIS_ANGLE':
130 rot = mat.to_3x3().to_quaternion().to_axis_angle() # returns (Vector((x, y, z)), w)
131 axis_angle = rot[1], rot[0][0], rot[0][1], rot[0][2] # convert to w, x, y, z
132 item.rotation_axis_angle = axis_angle
133 else:
134 item.rotation_euler = mat.to_3x3().to_euler(item.rotation_mode)
137 def pLoopExec(self, context, funk):
138 """Loop over selected bones and execute funk on them"""
139 active = context.active_pose_bone
140 selected = context.selected_pose_bones
141 selected.remove(active)
142 for bone in selected:
143 funk(bone, active, context)
146 # The following functions are used to copy attributes from active to bone
148 def pLocLocExec(bone, active, context):
149 bone.location = active.location
152 def pLocRotExec(bone, active, context):
153 rotcopy(bone, active.matrix_basis.to_3x3())
156 def pLocScaExec(bone, active, context):
157 bone.scale = active.scale
160 def pVisLocExec(bone, active, context):
161 bone.location = getmat(bone, active, context, False).to_translation()
164 def pVisRotExec(bone, active, context):
165 obj_bone = bone.id_data
166 rotcopy(bone, getmat(bone, active,
167 context, not obj_bone.data.bones[bone.name].use_inherit_rotation))
170 def pVisScaExec(bone, active, context):
171 obj_bone = bone.id_data
172 bone.scale = getmat(bone, active, context,
173 not obj_bone.data.bones[bone.name].use_inherit_scale)\
174 .to_scale()
177 def pDrwExec(bone, active, context):
178 bone.custom_shape = active.custom_shape
179 bone.use_custom_shape_bone_size = active.use_custom_shape_bone_size
180 bone.custom_shape_scale = active.custom_shape_scale
181 bone.bone.show_wire = active.bone.show_wire
184 def pLokExec(bone, active, context):
185 for index, state in enumerate(active.lock_location):
186 bone.lock_location[index] = state
187 for index, state in enumerate(active.lock_rotation):
188 bone.lock_rotation[index] = state
189 bone.lock_rotations_4d = active.lock_rotations_4d
190 bone.lock_rotation_w = active.lock_rotation_w
191 for index, state in enumerate(active.lock_scale):
192 bone.lock_scale[index] = state
195 def pConExec(bone, active, context):
196 for old_constraint in active.constraints.values():
197 new_constraint = bone.constraints.new(old_constraint.type)
198 generic_copy(old_constraint, new_constraint)
201 def pIKsExec(bone, active, context):
202 generic_copy(active, bone, "ik_")
205 def pBBonesExec(bone, active, context):
206 object = active.id_data
207 generic_copy(
208 object.data.bones[active.name],
209 object.data.bones[bone.name],
210 "bbone_")
213 pose_copies = (
214 ('pose_loc_loc', "Local Location",
215 "Copy Location from Active to Selected", pLocLocExec),
216 ('pose_loc_rot', "Local Rotation",
217 "Copy Rotation from Active to Selected", pLocRotExec),
218 ('pose_loc_sca', "Local Scale",
219 "Copy Scale from Active to Selected", pLocScaExec),
220 ('pose_vis_loc', "Visual Location",
221 "Copy Location from Active to Selected", pVisLocExec),
222 ('pose_vis_rot', "Visual Rotation",
223 "Copy Rotation from Active to Selected", pVisRotExec),
224 ('pose_vis_sca', "Visual Scale",
225 "Copy Scale from Active to Selected", pVisScaExec),
226 ('pose_drw', "Bone Shape",
227 "Copy Bone Shape from Active to Selected", pDrwExec),
228 ('pose_lok', "Protected Transform",
229 "Copy Protected Transforms from Active to Selected", pLokExec),
230 ('pose_con', "Bone Constraints",
231 "Copy Object Constraints from Active to Selected", pConExec),
232 ('pose_iks', "IK Limits",
233 "Copy IK Limits from Active to Selected", pIKsExec),
234 ('bbone_settings', "BBone Settings",
235 "Copy BBone Settings from Active to Selected", pBBonesExec),
238 @classmethod
239 def pose_poll_func(cls, context):
240 return(context.mode == 'POSE')
243 def pose_invoke_func(self, context, event):
244 wm = context.window_manager
245 wm.invoke_props_dialog(self)
246 return {'RUNNING_MODAL'}
249 class CopySelectedPoseConstraints(Operator):
250 """Copy Chosen constraints from active to selected"""
251 bl_idname = "pose.copy_selected_constraints"
252 bl_label = "Copy Selected Constraints"
254 selection: BoolVectorProperty(
255 size=32,
256 options={'SKIP_SAVE'}
259 poll = pose_poll_func
260 invoke = pose_invoke_func
262 def draw(self, context):
263 layout = self.layout
264 for idx, const in enumerate(context.active_pose_bone.constraints):
265 layout.prop(self, "selection", index=idx, text=const.name,
266 toggle=True)
268 def execute(self, context):
269 active = context.active_pose_bone
270 selected = context.selected_pose_bones[:]
271 selected.remove(active)
272 for bone in selected:
273 for index, flag in enumerate(self.selection):
274 if flag:
275 bone.constraints.copy(active.constraints[index])
276 return {'FINISHED'}
279 pose_ops = [] # list of pose mode copy operators
280 genops(pose_copies, pose_ops, "pose.copy_", pose_poll_func, pLoopExec)
283 class VIEW3D_MT_posecopypopup(Menu):
284 bl_label = "Copy Attributes"
286 def draw(self, context):
287 layout = self.layout
288 layout.operator_context = 'INVOKE_REGION_WIN'
289 for op in pose_copies:
290 layout.operator("pose.copy_" + op[0])
291 layout.operator("pose.copy_selected_constraints")
292 layout.operator("pose.copy", text="copy pose")
295 def obLoopExec(self, context, funk):
296 """Loop over selected objects and execute funk on them"""
297 active = context.active_object
298 selected = context.selected_objects[:]
299 selected.remove(active)
300 for obj in selected:
301 msg = funk(obj, active, context)
302 if msg:
303 self.report({msg[0]}, INFO_MESSAGE + msg[1])
306 def world_to_basis(active, ob, context):
307 """put world coords of active as basis coords of ob"""
308 local = ob.parent.matrix_world.inverted() @ active.matrix_world
309 P = ob.matrix_basis @ ob.matrix_local.inverted()
310 mat = P @ local
311 return(mat)
314 # The following functions are used to copy attributes from
315 # active to selected object
317 def obLoc(ob, active, context):
318 ob.location = active.location
321 def obRot(ob, active, context):
322 rotcopy(ob, active.matrix_local.to_3x3())
325 def obSca(ob, active, context):
326 ob.scale = active.scale
329 def obVisLoc(ob, active, context):
330 if ob.parent:
331 mat = world_to_basis(active, ob, context)
332 ob.location = mat.to_translation()
333 else:
334 ob.location = active.matrix_world.to_translation()
335 return('INFO', "Object location copied")
338 def obVisRot(ob, active, context):
339 if ob.parent:
340 mat = world_to_basis(active, ob, context)
341 rotcopy(ob, mat.to_3x3())
342 else:
343 rotcopy(ob, active.matrix_world.to_3x3())
344 return('INFO', "Object rotation copied")
347 def obVisSca(ob, active, context):
348 if ob.parent:
349 mat = world_to_basis(active, ob, context)
350 ob.scale = mat.to_scale()
351 else:
352 ob.scale = active.matrix_world.to_scale()
353 return('INFO', "Object scale copied")
356 def obDrw(ob, active, context):
357 ob.display_type = active.display_type
358 ob.show_axis = active.show_axis
359 ob.show_bounds = active.show_bounds
360 ob.display_bounds_type = active.display_bounds_type
361 ob.show_name = active.show_name
362 ob.show_texture_space = active.show_texture_space
363 ob.show_transparent = active.show_transparent
364 ob.show_wire = active.show_wire
365 ob.show_in_front = active.show_in_front
366 ob.empty_display_type = active.empty_display_type
367 ob.empty_display_size = active.empty_display_size
370 def obOfs(ob, active, context):
371 ob.time_offset = active.time_offset
372 return('INFO', "Time offset copied")
375 def obDup(ob, active, context):
376 generic_copy(active, ob, "dupli")
377 return('INFO', "Duplication method copied")
380 def obCol(ob, active, context):
381 ob.color = active.color
384 def obLok(ob, active, context):
385 for index, state in enumerate(active.lock_location):
386 ob.lock_location[index] = state
387 for index, state in enumerate(active.lock_rotation):
388 ob.lock_rotation[index] = state
389 ob.lock_rotations_4d = active.lock_rotations_4d
390 ob.lock_rotation_w = active.lock_rotation_w
391 for index, state in enumerate(active.lock_scale):
392 ob.lock_scale[index] = state
393 return('INFO', "Transform locks copied")
396 def obCon(ob, active, context):
397 # for consistency with 2.49, delete old constraints first
398 for removeconst in ob.constraints:
399 ob.constraints.remove(removeconst)
400 for old_constraint in active.constraints.values():
401 new_constraint = ob.constraints.new(old_constraint.type)
402 generic_copy(old_constraint, new_constraint)
403 return('INFO', "Constraints copied")
406 def obTex(ob, active, context):
407 if 'texspace_location' in dir(ob.data) and 'texspace_location' in dir(
408 active.data):
409 ob.data.texspace_location[:] = active.data.texspace_location[:]
410 if 'texspace_size' in dir(ob.data) and 'texspace_size' in dir(active.data):
411 ob.data.texspace_size[:] = active.data.texspace_size[:]
412 return('INFO', "Texture space copied")
415 def obIdx(ob, active, context):
416 ob.pass_index = active.pass_index
417 return('INFO', "Pass index copied")
420 def obMod(ob, active, context):
421 for modifier in ob.modifiers:
422 # remove existing before adding new:
423 ob.modifiers.remove(modifier)
424 for old_modifier in active.modifiers.values():
425 new_modifier = ob.modifiers.new(name=old_modifier.name,
426 type=old_modifier.type)
427 generic_copy(old_modifier, new_modifier)
428 return('INFO', "Modifiers copied")
431 def obGrp(ob, active, context):
432 for grp in bpy.data.collections:
433 if active.name in grp.objects and ob.name not in grp.objects:
434 grp.objects.link(ob)
435 return('INFO', "Groups copied")
438 def obWei(ob, active, context):
439 me_source = active.data
440 me_target = ob.data
441 # sanity check: do source and target have the same amount of verts?
442 if len(me_source.vertices) != len(me_target.vertices):
443 return('ERROR', "objects have different vertex counts, doing nothing")
444 vgroups_IndexName = {}
445 for i in range(0, len(active.vertex_groups)):
446 groups = active.vertex_groups[i]
447 vgroups_IndexName[groups.index] = groups.name
448 data = {} # vert_indices, [(vgroup_index, weights)]
449 for v in me_source.vertices:
450 vg = v.groups
451 vi = v.index
452 if len(vg) > 0:
453 vgroup_collect = []
454 for i in range(0, len(vg)):
455 vgroup_collect.append((vg[i].group, vg[i].weight))
456 data[vi] = vgroup_collect
457 # write data to target
458 if ob != active:
459 # add missing vertex groups
460 for vgroup_name in vgroups_IndexName.values():
461 # check if group already exists...
462 already_present = 0
463 for i in range(0, len(ob.vertex_groups)):
464 if ob.vertex_groups[i].name == vgroup_name:
465 already_present = 1
466 # ... if not, then add
467 if already_present == 0:
468 ob.vertex_groups.new(name=vgroup_name)
469 # write weights
470 for v in me_target.vertices:
471 for vi_source, vgroupIndex_weight in data.items():
472 if v.index == vi_source:
474 for i in range(0, len(vgroupIndex_weight)):
475 groupName = vgroups_IndexName[vgroupIndex_weight[i][0]]
476 groups = ob.vertex_groups
477 for vgs in range(0, len(groups)):
478 if groups[vgs].name == groupName:
479 groups[vgs].add((v.index,),
480 vgroupIndex_weight[i][1], "REPLACE")
481 return('INFO', "Weights copied")
484 object_copies = (
485 # ('obj_loc', "Location",
486 # "Copy Location from Active to Selected", obLoc),
487 # ('obj_rot', "Rotation",
488 # "Copy Rotation from Active to Selected", obRot),
489 # ('obj_sca', "Scale",
490 # "Copy Scale from Active to Selected", obSca),
491 ('obj_vis_loc', "Location",
492 "Copy Location from Active to Selected", obVisLoc),
493 ('obj_vis_rot', "Rotation",
494 "Copy Rotation from Active to Selected", obVisRot),
495 ('obj_vis_sca', "Scale",
496 "Copy Scale from Active to Selected", obVisSca),
497 ('obj_drw', "Draw Options",
498 "Copy Draw Options from Active to Selected", obDrw),
499 ('obj_ofs', "Time Offset",
500 "Copy Time Offset from Active to Selected", obOfs),
501 ('obj_dup', "Dupli",
502 "Copy Dupli from Active to Selected", obDup),
503 ('obj_col', "Object Color",
504 "Copy Object Color from Active to Selected", obCol),
505 # ('obj_dmp', "Damping",
506 # "Copy Damping from Active to Selected"),
507 # ('obj_all', "All Physical Attributes",
508 # "Copy Physical Attributes from Active to Selected"),
509 # ('obj_prp', "Properties",
510 # "Copy Properties from Active to Selected"),
511 # ('obj_log', "Logic Bricks",
512 # "Copy Logic Bricks from Active to Selected"),
513 ('obj_lok', "Protected Transform",
514 "Copy Protected Transforms from Active to Selected", obLok),
515 ('obj_con', "Object Constraints",
516 "Copy Object Constraints from Active to Selected", obCon),
517 # ('obj_nla', "NLA Strips",
518 # "Copy NLA Strips from Active to Selected"),
519 # ('obj_tex', "Texture Space",
520 # "Copy Texture Space from Active to Selected", obTex),
521 # ('obj_sub', "Subdivision Surface Settings",
522 # "Copy Subdivision Surface Settings from Active to Selected"),
523 # ('obj_smo', "AutoSmooth",
524 # "Copy AutoSmooth from Active to Selected"),
525 ('obj_idx', "Pass Index",
526 "Copy Pass Index from Active to Selected", obIdx),
527 ('obj_mod', "Modifiers",
528 "Copy Modifiers from Active to Selected", obMod),
529 ('obj_wei', "Vertex Weights",
530 "Copy vertex weights based on indices", obWei),
531 ('obj_grp', "Group Links",
532 "Copy selected into active object's groups", obGrp)
536 @classmethod
537 def object_poll_func(cls, context):
538 return (len(context.selected_objects) > 1)
541 def object_invoke_func(self, context, event):
542 wm = context.window_manager
543 wm.invoke_props_dialog(self)
544 return {'RUNNING_MODAL'}
547 class CopySelectedObjectConstraints(Operator):
548 """Copy Chosen constraints from active to selected"""
549 bl_idname = "object.copy_selected_constraints"
550 bl_label = "Copy Selected Constraints"
552 selection: BoolVectorProperty(
553 size=32,
554 options={'SKIP_SAVE'}
557 poll = object_poll_func
558 invoke = object_invoke_func
560 def draw(self, context):
561 layout = self.layout
562 for idx, const in enumerate(context.active_object.constraints):
563 layout.prop(self, "selection", index=idx, text=const.name,
564 toggle=True)
566 def execute(self, context):
567 active = context.active_object
568 selected = context.selected_objects[:]
569 selected.remove(active)
570 for obj in selected:
571 for index, flag in enumerate(self.selection):
572 if flag:
573 old_constraint = active.constraints[index]
574 new_constraint = obj.constraints.new(
575 active.constraints[index].type
577 generic_copy(old_constraint, new_constraint)
578 return{'FINISHED'}
581 class CopySelectedObjectModifiers(Operator):
582 """Copy Chosen modifiers from active to selected"""
583 bl_idname = "object.copy_selected_modifiers"
584 bl_label = "Copy Selected Modifiers"
586 selection: BoolVectorProperty(
587 size=32,
588 options={'SKIP_SAVE'}
591 poll = object_poll_func
592 invoke = object_invoke_func
594 def draw(self, context):
595 layout = self.layout
596 for idx, const in enumerate(context.active_object.modifiers):
597 layout.prop(self, 'selection', index=idx, text=const.name,
598 toggle=True)
600 def execute(self, context):
601 active = context.active_object
602 selected = context.selected_objects[:]
603 selected.remove(active)
604 for obj in selected:
605 for index, flag in enumerate(self.selection):
606 if flag:
607 old_modifier = active.modifiers[index]
608 new_modifier = obj.modifiers.new(
609 type=active.modifiers[index].type,
610 name=active.modifiers[index].name
612 generic_copy(old_modifier, new_modifier)
613 return{'FINISHED'}
616 object_ops = []
617 genops(object_copies, object_ops, "object.copy_", object_poll_func, obLoopExec)
620 class VIEW3D_MT_copypopup(Menu):
621 bl_label = "Copy Attributes"
623 def draw(self, context):
624 layout = self.layout
626 layout.operator_context = 'INVOKE_REGION_WIN'
627 layout.operator("view3d.copybuffer", icon="COPY_ID")
629 if (len(context.selected_objects) <= 1):
630 layout.separator()
631 layout.label(text="Please select at least two objects", icon="INFO")
632 layout.separator()
634 for entry, op in enumerate(object_copies):
635 if entry and entry % 4 == 0:
636 layout.separator()
637 layout.operator("object.copy_" + op[0])
638 layout.operator("object.copy_selected_constraints")
639 layout.operator("object.copy_selected_modifiers")
642 # Begin Mesh copy settings:
644 class MESH_MT_CopyFaceSettings(Menu):
645 bl_label = "Copy Face Settings"
647 @classmethod
648 def poll(cls, context):
649 return context.mode == 'EDIT_MESH'
651 def draw(self, context):
652 mesh = context.object.data
653 uv = len(mesh.uv_layers) > 1
654 vc = len(mesh.vertex_colors) > 1
656 layout = self.layout
658 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
659 text="Copy Material")
660 op['layer'] = ''
661 op['mode'] = 'MAT'
663 if mesh.uv_layers.active:
664 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
665 text="Copy Active UV Coords")
666 op['layer'] = ''
667 op['mode'] = 'UV'
669 if mesh.vertex_colors.active:
670 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
671 text="Copy Active Vertex Colors")
672 op['layer'] = ''
673 op['mode'] = 'VCOL'
675 if uv or vc:
676 layout.separator()
677 if uv:
678 layout.menu("MESH_MT_CopyImagesFromLayer")
679 layout.menu("MESH_MT_CopyUVCoordsFromLayer")
680 if vc:
681 layout.menu("MESH_MT_CopyVertexColorsFromLayer")
684 # Data (UV map, Image and Vertex color) menus calling MESH_OT_CopyFaceSettings
685 # Explicitly defined as using the generator code was broken in case of Menus
686 # causing issues with access and registration
689 class MESH_MT_CopyUVCoordsFromLayer(Menu):
690 bl_label = "Copy Other UV Coord Layers"
692 @classmethod
693 def poll(cls, context):
694 obj = context.active_object
695 return obj and obj.mode == "EDIT_MESH" and len(
696 obj.data.uv_layers) > 1
698 def draw(self, context):
699 mesh = context.active_object.data
700 _buildmenu(self, mesh, 'UV', "GROUP_UVS")
703 class MESH_MT_CopyVertexColorsFromLayer(Menu):
704 bl_label = "Copy Other Vertex Colors 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.vertex_colors) > 1
712 def draw(self, context):
713 mesh = context.active_object.data
714 _buildmenu(self, mesh, 'VCOL', "GROUP_VCOL")
717 def _buildmenu(self, mesh, mode, icon):
718 layout = self.layout
719 if mode == 'VCOL':
720 layers = mesh.vertex_colors
721 else:
722 layers = mesh.uv_layers
723 for layer in layers:
724 if not layer.active:
725 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
726 text=layer.name, icon=icon)
727 op['layer'] = layer.name
728 op['mode'] = mode
731 class MESH_OT_CopyFaceSettings(Operator):
732 """Copy settings from active face to all selected faces"""
733 bl_idname = 'mesh.copy_face_settings'
734 bl_label = "Copy Face Settings"
735 bl_options = {'REGISTER', 'UNDO'}
737 mode: StringProperty(
738 name="Mode",
739 options={"HIDDEN"},
741 layer: StringProperty(
742 name="Layer",
743 options={"HIDDEN"},
746 @classmethod
747 def poll(cls, context):
748 return context.mode == 'EDIT_MESH'
750 def execute(self, context):
751 mode = getattr(self, 'mode', '')
752 if mode not in {'MAT', 'VCOL', 'UV'}:
753 self.report({'ERROR'}, "No mode specified or invalid mode")
754 return self._end(context, {'CANCELLED'})
755 layername = getattr(self, 'layer', '')
756 mesh = context.object.data
758 # Switching out of edit mode updates the selected state of faces and
759 # makes the data from the uv texture and vertex color layers available.
760 bpy.ops.object.editmode_toggle()
762 polys = mesh.polygons
763 if mode == 'MAT':
764 to_data = from_data = polys
765 else:
766 if mode == 'VCOL':
767 layers = mesh.vertex_colors
768 act_layer = mesh.vertex_colors.active
769 elif mode == 'UV':
770 layers = mesh.uv_layers
771 act_layer = mesh.uv_layers.active
772 if not layers or (layername and layername not in layers):
773 self.report({'ERROR'}, "Invalid UV or color layer. Operation Cancelled")
774 return self._end(context, {'CANCELLED'})
775 from_data = layers[layername or act_layer.name].data
776 to_data = act_layer.data
777 from_index = polys.active
779 for f in polys:
780 if f.select:
781 if to_data != from_data:
782 # Copying from another layer.
783 # from_face is to_face's counterpart from other layer.
784 from_index = f.index
785 elif f.index == from_index:
786 # Otherwise skip copying a face to itself.
787 continue
788 if mode == 'MAT':
789 f.material_index = polys[from_index].material_index
790 continue
791 if len(f.loop_indices) != len(polys[from_index].loop_indices):
792 self.report({'WARNING'}, "Different number of vertices.")
793 for i in range(len(f.loop_indices)):
794 to_vertex = f.loop_indices[i]
795 from_vertex = polys[from_index].loop_indices[i]
796 if mode == 'VCOL':
797 to_data[to_vertex].color = from_data[from_vertex].color
798 elif mode == 'UV':
799 to_data[to_vertex].uv = from_data[from_vertex].uv
801 return self._end(context, {'FINISHED'})
803 def _end(self, context, retval):
804 if context.mode != 'EDIT_MESH':
805 # Clean up by returning to edit mode like it was before.
806 bpy.ops.object.editmode_toggle()
807 return(retval)
810 classes = (
811 CopySelectedPoseConstraints,
812 VIEW3D_MT_posecopypopup,
813 CopySelectedObjectConstraints,
814 CopySelectedObjectModifiers,
815 VIEW3D_MT_copypopup,
816 MESH_MT_CopyFaceSettings,
817 MESH_MT_CopyUVCoordsFromLayer,
818 MESH_MT_CopyVertexColorsFromLayer,
819 MESH_OT_CopyFaceSettings,
820 *pose_ops,
821 *object_ops,
824 def register():
825 from bpy.utils import register_class
826 for cls in classes:
827 register_class(cls)
829 # mostly to get the keymap working
830 kc = bpy.context.window_manager.keyconfigs.addon
831 if kc:
832 km = kc.keymaps.new(name="Object Mode")
833 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
834 kmi.properties.name = 'VIEW3D_MT_copypopup'
836 km = kc.keymaps.new(name="Pose")
837 kmi = km.keymap_items.get("pose.copy")
838 if kmi is not None:
839 kmi.idname = 'wm.call_menu'
840 else:
841 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
842 kmi.properties.name = 'VIEW3D_MT_posecopypopup'
844 km = kc.keymaps.new(name="Mesh")
845 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS')
846 kmi.ctrl = True
847 kmi.properties.name = 'MESH_MT_CopyFaceSettings'
850 def unregister():
851 # mostly to remove the keymap
852 kc = bpy.context.window_manager.keyconfigs.addon
853 if kc:
854 kms = kc.keymaps.get('Pose')
855 if kms is not None:
856 for item in kms.keymap_items:
857 if item.name == 'Call Menu' and item.idname == 'wm.call_menu' and \
858 item.properties.name == 'VIEW3D_MT_posecopypopup':
859 item.idname = 'pose.copy'
860 break
862 km = kc.keymaps.get('Mesh')
863 if km is not None:
864 for kmi in km.keymap_items:
865 if kmi.idname == 'wm.call_menu':
866 if kmi.properties.name == 'MESH_MT_CopyFaceSettings':
867 km.keymap_items.remove(kmi)
869 km = kc.keymaps.get('Object Mode')
870 if km is not None:
871 for kmi in km.keymap_items:
872 if kmi.idname == 'wm.call_menu':
873 if kmi.properties.name == 'VIEW3D_MT_copypopup':
874 km.keymap_items.remove(kmi)
876 from bpy.utils import unregister_class
877 for cls in classes:
878 unregister_class(cls)
881 if __name__ == "__main__":
882 register()