1 # SPDX-License-Identifier: GPL-2.0-or-later
4 "name": "Copy Attributes Menu",
5 "author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman, Demeter Dzadik",
8 "location": "View3D > Ctrl-C",
9 "description": "Copy Attributes Menu",
10 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/copy_attributes.html",
11 "category": "Interface",
15 from mathutils
import Matrix
16 from bpy
.types
import (
20 from bpy
.props
import (
25 # First part of the operator Info message
26 INFO_MESSAGE
= "Copy Attributes: "
29 def build_exec(loopfunc
, func
):
30 """Generator function that returns exec functions for operators """
32 def exec_func(self
, context
):
33 loopfunc(self
, context
, func
)
38 def build_invoke(loopfunc
, func
):
39 """Generator function that returns invoke functions for operators"""
41 def invoke_func(self
, context
, event
):
42 loopfunc(self
, context
, func
)
47 def build_op(idname
, label
, description
, fpoll
, fexec
, finvoke
):
48 """Generator function that returns the basic operator"""
50 class myopic(Operator
):
53 bl_description
= description
60 def genops(copylist
, oplist
, prefix
, poll_func
, loopfunc
):
61 """Generate ops from the copy list and its associated functions"""
63 exec_func
= build_exec(loopfunc
, op
[3])
64 invoke_func
= build_invoke(loopfunc
, op
[3])
65 opclass
= build_op(prefix
+ op
[0], "Copy " + op
[1], op
[2],
66 poll_func
, exec_func
, invoke_func
)
67 oplist
.append(opclass
)
70 def generic_copy(source
, target
, string
=""):
71 """Copy attributes from source to target that have string in them"""
72 for attr
in dir(source
):
73 if attr
.find(string
) > -1:
75 setattr(target
, attr
, getattr(source
, attr
))
81 def getmat(bone
, active
, context
, ignoreparent
):
82 """Helper function for visual transform copy,
83 gets the active transform in bone space
85 obj_bone
= bone
.id_data
86 obj_active
= active
.id_data
87 data_bone
= obj_bone
.data
.bones
[bone
.name
]
88 # all matrices are in armature space unless commented otherwise
89 active_to_selected
= obj_bone
.matrix_world
.inverted() @ obj_active
.matrix_world
90 active_matrix
= active_to_selected
@ active
.matrix
91 otherloc
= active_matrix
# final 4x4 mat of target, location.
92 bonemat_local
= data_bone
.matrix_local
.copy() # self rest matrix
94 parentposemat
= obj_bone
.pose
.bones
[data_bone
.parent
.name
].matrix
.copy()
95 parentbonemat
= data_bone
.parent
.matrix_local
.copy()
97 parentposemat
= parentbonemat
= Matrix()
98 if parentbonemat
== parentposemat
or ignoreparent
:
99 newmat
= bonemat_local
.inverted() @ otherloc
101 bonemat
= parentbonemat
.inverted() @ bonemat_local
103 newmat
= bonemat
.inverted() @ parentposemat
.inverted() @ otherloc
107 def rotcopy(item
, mat
):
108 """Copy rotation to item from matrix mat depending on item.rotation_mode"""
109 if item
.rotation_mode
== 'QUATERNION':
110 item
.rotation_quaternion
= mat
.to_3x3().to_quaternion()
111 elif item
.rotation_mode
== 'AXIS_ANGLE':
112 rot
= mat
.to_3x3().to_quaternion().to_axis_angle() # returns (Vector((x, y, z)), w)
113 axis_angle
= rot
[1], rot
[0][0], rot
[0][1], rot
[0][2] # convert to w, x, y, z
114 item
.rotation_axis_angle
= axis_angle
116 item
.rotation_euler
= mat
.to_3x3().to_euler(item
.rotation_mode
)
119 def pLoopExec(self
, context
, funk
):
120 """Loop over selected bones and execute funk on them"""
121 active
= context
.active_pose_bone
122 selected
= context
.selected_pose_bones
123 selected
.remove(active
)
124 for bone
in selected
:
125 funk(bone
, active
, context
)
128 # The following functions are used to copy attributes from active to bone
130 def pLocLocExec(bone
, active
, context
):
131 bone
.location
= active
.location
134 def pLocRotExec(bone
, active
, context
):
135 rotcopy(bone
, active
.matrix_basis
.to_3x3())
138 def pLocScaExec(bone
, active
, context
):
139 bone
.scale
= active
.scale
142 def pVisLocExec(bone
, active
, context
):
143 bone
.location
= getmat(bone
, active
, context
, False).to_translation()
146 def pVisRotExec(bone
, active
, context
):
147 obj_bone
= bone
.id_data
148 rotcopy(bone
, getmat(bone
, active
,
149 context
, not obj_bone
.data
.bones
[bone
.name
].use_inherit_rotation
))
152 def pVisScaExec(bone
, active
, context
):
153 obj_bone
= bone
.id_data
154 bone
.scale
= getmat(bone
, active
, context
,
155 not obj_bone
.data
.bones
[bone
.name
].use_inherit_scale
)\
159 def pDrwExec(bone
, active
, context
):
160 bone
.custom_shape
= active
.custom_shape
161 bone
.use_custom_shape_bone_size
= active
.use_custom_shape_bone_size
162 bone
.custom_shape_translation
= active
.custom_shape_translation
163 bone
.custom_shape_rotation_euler
= active
.custom_shape_rotation_euler
164 bone
.custom_shape_scale_xyz
= active
.custom_shape_scale_xyz
165 bone
.bone
.show_wire
= active
.bone
.show_wire
168 def pLokExec(bone
, active
, context
):
169 for index
, state
in enumerate(active
.lock_location
):
170 bone
.lock_location
[index
] = state
171 for index
, state
in enumerate(active
.lock_rotation
):
172 bone
.lock_rotation
[index
] = state
173 bone
.lock_rotations_4d
= active
.lock_rotations_4d
174 bone
.lock_rotation_w
= active
.lock_rotation_w
175 for index
, state
in enumerate(active
.lock_scale
):
176 bone
.lock_scale
[index
] = state
179 def pConExec(bone
, active
, context
):
180 for old_constraint
in active
.constraints
.values():
181 new_constraint
= bone
.constraints
.new(old_constraint
.type)
182 generic_copy(old_constraint
, new_constraint
)
185 def pIKsExec(bone
, active
, context
):
186 generic_copy(active
, bone
, "ik_")
189 def pBBonesExec(bone
, active
, context
):
190 object = active
.id_data
192 object.data
.bones
[active
.name
],
193 object.data
.bones
[bone
.name
],
198 ('pose_loc_loc', "Local Location",
199 "Copy Location from Active to Selected", pLocLocExec
),
200 ('pose_loc_rot', "Local Rotation",
201 "Copy Rotation from Active to Selected", pLocRotExec
),
202 ('pose_loc_sca', "Local Scale",
203 "Copy Scale from Active to Selected", pLocScaExec
),
204 ('pose_vis_loc', "Visual Location",
205 "Copy Location from Active to Selected", pVisLocExec
),
206 ('pose_vis_rot', "Visual Rotation",
207 "Copy Rotation from Active to Selected", pVisRotExec
),
208 ('pose_vis_sca', "Visual Scale",
209 "Copy Scale from Active to Selected", pVisScaExec
),
210 ('pose_drw', "Bone Shape",
211 "Copy Bone Shape from Active to Selected", pDrwExec
),
212 ('pose_lok', "Protected Transform",
213 "Copy Protected Transforms from Active to Selected", pLokExec
),
214 ('pose_con', "Bone Constraints",
215 "Copy Object Constraints from Active to Selected", pConExec
),
216 ('pose_iks', "IK Limits",
217 "Copy IK Limits from Active to Selected", pIKsExec
),
218 ('bbone_settings', "BBone Settings",
219 "Copy BBone Settings from Active to Selected", pBBonesExec
),
223 def pose_poll_func(cls
, context
):
224 return(context
.mode
== 'POSE')
227 def pose_invoke_func(self
, context
, event
):
228 wm
= context
.window_manager
229 wm
.invoke_props_dialog(self
)
230 return {'RUNNING_MODAL'}
233 CustomPropSelectionBoolsProperty
= BoolVectorProperty(
235 options
={'SKIP_SAVE'}
239 """Base class for copying properties from active to selected based on a selection."""
241 selection
: CustomPropSelectionBoolsProperty
243 def draw_bools(self
, button_names
):
244 """Draws the boolean toggle list with a list of strings for the button texts."""
246 for idx
, name
in enumerate(button_names
):
247 layout
.prop(self
, "selection", index
=idx
, text
=name
,
250 def copy_custom_property(source
, destination
, prop_name
):
251 """Copy a custom property called prop_name, from source to destination.
252 source and destination must be a Blender data type that can hold custom properties.
253 For a list of such data types, see:
254 https://docs.blender.org/manual/en/latest/files/data_blocks.html#files-data-blocks-custom-properties
257 # Create the property.
258 destination
[prop_name
] = source
[prop_name
]
259 # Copy the settings of the property.
261 dst_prop_manager
= destination
.id_properties_ui(prop_name
)
263 # Python values like lists or dictionaries don't have any settings to copy.
264 # They just consist of a value and nothing else.
267 src_prop_manager
= source
.id_properties_ui(prop_name
)
268 assert src_prop_manager
, f
'Property "{prop_name}" not found in {source}'
270 dst_prop_manager
.update_from(src_prop_manager
)
272 # Copy the Library Overridable flag, which is stored elsewhere.
273 prop_rna_path
= f
'["{prop_name}"]'
274 is_lib_overridable
= source
.is_property_overridable_library(prop_rna_path
)
275 destination
.property_overridable_library_set(prop_rna_path
, is_lib_overridable
)
277 class CopyCustomProperties(CopySelection
):
278 """Base class for copying a selection of custom properties."""
280 def copy_selected_custom_props(self
, active
, selected
):
281 keys
= list(active
.keys())
282 for item
in selected
:
285 for index
, is_selected
in enumerate(self
.selection
):
287 copy_custom_property(active
, item
, keys
[index
])
289 class CopySelectedBoneCustomProperties(CopyCustomProperties
, Operator
):
290 """Copy Chosen custom properties from active to selected"""
291 bl_idname
= "pose.copy_selected_custom_props"
292 bl_label
= "Copy Selected Custom Properties"
293 bl_options
= {'REGISTER', 'UNDO'}
295 poll
= pose_poll_func
296 invoke
= pose_invoke_func
298 def draw(self
, context
):
299 self
.draw_bools(context
.active_pose_bone
.keys())
301 def execute(self
, context
):
302 self
.copy_selected_custom_props(context
.active_pose_bone
, context
.selected_pose_bones
)
306 class CopySelectedPoseConstraints(Operator
):
307 """Copy Chosen constraints from active to selected"""
308 bl_idname
= "pose.copy_selected_constraints"
309 bl_label
= "Copy Selected Constraints"
311 selection
: BoolVectorProperty(
313 options
={'SKIP_SAVE'}
316 poll
= pose_poll_func
317 invoke
= pose_invoke_func
319 def draw(self
, context
):
321 for idx
, const
in enumerate(context
.active_pose_bone
.constraints
):
322 layout
.prop(self
, "selection", index
=idx
, text
=const
.name
,
325 def execute(self
, context
):
326 active
= context
.active_pose_bone
327 selected
= context
.selected_pose_bones
[:]
328 selected
.remove(active
)
329 for bone
in selected
:
330 for index
, flag
in enumerate(self
.selection
):
332 bone
.constraints
.copy(active
.constraints
[index
])
336 pose_ops
= [] # list of pose mode copy operators
337 genops(pose_copies
, pose_ops
, "pose.copy_", pose_poll_func
, pLoopExec
)
340 class VIEW3D_MT_posecopypopup(Menu
):
341 bl_label
= "Copy Attributes"
343 def draw(self
, context
):
345 layout
.operator_context
= 'INVOKE_REGION_WIN'
346 for op
in pose_copies
:
347 layout
.operator("pose.copy_" + op
[0])
348 layout
.operator("pose.copy_selected_constraints")
349 layout
.operator("pose.copy_selected_custom_props")
350 layout
.operator("pose.copy", text
="copy pose")
353 def obLoopExec(self
, context
, funk
):
354 """Loop over selected objects and execute funk on them"""
355 active
= context
.active_object
356 selected
= context
.selected_objects
[:]
357 selected
.remove(active
)
359 msg
= funk(obj
, active
, context
)
361 self
.report({msg
[0]}, INFO_MESSAGE
+ msg
[1])
364 def world_to_basis(active
, ob
, context
):
365 """put world coords of active as basis coords of ob"""
366 local
= ob
.parent
.matrix_world
.inverted() @ active
.matrix_world
367 P
= ob
.matrix_basis
@ ob
.matrix_local
.inverted()
372 # The following functions are used to copy attributes from
373 # active to selected object
375 def obLoc(ob
, active
, context
):
376 ob
.location
= active
.location
379 def obRot(ob
, active
, context
):
380 rotcopy(ob
, active
.matrix_local
.to_3x3())
383 def obSca(ob
, active
, context
):
384 ob
.scale
= active
.scale
387 def obVisLoc(ob
, active
, context
):
389 mat
= world_to_basis(active
, ob
, context
)
390 ob
.location
= mat
.to_translation()
392 ob
.location
= active
.matrix_world
.to_translation()
393 return('INFO', "Object location copied")
396 def obVisRot(ob
, active
, context
):
398 mat
= world_to_basis(active
, ob
, context
)
399 rotcopy(ob
, mat
.to_3x3())
401 rotcopy(ob
, active
.matrix_world
.to_3x3())
402 return('INFO', "Object rotation copied")
405 def obVisSca(ob
, active
, context
):
407 mat
= world_to_basis(active
, ob
, context
)
408 ob
.scale
= mat
.to_scale()
410 ob
.scale
= active
.matrix_world
.to_scale()
411 return('INFO', "Object scale copied")
414 def obDrw(ob
, active
, context
):
415 ob
.display_type
= active
.display_type
416 ob
.show_axis
= active
.show_axis
417 ob
.show_bounds
= active
.show_bounds
418 ob
.display_bounds_type
= active
.display_bounds_type
419 ob
.show_name
= active
.show_name
420 ob
.show_texture_space
= active
.show_texture_space
421 ob
.show_transparent
= active
.show_transparent
422 ob
.show_wire
= active
.show_wire
423 ob
.show_in_front
= active
.show_in_front
424 ob
.empty_display_type
= active
.empty_display_type
425 ob
.empty_display_size
= active
.empty_display_size
428 def obOfs(ob
, active
, context
):
429 ob
.time_offset
= active
.time_offset
430 return('INFO', "Time offset copied")
433 def obDup(ob
, active
, context
):
434 generic_copy(active
, ob
, "dupli")
435 return('INFO', "Duplication method copied")
438 def obCol(ob
, active
, context
):
439 ob
.color
= active
.color
442 def obLok(ob
, active
, context
):
443 for index
, state
in enumerate(active
.lock_location
):
444 ob
.lock_location
[index
] = state
445 for index
, state
in enumerate(active
.lock_rotation
):
446 ob
.lock_rotation
[index
] = state
447 ob
.lock_rotations_4d
= active
.lock_rotations_4d
448 ob
.lock_rotation_w
= active
.lock_rotation_w
449 for index
, state
in enumerate(active
.lock_scale
):
450 ob
.lock_scale
[index
] = state
451 return('INFO', "Transform locks copied")
454 def obCon(ob
, active
, context
):
455 # for consistency with 2.49, delete old constraints first
456 for removeconst
in ob
.constraints
:
457 ob
.constraints
.remove(removeconst
)
458 for old_constraint
in active
.constraints
.values():
459 new_constraint
= ob
.constraints
.new(old_constraint
.type)
460 generic_copy(old_constraint
, new_constraint
)
461 return('INFO', "Constraints copied")
464 def obTex(ob
, active
, context
):
465 if 'texspace_location' in dir(ob
.data
) and 'texspace_location' in dir(
467 ob
.data
.texspace_location
[:] = active
.data
.texspace_location
[:]
468 if 'texspace_size' in dir(ob
.data
) and 'texspace_size' in dir(active
.data
):
469 ob
.data
.texspace_size
[:] = active
.data
.texspace_size
[:]
470 return('INFO', "Texture space copied")
473 def obIdx(ob
, active
, context
):
474 ob
.pass_index
= active
.pass_index
475 return('INFO', "Pass index copied")
478 def obMod(ob
, active
, context
):
479 for modifier
in ob
.modifiers
:
480 # remove existing before adding new:
481 ob
.modifiers
.remove(modifier
)
482 for old_modifier
in active
.modifiers
.values():
483 new_modifier
= ob
.modifiers
.new(name
=old_modifier
.name
,
484 type=old_modifier
.type)
485 generic_copy(old_modifier
, new_modifier
)
486 return('INFO', "Modifiers copied")
489 def obGrp(ob
, active
, context
):
490 for grp
in bpy
.data
.collections
:
491 if active
.name
in grp
.objects
and ob
.name
not in grp
.objects
:
493 return('INFO', "Groups copied")
496 def obWei(ob
, active
, context
):
497 # sanity check: are source and target both mesh objects?
498 if ob
.type != 'MESH' or active
.type != 'MESH':
499 return('ERROR', "objects have to be of mesh type, doing nothing")
500 me_source
= active
.data
502 # sanity check: do source and target have the same amount of verts?
503 if len(me_source
.vertices
) != len(me_target
.vertices
):
504 return('ERROR', "objects have different vertex counts, doing nothing")
505 vgroups_IndexName
= {}
506 for i
in range(0, len(active
.vertex_groups
)):
507 groups
= active
.vertex_groups
[i
]
508 vgroups_IndexName
[groups
.index
] = groups
.name
509 data
= {} # vert_indices, [(vgroup_index, weights)]
510 for v
in me_source
.vertices
:
515 for i
in range(0, len(vg
)):
516 vgroup_collect
.append((vg
[i
].group
, vg
[i
].weight
))
517 data
[vi
] = vgroup_collect
518 # write data to target
520 # add missing vertex groups
521 for vgroup_name
in vgroups_IndexName
.values():
522 # check if group already exists...
524 for i
in range(0, len(ob
.vertex_groups
)):
525 if ob
.vertex_groups
[i
].name
== vgroup_name
:
527 # ... if not, then add
528 if already_present
== 0:
529 ob
.vertex_groups
.new(name
=vgroup_name
)
531 for v
in me_target
.vertices
:
532 for vi_source
, vgroupIndex_weight
in data
.items():
533 if v
.index
== vi_source
:
535 for i
in range(0, len(vgroupIndex_weight
)):
536 groupName
= vgroups_IndexName
[vgroupIndex_weight
[i
][0]]
537 groups
= ob
.vertex_groups
538 for vgs
in range(0, len(groups
)):
539 if groups
[vgs
].name
== groupName
:
540 groups
[vgs
].add((v
.index
,),
541 vgroupIndex_weight
[i
][1], "REPLACE")
542 return('INFO', "Weights copied")
546 # ('obj_loc', "Location",
547 # "Copy Location from Active to Selected", obLoc),
548 # ('obj_rot', "Rotation",
549 # "Copy Rotation from Active to Selected", obRot),
550 # ('obj_sca', "Scale",
551 # "Copy Scale from Active to Selected", obSca),
552 ('obj_vis_loc', "Location",
553 "Copy Location from Active to Selected", obVisLoc
),
554 ('obj_vis_rot', "Rotation",
555 "Copy Rotation from Active to Selected", obVisRot
),
556 ('obj_vis_sca', "Scale",
557 "Copy Scale from Active to Selected", obVisSca
),
558 ('obj_drw', "Draw Options",
559 "Copy Draw Options from Active to Selected", obDrw
),
560 ('obj_ofs', "Time Offset",
561 "Copy Time Offset from Active to Selected", obOfs
),
563 "Copy Dupli from Active to Selected", obDup
),
564 ('obj_col', "Object Color",
565 "Copy Object Color from Active to Selected", obCol
),
566 # ('obj_dmp', "Damping",
567 # "Copy Damping from Active to Selected"),
568 # ('obj_all', "All Physical Attributes",
569 # "Copy Physical Attributes from Active to Selected"),
570 # ('obj_prp', "Properties",
571 # "Copy Properties from Active to Selected"),
572 # ('obj_log', "Logic Bricks",
573 # "Copy Logic Bricks from Active to Selected"),
574 ('obj_lok', "Protected Transform",
575 "Copy Protected Transforms from Active to Selected", obLok
),
576 ('obj_con', "Object Constraints",
577 "Copy Object Constraints from Active to Selected", obCon
),
578 # ('obj_nla', "NLA Strips",
579 # "Copy NLA Strips from Active to Selected"),
580 # ('obj_tex', "Texture Space",
581 # "Copy Texture Space from Active to Selected", obTex),
582 # ('obj_sub', "Subdivision Surface Settings",
583 # "Copy Subdivision Surface Settings from Active to Selected"),
584 # ('obj_smo', "AutoSmooth",
585 # "Copy AutoSmooth from Active to Selected"),
586 ('obj_idx', "Pass Index",
587 "Copy Pass Index from Active to Selected", obIdx
),
588 ('obj_mod', "Modifiers",
589 "Copy Modifiers from Active to Selected", obMod
),
590 ('obj_wei', "Vertex Weights",
591 "Copy vertex weights based on indices", obWei
),
592 ('obj_grp', "Group Links",
593 "Copy selected into active object's groups", obGrp
)
598 def object_poll_func(cls
, context
):
599 return (len(context
.selected_objects
) > 1)
602 def object_invoke_func(self
, context
, event
):
603 wm
= context
.window_manager
604 wm
.invoke_props_dialog(self
)
605 return {'RUNNING_MODAL'}
608 class CopySelectedObjectConstraints(Operator
):
609 """Copy Chosen constraints from active to selected"""
610 bl_idname
= "object.copy_selected_constraints"
611 bl_label
= "Copy Selected Constraints"
613 selection
: BoolVectorProperty(
615 options
={'SKIP_SAVE'}
618 poll
= object_poll_func
619 invoke
= object_invoke_func
621 def draw(self
, context
):
623 for idx
, const
in enumerate(context
.active_object
.constraints
):
624 layout
.prop(self
, "selection", index
=idx
, text
=const
.name
,
627 def execute(self
, context
):
628 active
= context
.active_object
629 selected
= context
.selected_objects
[:]
630 selected
.remove(active
)
632 for index
, flag
in enumerate(self
.selection
):
634 obj
.constraints
.copy(active
.constraints
[index
])
638 class CopySelectedObjectModifiers(Operator
):
639 """Copy Chosen modifiers from active to selected"""
640 bl_idname
= "object.copy_selected_modifiers"
641 bl_label
= "Copy Selected Modifiers"
643 selection
: BoolVectorProperty(
645 options
={'SKIP_SAVE'}
648 poll
= object_poll_func
649 invoke
= object_invoke_func
651 def draw(self
, context
):
653 for idx
, const
in enumerate(context
.active_object
.modifiers
):
654 layout
.prop(self
, 'selection', index
=idx
, text
=const
.name
,
657 def execute(self
, context
):
658 active
= context
.active_object
659 selected
= context
.selected_objects
[:]
660 selected
.remove(active
)
662 for index
, flag
in enumerate(self
.selection
):
664 old_modifier
= active
.modifiers
[index
]
665 new_modifier
= obj
.modifiers
.new(
666 type=active
.modifiers
[index
].type,
667 name
=active
.modifiers
[index
].name
669 generic_copy(old_modifier
, new_modifier
)
673 class CopySelectedObjectCustomProperties(CopyCustomProperties
, Operator
):
674 """Copy Chosen custom properties from active to selected objects"""
675 bl_idname
= "object.copy_selected_custom_props"
676 bl_label
= "Copy Selected Custom Properties"
677 bl_options
= {'REGISTER', 'UNDO'}
679 poll
= object_poll_func
680 invoke
= object_invoke_func
682 def draw(self
, context
):
683 self
.draw_bools(context
.object.keys())
685 def execute(self
, context
):
686 self
.copy_selected_custom_props(context
.object, context
.selected_objects
)
690 genops(object_copies
, object_ops
, "object.copy_", object_poll_func
, obLoopExec
)
693 class VIEW3D_MT_copypopup(Menu
):
694 bl_label
= "Copy Attributes"
696 def draw(self
, context
):
699 layout
.operator_context
= 'INVOKE_REGION_WIN'
700 layout
.operator("view3d.copybuffer", icon
="COPY_ID")
702 if (len(context
.selected_objects
) <= 1):
704 layout
.label(text
="Please select at least two objects", icon
="INFO")
707 for entry
, op
in enumerate(object_copies
):
708 if entry
and entry
% 4 == 0:
710 layout
.operator("object.copy_" + op
[0])
711 layout
.operator("object.copy_selected_constraints")
712 layout
.operator("object.copy_selected_modifiers")
713 layout
.operator("object.copy_selected_custom_props")
716 # Begin Mesh copy settings:
718 class MESH_MT_CopyFaceSettings(Menu
):
719 bl_label
= "Copy Face Settings"
722 def poll(cls
, context
):
723 return context
.mode
== 'EDIT_MESH'
725 def draw(self
, context
):
726 mesh
= context
.object.data
727 uv
= len(mesh
.uv_layers
) > 1
728 vc
= len(mesh
.vertex_colors
) > 1
732 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
733 text
="Copy Material")
737 if mesh
.uv_layers
.active
:
738 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
739 text
="Copy Active UV Coords")
743 if mesh
.vertex_colors
.active
:
744 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
745 text
="Copy Active Vertex Colors")
752 layout
.menu("MESH_MT_CopyImagesFromLayer")
753 layout
.menu("MESH_MT_CopyUVCoordsFromLayer")
755 layout
.menu("MESH_MT_CopyVertexColorsFromLayer")
758 # Data (UV map, Image and Vertex color) menus calling MESH_OT_CopyFaceSettings
759 # Explicitly defined as using the generator code was broken in case of Menus
760 # causing issues with access and registration
763 class MESH_MT_CopyUVCoordsFromLayer(Menu
):
764 bl_label
= "Copy Other UV Coord Layers"
767 def poll(cls
, context
):
768 obj
= context
.active_object
769 return obj
and obj
.mode
== "EDIT_MESH" and len(
770 obj
.data
.uv_layers
) > 1
772 def draw(self
, context
):
773 mesh
= context
.active_object
.data
774 _buildmenu(self
, mesh
, 'UV', "GROUP_UVS")
777 class MESH_MT_CopyVertexColorsFromLayer(Menu
):
778 bl_label
= "Copy Other Vertex Colors Layers"
781 def poll(cls
, context
):
782 obj
= context
.active_object
783 return obj
and obj
.mode
== "EDIT_MESH" and len(
784 obj
.data
.vertex_colors
) > 1
786 def draw(self
, context
):
787 mesh
= context
.active_object
.data
788 _buildmenu(self
, mesh
, 'VCOL', "GROUP_VCOL")
791 def _buildmenu(self
, mesh
, mode
, icon
):
794 layers
= mesh
.vertex_colors
796 layers
= mesh
.uv_layers
799 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
800 text
=layer
.name
, icon
=icon
)
801 op
['layer'] = layer
.name
805 class MESH_OT_CopyFaceSettings(Operator
):
806 """Copy settings from active face to all selected faces"""
807 bl_idname
= 'mesh.copy_face_settings'
808 bl_label
= "Copy Face Settings"
809 bl_options
= {'REGISTER', 'UNDO'}
811 mode
: StringProperty(
815 layer
: StringProperty(
821 def poll(cls
, context
):
822 return context
.mode
== 'EDIT_MESH'
824 def execute(self
, context
):
825 mode
= getattr(self
, 'mode', '')
826 if mode
not in {'MAT', 'VCOL', 'UV'}:
827 self
.report({'ERROR'}, "No mode specified or invalid mode")
828 return self
._end
(context
, {'CANCELLED'})
829 layername
= getattr(self
, 'layer', '')
830 mesh
= context
.object.data
832 # Switching out of edit mode updates the selected state of faces and
833 # makes the data from the uv texture and vertex color layers available.
834 bpy
.ops
.object.editmode_toggle()
836 polys
= mesh
.polygons
838 to_data
= from_data
= polys
841 layers
= mesh
.vertex_colors
842 act_layer
= mesh
.vertex_colors
.active
844 layers
= mesh
.uv_layers
845 act_layer
= mesh
.uv_layers
.active
846 if not layers
or (layername
and layername
not in layers
):
847 self
.report({'ERROR'}, "Invalid UV or color layer. Operation Cancelled")
848 return self
._end
(context
, {'CANCELLED'})
849 from_data
= layers
[layername
or act_layer
.name
].data
850 to_data
= act_layer
.data
851 from_index
= polys
.active
855 if to_data
!= from_data
:
856 # Copying from another layer.
857 # from_face is to_face's counterpart from other layer.
859 elif f
.index
== from_index
:
860 # Otherwise skip copying a face to itself.
863 f
.material_index
= polys
[from_index
].material_index
865 if len(f
.loop_indices
) != len(polys
[from_index
].loop_indices
):
866 self
.report({'WARNING'}, "Different number of vertices.")
867 for i
in range(len(f
.loop_indices
)):
868 to_vertex
= f
.loop_indices
[i
]
869 from_vertex
= polys
[from_index
].loop_indices
[i
]
871 to_data
[to_vertex
].color
= from_data
[from_vertex
].color
873 to_data
[to_vertex
].uv
= from_data
[from_vertex
].uv
875 return self
._end
(context
, {'FINISHED'})
877 def _end(self
, context
, retval
):
878 if context
.mode
!= 'EDIT_MESH':
879 # Clean up by returning to edit mode like it was before.
880 bpy
.ops
.object.editmode_toggle()
885 CopySelectedPoseConstraints
,
886 CopySelectedBoneCustomProperties
,
887 VIEW3D_MT_posecopypopup
,
888 CopySelectedObjectConstraints
,
889 CopySelectedObjectModifiers
,
890 CopySelectedObjectCustomProperties
,
892 MESH_MT_CopyFaceSettings
,
893 MESH_MT_CopyUVCoordsFromLayer
,
894 MESH_MT_CopyVertexColorsFromLayer
,
895 MESH_OT_CopyFaceSettings
,
901 from bpy
.utils
import register_class
905 # mostly to get the keymap working
906 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
908 km
= kc
.keymaps
.new(name
="Object Mode")
909 kmi
= km
.keymap_items
.new('wm.call_menu', 'C', 'PRESS', ctrl
=True)
910 kmi
.properties
.name
= 'VIEW3D_MT_copypopup'
912 km
= kc
.keymaps
.new(name
="Pose")
913 kmi
= km
.keymap_items
.get("pose.copy")
915 kmi
.idname
= 'wm.call_menu'
917 kmi
= km
.keymap_items
.new('wm.call_menu', 'C', 'PRESS', ctrl
=True)
918 kmi
.properties
.name
= 'VIEW3D_MT_posecopypopup'
920 km
= kc
.keymaps
.new(name
="Mesh")
921 kmi
= km
.keymap_items
.new('wm.call_menu', 'C', 'PRESS')
923 kmi
.properties
.name
= 'MESH_MT_CopyFaceSettings'
927 # mostly to remove the keymap
928 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
930 kms
= kc
.keymaps
.get('Pose')
932 for item
in kms
.keymap_items
:
933 if item
.name
== 'Call Menu' and item
.idname
== 'wm.call_menu' and \
934 item
.properties
.name
== 'VIEW3D_MT_posecopypopup':
935 item
.idname
= 'pose.copy'
938 km
= kc
.keymaps
.get('Mesh')
940 for kmi
in km
.keymap_items
:
941 if kmi
.idname
== 'wm.call_menu':
942 if kmi
.properties
.name
== 'MESH_MT_CopyFaceSettings':
943 km
.keymap_items
.remove(kmi
)
945 km
= kc
.keymaps
.get('Object Mode')
947 for kmi
in km
.keymap_items
:
948 if kmi
.idname
== 'wm.call_menu':
949 if kmi
.properties
.name
== 'VIEW3D_MT_copypopup':
950 km
.keymap_items
.remove(kmi
)
952 from bpy
.utils
import unregister_class
954 unregister_class(cls
)
957 if __name__
== "__main__":