Fix T52833: OBJ triangulate doesn't match viewport
[blender-addons.git] / space_view3d_copy_attributes.py
blobd171d2c27eb8e26d6eda074c7b017862d119ac9b
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, 7),
25 "blender": (2, 63, 0),
26 "location": "View3D > Ctrl-C",
27 "description": "Copy Attributes Menu from Blender 2.4",
28 "wiki_url": "http://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
37 def build_exec(loopfunc, func):
38 """Generator function that returns exec functions for operators """
40 def exec_func(self, context):
41 loopfunc(self, context, func)
42 return {'FINISHED'}
43 return exec_func
46 def build_invoke(loopfunc, func):
47 """Generator function that returns invoke functions for operators"""
49 def invoke_func(self, context, event):
50 loopfunc(self, context, func)
51 return {'FINISHED'}
52 return invoke_func
55 def build_op(idname, label, description, fpoll, fexec, finvoke):
56 """Generator function that returns the basic operator"""
58 class myopic(bpy.types.Operator):
59 bl_idname = idname
60 bl_label = label
61 bl_description = description
62 execute = fexec
63 poll = fpoll
64 invoke = finvoke
65 return myopic
68 def genops(copylist, oplist, prefix, poll_func, loopfunc):
69 """Generate ops from the copy list and its associated functions """
70 for op in copylist:
71 exec_func = build_exec(loopfunc, op[3])
72 invoke_func = build_invoke(loopfunc, op[3])
73 opclass = build_op(prefix + op[0], "Copy " + op[1], op[2],
74 poll_func, exec_func, invoke_func)
75 oplist.append(opclass)
78 def generic_copy(source, target, string=""):
79 """ copy attributes from source to target that have string in them """
80 for attr in dir(source):
81 if attr.find(string) > -1:
82 try:
83 setattr(target, attr, getattr(source, attr))
84 except:
85 pass
86 return
89 def getmat(bone, active, context, ignoreparent):
90 """Helper function for visual transform copy,
91 gets the active transform in bone space
92 """
93 obj_act = context.active_object
94 data_bone = obj_act.data.bones[bone.name]
95 #all matrices are in armature space unless commented otherwise
96 otherloc = active.matrix # final 4x4 mat of target, location.
97 bonemat_local = data_bone.matrix_local.copy() # self rest matrix
98 if data_bone.parent:
99 parentposemat = obj_act.pose.bones[data_bone.parent.name].matrix.copy()
100 parentbonemat = data_bone.parent.matrix_local.copy()
101 else:
102 parentposemat = parentbonemat = Matrix()
103 if parentbonemat == parentposemat or ignoreparent:
104 newmat = bonemat_local.inverted() * otherloc
105 else:
106 bonemat = parentbonemat.inverted() * bonemat_local
108 newmat = bonemat.inverted() * parentposemat.inverted() * otherloc
109 return newmat
112 def rotcopy(item, mat):
113 """copy rotation to item from matrix mat depending on item.rotation_mode"""
114 if item.rotation_mode == 'QUATERNION':
115 item.rotation_quaternion = mat.to_3x3().to_quaternion()
116 elif item.rotation_mode == 'AXIS_ANGLE':
117 rot = mat.to_3x3().to_quaternion().to_axis_angle() # returns (Vector((x, y, z)), w)
118 axis_angle = rot[1], rot[0][0], rot[0][1], rot[0][2] # convert to w, x, y, z
119 item.rotation_axis_angle = axis_angle
120 else:
121 item.rotation_euler = mat.to_3x3().to_euler(item.rotation_mode)
124 def pLoopExec(self, context, funk):
125 """Loop over selected bones and execute funk on them"""
126 active = context.active_pose_bone
127 selected = context.selected_pose_bones
128 selected.remove(active)
129 for bone in selected:
130 funk(bone, active, context)
132 #The following functions are used o copy attributes frome active to bone
135 def pLocLocExec(bone, active, context):
136 bone.location = active.location
139 def pLocRotExec(bone, active, context):
140 rotcopy(bone, active.matrix_basis.to_3x3())
143 def pLocScaExec(bone, active, context):
144 bone.scale = active.scale
147 def pVisLocExec(bone, active, context):
148 bone.location = getmat(bone, active, context, False).to_translation()
151 def pVisRotExec(bone, active, context):
152 rotcopy(bone, getmat(bone, active,
153 context, not context.active_object.data.bones[bone.name].use_inherit_rotation))
156 def pVisScaExec(bone, active, context):
157 bone.scale = getmat(bone, active, context,
158 not context.active_object.data.bones[bone.name].use_inherit_scale)\
159 .to_scale()
162 def pDrwExec(bone, active, context):
163 bone.custom_shape = active.custom_shape
164 bone.use_custom_shape_bone_size = active.use_custom_shape_bone_size
165 bone.custom_shape_scale = active.custom_shape_scale
166 bone.bone.show_wire = active.bone.show_wire
169 def pLokExec(bone, active, context):
170 for index, state in enumerate(active.lock_location):
171 bone.lock_location[index] = state
172 for index, state in enumerate(active.lock_rotation):
173 bone.lock_rotation[index] = state
174 bone.lock_rotations_4d = active.lock_rotations_4d
175 bone.lock_rotation_w = active.lock_rotation_w
176 for index, state in enumerate(active.lock_scale):
177 bone.lock_scale[index] = state
180 def pConExec(bone, active, context):
181 for old_constraint in active.constraints.values():
182 new_constraint = bone.constraints.new(old_constraint.type)
183 generic_copy(old_constraint, new_constraint)
186 def pIKsExec(bone, active, context):
187 generic_copy(active, bone, "ik_")
190 def pBBonesExec(bone, active, context):
191 object = active.id_data
192 generic_copy(
193 object.data.bones[active.name],
194 object.data.bones[bone.name],
195 "bbone_")
197 pose_copies = (('pose_loc_loc', "Local Location",
198 "Copy Location from Active to Selected", pLocLocExec),
199 ('pose_loc_rot', "Local Rotation",
200 "Copy Rotation from Active to Selected", pLocRotExec),
201 ('pose_loc_sca', "Local Scale",
202 "Copy Scale from Active to Selected", pLocScaExec),
203 ('pose_vis_loc', "Visual Location",
204 "Copy Location from Active to Selected", pVisLocExec),
205 ('pose_vis_rot', "Visual Rotation",
206 "Copy Rotation from Active to Selected", pVisRotExec),
207 ('pose_vis_sca', "Visual Scale",
208 "Copy Scale from Active to Selected", pVisScaExec),
209 ('pose_drw', "Bone Shape",
210 "Copy Bone Shape from Active to Selected", pDrwExec),
211 ('pose_lok', "Protected Transform",
212 "Copy Protected Tranforms from Active to Selected", pLokExec),
213 ('pose_con', "Bone Constraints",
214 "Copy Object Constraints from Active to Selected", pConExec),
215 ('pose_iks', "IK Limits",
216 "Copy IK Limits from Active to Selected", pIKsExec),
217 ('bbone_settings', "BBone Settings",
218 "Copy BBone Settings from Active to Selected", pBBonesExec),)
221 @classmethod
222 def pose_poll_func(cls, context):
223 return(context.mode == 'POSE')
226 def pose_invoke_func(self, context, event):
227 wm = context.window_manager
228 wm.invoke_props_dialog(self)
229 return {'RUNNING_MODAL'}
232 class CopySelectedPoseConstraints(bpy.types.Operator):
233 """Copy Chosen constraints from active to selected"""
234 bl_idname = "pose.copy_selected_constraints"
235 bl_label = "Copy Selected Constraints"
236 selection = bpy.props.BoolVectorProperty(size=32, options={'SKIP_SAVE'})
238 poll = pose_poll_func
239 invoke = pose_invoke_func
241 def draw(self, context):
242 layout = self.layout
243 for idx, const in enumerate(context.active_pose_bone.constraints):
244 layout.prop(self, "selection", index=idx, text=const.name,
245 toggle=True)
247 def execute(self, context):
248 active = context.active_pose_bone
249 selected = context.selected_pose_bones[:]
250 selected.remove(active)
251 for bone in selected:
252 for index, flag in enumerate(self.selection):
253 if flag:
254 old_constraint = active.constraints[index]
255 new_constraint = bone.constraints.new(\
256 active.constraints[index].type)
257 generic_copy(old_constraint, new_constraint)
258 return {'FINISHED'}
260 pose_ops = [] # list of pose mode copy operators
262 genops(pose_copies, pose_ops, "pose.copy_", pose_poll_func, pLoopExec)
265 class VIEW3D_MT_posecopypopup(bpy.types.Menu):
266 bl_label = "Copy Attributes"
268 def draw(self, context):
269 layout = self.layout
270 layout.operator_context = 'INVOKE_REGION_WIN'
271 layout.operator("view3d.copybuffer", icon="COPY_ID")
272 for op in pose_copies:
273 layout.operator("pose.copy_" + op[0])
274 layout.operator("pose.copy_selected_constraints")
275 layout.operator("pose.copy", text="copy pose")
278 def obLoopExec(self, context, funk):
279 """Loop over selected objects and execute funk on them"""
280 active = context.active_object
281 selected = context.selected_objects[:]
282 selected.remove(active)
283 for obj in selected:
284 msg = funk(obj, active, context)
285 if msg:
286 self.report({msg[0]}, msg[1])
289 def world_to_basis(active, ob, context):
290 """put world coords of active as basis coords of ob"""
291 local = ob.parent.matrix_world.inverted() * active.matrix_world
292 P = ob.matrix_basis * ob.matrix_local.inverted()
293 mat = P * local
294 return(mat)
296 #The following functions are used o copy attributes from
297 #active to selected object
300 def obLoc(ob, active, context):
301 ob.location = active.location
304 def obRot(ob, active, context):
305 rotcopy(ob, active.matrix_local.to_3x3())
308 def obSca(ob, active, context):
309 ob.scale = active.scale
312 def obVisLoc(ob, active, context):
313 if ob.parent:
314 mat = world_to_basis(active, ob, context)
315 ob.location = mat.to_translation()
316 else:
317 ob.location = active.matrix_world.to_translation()
320 def obVisRot(ob, active, context):
321 if ob.parent:
322 mat = world_to_basis(active, ob, context)
323 rotcopy(ob, mat.to_3x3())
324 else:
325 rotcopy(ob, active.matrix_world.to_3x3())
328 def obVisSca(ob, active, context):
329 if ob.parent:
330 mat = world_to_basis(active, ob, context)
331 ob.scale = mat.to_scale()
332 else:
333 ob.scale = active.matrix_world.to_scale()
336 def obDrw(ob, active, context):
337 ob.draw_type = active.draw_type
338 ob.show_axis = active.show_axis
339 ob.show_bounds = active.show_bounds
340 ob.draw_bounds_type = active.draw_bounds_type
341 ob.show_name = active.show_name
342 ob.show_texture_space = active.show_texture_space
343 ob.show_transparent = active.show_transparent
344 ob.show_wire = active.show_wire
345 ob.show_x_ray = active.show_x_ray
346 ob.empty_draw_type = active.empty_draw_type
347 ob.empty_draw_size = active.empty_draw_size
350 def obOfs(ob, active, context):
351 ob.time_offset = active.time_offset
352 return('INFO', "time offset copied")
355 def obDup(ob, active, context):
356 generic_copy(active, ob, "dupli")
357 return('INFO', "duplication method copied")
360 def obCol(ob, active, context):
361 ob.color = active.color
364 def obMas(ob, active, context):
365 ob.game.mass = active.game.mass
366 return('INFO', "mass copied")
369 def obLok(ob, active, context):
370 for index, state in enumerate(active.lock_location):
371 ob.lock_location[index] = state
372 for index, state in enumerate(active.lock_rotation):
373 ob.lock_rotation[index] = state
374 ob.lock_rotations_4d = active.lock_rotations_4d
375 ob.lock_rotation_w = active.lock_rotation_w
376 for index, state in enumerate(active.lock_scale):
377 ob.lock_scale[index] = state
378 return('INFO', "transform locks copied")
381 def obCon(ob, active, context):
382 #for consistency with 2.49, delete old constraints first
383 for removeconst in ob.constraints:
384 ob.constraints.remove(removeconst)
385 for old_constraint in active.constraints.values():
386 new_constraint = ob.constraints.new(old_constraint.type)
387 generic_copy(old_constraint, new_constraint)
388 return('INFO', "constraints copied")
391 def obTex(ob, active, context):
392 if 'texspace_location' in dir(ob.data) and 'texspace_location' in dir(
393 active.data):
394 ob.data.texspace_location[:] = active.data.texspace_location[:]
395 if 'texspace_size' in dir(ob.data) and 'texspace_size' in dir(active.data):
396 ob.data.texspace_size[:] = active.data.texspace_size[:]
397 return('INFO', "texture space copied")
400 def obIdx(ob, active, context):
401 ob.pass_index = active.pass_index
402 return('INFO', "pass index copied")
405 def obMod(ob, active, context):
406 for modifier in ob.modifiers:
407 #remove existing before adding new:
408 ob.modifiers.remove(modifier)
409 for old_modifier in active.modifiers.values():
410 new_modifier = ob.modifiers.new(name=old_modifier.name,
411 type=old_modifier.type)
412 generic_copy(old_modifier, new_modifier)
413 return('INFO', "modifiers copied")
416 def obGrp(ob, active, context):
417 for grp in bpy.data.groups:
418 if active.name in grp.objects and ob.name not in grp.objects:
419 grp.objects.link(ob)
420 return('INFO', "groups copied")
423 def obWei(ob, active, context):
424 me_source = active.data
425 me_target = ob.data
426 # sanity check: do source and target have the same amount of verts?
427 if len(me_source.vertices) != len(me_target.vertices):
428 return('ERROR', "objects have different vertex counts, doing nothing")
429 vgroups_IndexName = {}
430 for i in range(0, len(active.vertex_groups)):
431 groups = active.vertex_groups[i]
432 vgroups_IndexName[groups.index] = groups.name
433 data = {} # vert_indices, [(vgroup_index, weights)]
434 for v in me_source.vertices:
435 vg = v.groups
436 vi = v.index
437 if len(vg) > 0:
438 vgroup_collect = []
439 for i in range(0, len(vg)):
440 vgroup_collect.append((vg[i].group, vg[i].weight))
441 data[vi] = vgroup_collect
442 # write data to target
443 if ob != active:
444 # add missing vertex groups
445 for vgroup_name in vgroups_IndexName.values():
446 #check if group already exists...
447 already_present = 0
448 for i in range(0, len(ob.vertex_groups)):
449 if ob.vertex_groups[i].name == vgroup_name:
450 already_present = 1
451 # ... if not, then add
452 if already_present == 0:
453 ob.vertex_groups.new(name=vgroup_name)
454 # write weights
455 for v in me_target.vertices:
456 for vi_source, vgroupIndex_weight in data.items():
457 if v.index == vi_source:
459 for i in range(0, len(vgroupIndex_weight)):
460 groupName = vgroups_IndexName[vgroupIndex_weight[i][0]]
461 groups = ob.vertex_groups
462 for vgs in range(0, len(groups)):
463 if groups[vgs].name == groupName:
464 groups[vgs].add((v.index,),
465 vgroupIndex_weight[i][1], "REPLACE")
466 return('INFO', "weights copied")
468 object_copies = (
469 #('obj_loc', "Location",
470 #"Copy Location from Active to Selected", obLoc),
471 #('obj_rot', "Rotation",
472 #"Copy Rotation from Active to Selected", obRot),
473 #('obj_sca', "Scale",
474 #"Copy Scale from Active to Selected", obSca),
475 ('obj_vis_loc', "Location",
476 "Copy Location from Active to Selected", obVisLoc),
477 ('obj_vis_rot', "Rotation",
478 "Copy Rotation from Active to Selected", obVisRot),
479 ('obj_vis_sca', "Scale",
480 "Copy Scale from Active to Selected", obVisSca),
481 ('obj_drw', "Draw Options",
482 "Copy Draw Options from Active to Selected", obDrw),
483 ('obj_ofs', "Time Offset",
484 "Copy Time Offset from Active to Selected", obOfs),
485 ('obj_dup', "Dupli",
486 "Copy Dupli from Active to Selected", obDup),
487 ('obj_col', "Object Color",
488 "Copy Object Color from Active to Selected", obCol),
489 ('obj_mas', "Mass",
490 "Copy Mass from Active to Selected", obMas),
491 #('obj_dmp', "Damping",
492 #"Copy Damping from Active to Selected"),
493 #('obj_all', "All Physical Attributes",
494 #"Copy Physical Attributes from Active to Selected"),
495 #('obj_prp', "Properties",
496 #"Copy Properties from Active to Selected"),
497 #('obj_log', "Logic Bricks",
498 #"Copy Logic Bricks from Active to Selected"),
499 ('obj_lok', "Protected Transform",
500 "Copy Protected Tranforms from Active to Selected", obLok),
501 ('obj_con', "Object Constraints",
502 "Copy Object Constraints from Active to Selected", obCon),
503 #('obj_nla', "NLA Strips",
504 #"Copy NLA Strips from Active to Selected"),
505 #('obj_tex', "Texture Space",
506 #"Copy Texture Space from Active to Selected", obTex),
507 #('obj_sub', "Subsurf Settings",
508 #"Copy Subsurf Setings from Active to Selected"),
509 #('obj_smo', "AutoSmooth",
510 #"Copy AutoSmooth from Active to Selected"),
511 ('obj_idx', "Pass Index",
512 "Copy Pass Index from Active to Selected", obIdx),
513 ('obj_mod', "Modifiers",
514 "Copy Modifiers from Active to Selected", obMod),
515 ('obj_wei', "Vertex Weights",
516 "Copy vertex weights based on indices", obWei),
517 ('obj_grp', "Group Links",
518 "Copy selected into active object's groups", obGrp))
521 @classmethod
522 def object_poll_func(cls, context):
523 return(len(context.selected_objects) > 1)
526 def object_invoke_func(self, context, event):
527 wm = context.window_manager
528 wm.invoke_props_dialog(self)
529 return {'RUNNING_MODAL'}
532 class CopySelectedObjectConstraints(bpy.types.Operator):
533 """Copy Chosen constraints from active to selected"""
534 bl_idname = "object.copy_selected_constraints"
535 bl_label = "Copy Selected Constraints"
536 selection = bpy.props.BoolVectorProperty(size=32, options={'SKIP_SAVE'})
538 poll = object_poll_func
540 invoke = object_invoke_func
542 def draw(self, context):
543 layout = self.layout
544 for idx, const in enumerate(context.active_object.constraints):
545 layout.prop(self, "selection", index=idx, text=const.name,
546 toggle=True)
548 def execute(self, context):
549 active = context.active_object
550 selected = context.selected_objects[:]
551 selected.remove(active)
552 for obj in selected:
553 for index, flag in enumerate(self.selection):
554 if flag:
555 old_constraint = active.constraints[index]
556 new_constraint = obj.constraints.new(\
557 active.constraints[index].type)
558 generic_copy(old_constraint, new_constraint)
559 return{'FINISHED'}
562 class CopySelectedObjectModifiers(bpy.types.Operator):
563 """Copy Chosen modifiers from active to selected"""
564 bl_idname = "object.copy_selected_modifiers"
565 bl_label = "Copy Selected Modifiers"
566 selection = bpy.props.BoolVectorProperty(size=32, options={'SKIP_SAVE'})
568 poll = object_poll_func
570 invoke = object_invoke_func
572 def draw(self, context):
573 layout = self.layout
574 for idx, const in enumerate(context.active_object.modifiers):
575 layout.prop(self, 'selection', index=idx, text=const.name,
576 toggle=True)
578 def execute(self, context):
579 active = context.active_object
580 selected = context.selected_objects[:]
581 selected.remove(active)
582 for obj in selected:
583 for index, flag in enumerate(self.selection):
584 if flag:
585 old_modifier = active.modifiers[index]
586 new_modifier = obj.modifiers.new(\
587 type=active.modifiers[index].type,
588 name=active.modifiers[index].name)
589 generic_copy(old_modifier, new_modifier)
590 return{'FINISHED'}
592 object_ops = []
593 genops(object_copies, object_ops, "object.copy_", object_poll_func, obLoopExec)
596 class VIEW3D_MT_copypopup(bpy.types.Menu):
597 bl_label = "Copy Attributes"
599 def draw(self, context):
600 layout = self.layout
601 layout.operator_context = 'INVOKE_REGION_WIN'
602 layout.operator("view3d.copybuffer", icon="COPY_ID")
603 for op in object_copies:
604 layout.operator("object.copy_" + op[0])
605 layout.operator("object.copy_selected_constraints")
606 layout.operator("object.copy_selected_modifiers")
608 #Begin Mesh copy settings:
611 class MESH_MT_CopyFaceSettings(bpy.types.Menu):
612 bl_label = "Copy Face Settings"
614 @classmethod
615 def poll(cls, context):
616 return context.mode == 'EDIT_MESH'
618 def draw(self, context):
619 mesh = context.object.data
620 uv = len(mesh.uv_textures) > 1
621 vc = len(mesh.vertex_colors) > 1
622 layout = self.layout
623 layout.operator("view3d.copybuffer", icon="COPY_ID")
624 layout.operator("view3d.pastebuffer", icon="COPY_ID")
625 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
626 text="Copy Material")
627 op['layer'] = ''
628 op['mode'] = 'MAT'
629 if mesh.uv_textures.active:
630 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
631 text="Copy Image")
632 op['layer'] = ''
633 op['mode'] = 'IMAGE'
634 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
635 text="Copy UV Coords")
636 op['layer'] = ''
637 op['mode'] = 'UV'
638 if mesh.vertex_colors.active:
639 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
640 text="Copy Vertex Colors")
641 op['layer'] = ''
642 op['mode'] = 'VCOL'
643 if uv or vc:
644 layout.separator()
645 if uv:
646 layout.menu("MESH_MT_CopyImagesFromLayer")
647 layout.menu("MESH_MT_CopyUVCoordsFromLayer")
648 if vc:
649 layout.menu("MESH_MT_CopyVertexColorsFromLayer")
652 def _buildmenu(self, mesh, mode):
653 layout = self.layout
654 if mode == 'VCOL':
655 layers = mesh.vertex_colors
656 else:
657 layers = mesh.uv_textures
658 for layer in layers:
659 if not layer.active:
660 op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
661 text=layer.name)
662 op['layer'] = layer.name
663 op['mode'] = mode
666 @classmethod
667 def _poll_layer_uvs(cls, context):
668 return context.mode == "EDIT_MESH" and len(
669 context.object.data.uv_layers) > 1
672 @classmethod
673 def _poll_layer_vcols(cls, context):
674 return context.mode == "EDIT_MESH" and len(
675 context.object.data.vertex_colors) > 1
678 def _build_draw(mode):
679 return (lambda self, context: _buildmenu(self, context.object.data, mode))
681 _layer_menu_data = (("UV Coords", _build_draw("UV"), _poll_layer_uvs),
682 ("Images", _build_draw("IMAGE"), _poll_layer_uvs),
683 ("Vertex Colors", _build_draw("VCOL"), _poll_layer_vcols))
684 _layer_menus = []
685 for name, draw_func, poll_func in _layer_menu_data:
686 classname = "MESH_MT_Copy" + "".join(name.split()) + "FromLayer"
687 menuclass = type(classname, (bpy.types.Menu,),
688 dict(bl_label="Copy " + name + " from layer",
689 bl_idname=classname,
690 draw=draw_func,
691 poll=poll_func))
692 _layer_menus.append(menuclass)
695 class MESH_OT_CopyFaceSettings(bpy.types.Operator):
696 """Copy settings from active face to all selected faces"""
697 bl_idname = 'mesh.copy_face_settings'
698 bl_label = "Copy Face Settings"
699 bl_options = {'REGISTER', 'UNDO'}
701 mode = bpy.props.StringProperty(name="mode")
702 layer = bpy.props.StringProperty(name="layer")
704 @classmethod
705 def poll(cls, context):
706 return context.mode == 'EDIT_MESH'
708 def execute(self, context):
709 mode = getattr(self, 'mode', '')
710 if not mode in {'MAT', 'VCOL', 'IMAGE', 'UV'}:
711 self.report({'ERROR'}, "No mode specified or invalid mode.")
712 return self._end(context, {'CANCELLED'})
713 layername = getattr(self, 'layer', '')
714 mesh = context.object.data
716 # Switching out of edit mode updates the selected state of faces and
717 # makes the data from the uv texture and vertex color layers available.
718 bpy.ops.object.editmode_toggle()
720 polys = mesh.polygons
721 if mode == 'MAT':
722 to_data = from_data = polys
723 else:
724 if mode == 'VCOL':
725 layers = mesh.vertex_colors
726 act_layer = mesh.vertex_colors.active
727 elif mode == 'IMAGE':
728 layers = mesh.uv_textures
729 act_layer = mesh.uv_textures.active
730 elif mode == 'UV':
731 layers = mesh.uv_layers
732 act_layer = mesh.uv_layers.active
733 if not layers or (layername and not layername in layers):
734 self.report({'ERROR'}, "Invalid UV or color layer.")
735 return self._end(context, {'CANCELLED'})
736 from_data = layers[layername or act_layer.name].data
737 to_data = act_layer.data
738 from_index = polys.active
740 for f in polys:
741 if f.select:
742 if to_data != from_data:
743 # Copying from another layer.
744 # from_face is to_face's counterpart from other layer.
745 from_index = f.index
746 elif f.index == from_index:
747 # Otherwise skip copying a face to itself.
748 continue
749 if mode == 'MAT':
750 f.material_index = polys[from_index].material_index
751 continue
752 elif mode == 'IMAGE':
753 to_data[f.index].image = from_data[from_index].image
754 continue
755 if len(f.loop_indices) != len(polys[from_index].loop_indices):
756 self.report({'WARNING'}, "Different number of vertices.")
757 for i in range(len(f.loop_indices)):
758 to_vertex = f.loop_indices[i]
759 from_vertex = polys[from_index].loop_indices[i]
760 if mode == 'VCOL':
761 to_data[to_vertex].color = from_data[from_vertex].color
762 elif mode == 'UV':
763 to_data[to_vertex].uv = from_data[from_vertex].uv
765 return self._end(context, {'FINISHED'})
767 def _end(self, context, retval):
768 if context.mode != 'EDIT_MESH':
769 # Clean up by returning to edit mode like it was before.
770 bpy.ops.object.editmode_toggle()
771 return(retval)
774 def register():
775 bpy.utils.register_module(__name__)
777 ''' mostly to get the keymap working '''
778 kc = bpy.context.window_manager.keyconfigs.addon
779 if kc:
780 km = kc.keymaps.new(name="Object Mode")
781 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
782 kmi.properties.name = 'VIEW3D_MT_copypopup'
784 km = kc.keymaps.new(name="Pose")
785 kmi = km.keymap_items.get("pose.copy")
786 if kmi is not None:
787 kmi.idname = 'wm.call_menu'
788 else:
789 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
790 kmi.properties.name = 'VIEW3D_MT_posecopypopup'
792 km = kc.keymaps.new(name="Mesh")
793 kmi = km.keymap_items.new('wm.call_menu', 'C', 'PRESS')
794 kmi.ctrl = True
795 kmi.properties.name = 'MESH_MT_CopyFaceSettings'
798 def unregister():
799 # mostly to remove the keymap
800 kc = bpy.context.window_manager.keyconfigs.addon
801 if kc:
802 kms = kc.keymaps.get('Pose')
803 if kms is not None:
804 for item in kms.keymap_items:
805 if item.name == 'Call Menu' and item.idname == 'wm.call_menu' and \
806 item.properties.name == 'VIEW3D_MT_posecopypopup':
807 item.idname = 'pose.copy'
808 break
810 km = kc.keymaps.get('Mesh')
811 if km is not None:
812 for kmi in km.keymap_items:
813 if kmi.idname == 'wm.call_menu':
814 if kmi.properties.name == 'MESH_MT_CopyFaceSettings':
815 km.keymap_items.remove(kmi)
817 km = kc.keymaps.get('Object Mode')
818 if km is not None:
819 for kmi in km.keymap_items:
820 if kmi.idname == 'wm.call_menu':
821 if kmi.properties.name == 'VIEW3D_MT_copypopup':
822 km.keymap_items.remove(kmi)
824 bpy.utils.unregister_module(__name__)
826 if __name__ == "__main__":
827 register()