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 #####
22 "name": "Copy Attributes Menu",
23 "author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman",
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",
34 from mathutils
import Matrix
35 from bpy
.types
import (
39 from bpy
.props
import (
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
)
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
)
66 def build_op(idname
, label
, description
, fpoll
, fexec
, finvoke
):
67 """Generator function that returns the basic operator"""
69 class myopic(Operator
):
72 bl_description
= description
79 def genops(copylist
, oplist
, prefix
, poll_func
, loopfunc
):
80 """Generate ops from the copy list and its associated functions"""
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:
94 setattr(target
, attr
, getattr(source
, attr
))
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
113 parentposemat
= obj_bone
.pose
.bones
[data_bone
.parent
.name
].matrix
.copy()
114 parentbonemat
= data_bone
.parent
.matrix_local
.copy()
116 parentposemat
= parentbonemat
= Matrix()
117 if parentbonemat
== parentposemat
or ignoreparent
:
118 newmat
= bonemat_local
.inverted() @ otherloc
120 bonemat
= parentbonemat
.inverted() @ bonemat_local
122 newmat
= bonemat
.inverted() @ parentposemat
.inverted() @ otherloc
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
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
)\
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
209 object.data
.bones
[active
.name
],
210 object.data
.bones
[bone
.name
],
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
),
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(
258 options
={'SKIP_SAVE'}
261 poll
= pose_poll_func
262 invoke
= pose_invoke_func
264 def draw(self
, context
):
266 for idx
, const
in enumerate(context
.active_pose_bone
.constraints
):
267 layout
.prop(self
, "selection", index
=idx
, text
=const
.name
,
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
):
277 old_constraint
= active
.constraints
[index
]
278 new_constraint
= bone
.constraints
.new(
279 active
.constraints
[index
].type
281 generic_copy(old_constraint
, new_constraint
)
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
):
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
)
308 msg
= funk(obj
, active
, context
)
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()
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
):
338 mat
= world_to_basis(active
, ob
, context
)
339 ob
.location
= mat
.to_translation()
341 ob
.location
= active
.matrix_world
.to_translation()
342 return('INFO', "Object location copied")
345 def obVisRot(ob
, active
, context
):
347 mat
= world_to_basis(active
, ob
, context
)
348 rotcopy(ob
, mat
.to_3x3())
350 rotcopy(ob
, active
.matrix_world
.to_3x3())
351 return('INFO', "Object rotation copied")
354 def obVisSca(ob
, active
, context
):
356 mat
= world_to_basis(active
, ob
, context
)
357 ob
.scale
= mat
.to_scale()
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(
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
:
442 return('INFO', "Groups copied")
445 def obWei(ob
, active
, context
):
446 me_source
= active
.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
:
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
466 # add missing vertex groups
467 for vgroup_name
in vgroups_IndexName
.values():
468 # check if group already exists...
470 for i
in range(0, len(ob
.vertex_groups
)):
471 if ob
.vertex_groups
[i
].name
== vgroup_name
:
473 # ... if not, then add
474 if already_present
== 0:
475 ob
.vertex_groups
.new(name
=vgroup_name
)
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")
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
),
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
)
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(
561 options
={'SKIP_SAVE'}
564 poll
= object_poll_func
565 invoke
= object_invoke_func
567 def draw(self
, context
):
569 for idx
, const
in enumerate(context
.active_object
.constraints
):
570 layout
.prop(self
, "selection", index
=idx
, text
=const
.name
,
573 def execute(self
, context
):
574 active
= context
.active_object
575 selected
= context
.selected_objects
[:]
576 selected
.remove(active
)
578 for index
, flag
in enumerate(self
.selection
):
580 old_constraint
= active
.constraints
[index
]
581 new_constraint
= obj
.constraints
.new(
582 active
.constraints
[index
].type
584 generic_copy(old_constraint
, new_constraint
)
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(
595 options
={'SKIP_SAVE'}
598 poll
= object_poll_func
599 invoke
= object_invoke_func
601 def draw(self
, context
):
603 for idx
, const
in enumerate(context
.active_object
.modifiers
):
604 layout
.prop(self
, 'selection', index
=idx
, text
=const
.name
,
607 def execute(self
, context
):
608 active
= context
.active_object
609 selected
= context
.selected_objects
[:]
610 selected
.remove(active
)
612 for index
, flag
in enumerate(self
.selection
):
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
)
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
):
633 layout
.operator_context
= 'INVOKE_REGION_WIN'
634 layout
.operator("view3d.copybuffer", icon
="COPY_ID")
636 if (len(context
.selected_objects
) <= 1):
638 layout
.label(text
="Please select at least two objects", icon
="INFO")
641 for entry
, op
in enumerate(object_copies
):
642 if entry
and entry
% 4 == 0:
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"
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
664 layout
.operator("view3d.copybuffer", icon
="COPY_ID")
665 layout
.operator("view3d.pastebuffer", icon
="COPY_ID")
669 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
670 text
="Copy Material")
674 if mesh
.uv_textures
.active
:
675 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
676 text
="Copy Active UV Image")
679 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
680 text
="Copy Active UV Coords")
684 if mesh
.vertex_colors
.active
:
685 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
686 text
="Copy Active Vertex Colors")
693 layout
.menu("MESH_MT_CopyImagesFromLayer")
694 layout
.menu("MESH_MT_CopyUVCoordsFromLayer")
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"
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"
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"
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
):
748 layers
= mesh
.vertex_colors
750 layers
= mesh
.uv_textures
753 op
= layout
.operator(MESH_OT_CopyFaceSettings
.bl_idname
,
754 text
=layer
.name
, icon
=icon
)
755 op
['layer'] = layer
.name
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(
769 layer
: StringProperty(
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
792 to_data
= from_data
= polys
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
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
812 if to_data
!= from_data
:
813 # Copying from another layer.
814 # from_face is to_face's counterpart from other layer.
816 elif f
.index
== from_index
:
817 # Otherwise skip copying a face to itself.
820 f
.material_index
= polys
[from_index
].material_index
822 elif mode
== 'IMAGE':
823 to_data
[f
.index
].image
= from_data
[from_index
].image
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
]
831 to_data
[to_vertex
].color
= from_data
[from_vertex
].color
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()
845 CopySelectedPoseConstraints
,
846 VIEW3D_MT_posecopypopup
,
847 CopySelectedObjectConstraints
,
848 CopySelectedObjectModifiers
,
850 MESH_MT_CopyFaceSettings
,
851 MESH_MT_CopyImagesFromLayer
,
852 MESH_MT_CopyUVCoordsFromLayer
,
853 MESH_MT_CopyVertexColorsFromLayer
,
854 MESH_OT_CopyFaceSettings
,
860 from bpy
.utils
import register_class
864 # mostly to get the keymap working
865 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
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")
874 kmi
.idname
= 'wm.call_menu'
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')
882 kmi
.properties
.name
= 'MESH_MT_CopyFaceSettings'
886 # mostly to remove the keymap
887 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
889 kms
= kc
.keymaps
.get('Pose')
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'
897 km
= kc
.keymaps
.get('Mesh')
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')
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
913 unregister_class(cls
)
916 if __name__
== "__main__":