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 from bpy.types import Operator
23 from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty
26 class SelectPattern(Operator):
27 '''Select object matching a naming pattern'''
28 bl_idname = "object.select_pattern"
29 bl_label = "Select Pattern"
30 bl_options = {'REGISTER', 'UNDO'}
32 pattern = StringProperty(
34 description="Name filter using '*' and '?' wildcard chars",
38 case_sensitive = BoolProperty(
39 name="Case Sensitive",
40 description="Do a case sensitive compare",
43 extend = BoolProperty(
45 description="Extend the existing selection",
49 def execute(self, context):
53 if self.case_sensitive:
54 pattern_match = fnmatch.fnmatchcase
56 pattern_match = (lambda a, b:
57 fnmatch.fnmatchcase(a.upper(), b.upper()))
60 if obj and obj.mode == 'POSE':
61 items = obj.data.bones
63 bpy.ops.pose.select_all(action='DESELECT')
64 elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
65 items = obj.data.edit_bones
67 bpy.ops.armature.select_all(action='DESELECT')
70 items = context.visible_objects
72 bpy.ops.object.select_all(action='DESELECT')
74 # Can be pose bones or objects
76 if pattern_match(item.name, self.pattern):
79 # hrmf, perhaps there should be a utility function for this.
81 item.select_head = True
82 item.select_tail = True
84 item_parent = item.parent
85 if item_parent is not None:
86 item_parent.select_tail = True
90 def invoke(self, context, event):
91 wm = context.window_manager
92 return wm.invoke_props_popup(self, event)
94 def draw(self, context):
97 layout.prop(self, "pattern")
99 row.prop(self, "case_sensitive")
100 row.prop(self, "extend")
103 class SelectCamera(Operator):
104 '''Select object matching a naming pattern'''
105 bl_idname = "object.select_camera"
106 bl_label = "Select Camera"
107 bl_options = {'REGISTER', 'UNDO'}
110 def poll(cls, context):
111 return context.scene.camera is not None
113 def execute(self, context):
114 scene = context.scene
115 camera = scene.camera
116 if camera.name not in scene.objects:
117 self.report({'WARNING'}, "Active camera is not in this scene")
119 context.scene.objects.active = camera
124 class SelectHierarchy(Operator):
125 '''Select object relative to the active objects position''' \
126 '''in the hierarchy'''
127 bl_idname = "object.select_hierarchy"
128 bl_label = "Select Hierarchy"
129 bl_options = {'REGISTER', 'UNDO'}
131 direction = EnumProperty(
132 items=(('PARENT', "Parent", ""),
133 ('CHILD', "Child", ""),
136 description="Direction to select in the hierarchy",
139 extend = BoolProperty(
141 description="Extend the existing selection",
146 def poll(cls, context):
147 return context.object
149 def execute(self, context):
153 selected_objects = context.selected_objects
154 obj_act = context.object
156 if context.object not in selected_objects:
157 selected_objects.append(context.object)
159 if self.direction == 'PARENT':
160 for obj in selected_objects:
167 select_new.append(parent)
170 for obj in selected_objects:
171 select_new.extend(obj.children)
174 select_new.sort(key=lambda obj_iter: obj_iter.name)
175 act_new = select_new[0]
177 # dont edit any object settings above this
180 bpy.ops.object.select_all(action='DESELECT')
182 for obj in select_new:
185 context.scene.objects.active = act_new
191 class SubdivisionSet(Operator):
192 '''Sets a Subdivision Surface Level (1-5)'''
194 bl_idname = "object.subdivision_set"
195 bl_label = "Subdivision Set"
196 bl_options = {'REGISTER', 'UNDO'}
198 level = IntProperty(name="Level",
199 default=1, min=-100, max=100, soft_min=-6, soft_max=6)
201 relative = BoolProperty(
203 description=("Apply the subsurf level as an offset "
204 "relative to the current level"),
209 def poll(cls, context):
210 obs = context.selected_editable_objects
211 return (obs is not None)
213 def execute(self, context):
215 relative = self.relative
217 if relative and level == 0:
218 return {'CANCELLED'} # nothing to do
220 if not relative and level < 0:
221 self.level = level = 0
223 def set_object_subd(obj):
224 for mod in obj.modifiers:
225 if mod.type == 'MULTIRES':
227 if level <= mod.total_levels:
228 if obj.mode == 'SCULPT':
229 if mod.sculpt_levels != level:
230 mod.sculpt_levels = level
231 elif obj.mode == 'OBJECT':
232 if mod.levels != level:
236 if obj.mode == 'SCULPT':
237 if mod.sculpt_levels + level <= mod.total_levels:
238 mod.sculpt_levels += level
239 elif obj.mode == 'OBJECT':
240 if mod.levels + level <= mod.total_levels:
244 elif mod.type == 'SUBSURF':
248 if mod.levels != level:
255 mod = obj.modifiers.new("Subsurf", 'SUBSURF')
258 self.report({'WARNING'},
259 "Modifiers cannot be added to object: " + obj.name)
261 for obj in context.selected_editable_objects:
267 class ShapeTransfer(Operator):
268 '''Copy another selected objects active shape to this one by ''' \
269 '''applying the relative offsets'''
271 bl_idname = "object.shape_key_transfer"
272 bl_label = "Transfer Shape Key"
273 bl_options = {'REGISTER', 'UNDO'}
278 "Apply the relative positional offset",
282 "Calculate relative position (using faces).",
286 "Calculate relative position (using edges).",
289 name="Transformation Mode",
290 description="Relative shape positions to the new shape method",
293 use_clamp = BoolProperty(
295 description=("Clamp the transformation to the distance each "
296 "vertex moves in the original shape."),
300 def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
303 return [v.normal.copy() for v in verts]
306 return [v.co.copy() for v in verts]
308 def ob_add_shape(ob, name):
310 key = ob.shape_key_add(from_mix=False)
311 if len(me.shape_keys.key_blocks) == 1:
313 key = ob.shape_key_add(from_mix=False) # we need a rest
315 ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
316 ob.show_only_shape_key = True
318 from mathutils.geometry import barycentric_transform
319 from mathutils import Vector
321 if use_clamp and mode == 'OFFSET':
325 orig_key_name = ob_act.active_shape_key.name
327 orig_shape_coords = me_cos(ob_act.active_shape_key.data)
329 orig_normals = me_nos(me.vertices)
330 # the actual mverts location isnt as relyable as the base shape :S
331 # orig_coords = me_cos(me.vertices)
332 orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
334 for ob_other in objects:
335 me_other = ob_other.data
336 if len(me_other.vertices) != len(me.vertices):
337 self.report({'WARNING'},
339 "vertex count differs") % ob_other.name)
342 target_normals = me_nos(me_other.vertices)
343 if me_other.shape_keys:
344 target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
346 target_coords = me_cos(me_other.vertices)
348 ob_add_shape(ob_other, orig_key_name)
350 # editing the final coords, only list that stores wrapped coords
351 target_shape_coords = [v.co for v in
352 ob_other.active_shape_key.data]
354 median_coords = [[] for i in range(len(me.vertices))]
358 for i, vert_cos in enumerate(median_coords):
359 vert_cos.append(target_coords[i] +
360 (orig_shape_coords[i] - orig_coords[i]))
362 elif mode == 'RELATIVE_FACE':
363 for face in me.faces:
364 i1, i2, i3, i4 = face.vertices_raw
366 pt = barycentric_transform(orig_shape_coords[i1],
374 median_coords[i1].append(pt)
376 pt = barycentric_transform(orig_shape_coords[i2],
384 median_coords[i2].append(pt)
386 pt = barycentric_transform(orig_shape_coords[i3],
394 median_coords[i3].append(pt)
396 pt = barycentric_transform(orig_shape_coords[i4],
404 median_coords[i4].append(pt)
407 pt = barycentric_transform(orig_shape_coords[i1],
415 median_coords[i1].append(pt)
417 pt = barycentric_transform(orig_shape_coords[i2],
425 median_coords[i2].append(pt)
427 pt = barycentric_transform(orig_shape_coords[i3],
435 median_coords[i3].append(pt)
437 elif mode == 'RELATIVE_EDGE':
440 v1, v2 = orig_coords[i1], orig_coords[i2]
441 edge_length = (v1 - v2).length
442 n1loc = v1 + orig_normals[i1] * edge_length
443 n2loc = v2 + orig_normals[i2] * edge_length
445 # now get the target nloc's
446 v1_to, v2_to = target_coords[i1], target_coords[i2]
447 edlen_to = (v1_to - v2_to).length
448 n1loc_to = v1_to + target_normals[i1] * edlen_to
449 n2loc_to = v2_to + target_normals[i2] * edlen_to
451 pt = barycentric_transform(orig_shape_coords[i1],
453 v2_to, v1_to, n1loc_to)
454 median_coords[i1].append(pt)
456 pt = barycentric_transform(orig_shape_coords[i2],
458 v1_to, v2_to, n2loc_to)
459 median_coords[i2].append(pt)
461 # apply the offsets to the new shape
462 from functools import reduce
463 VectorAdd = Vector.__add__
465 for i, vert_cos in enumerate(median_coords):
467 co = reduce(VectorAdd, vert_cos) / len(vert_cos)
470 # clamp to the same movement as the original
471 # breaks copy between different scaled meshes.
472 len_from = (orig_shape_coords[i] -
473 orig_coords[i]).length
474 ofs = co - target_coords[i]
475 ofs.length = len_from
476 co = target_coords[i] + ofs
478 target_shape_coords[i][:] = co
483 def poll(cls, context):
484 obj = context.active_object
485 return (obj and obj.mode != 'EDIT')
487 def execute(self, context):
489 ob_act = C.active_object
490 objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
492 if 1: # swap from/to, means we cant copy to many at once.
493 if len(objects) != 1:
494 self.report({'ERROR'},
495 ("Expected one other selected "
496 "mesh object to copy from"))
499 ob_act, objects = objects[0], [ob_act]
501 if ob_act.type != 'MESH':
502 self.report({'ERROR'}, "Other object is not a mesh.")
505 if ob_act.active_shape_key is None:
506 self.report({'ERROR'}, "Other object has no shape key")
508 return self._main(ob_act, objects, self.mode, self.use_clamp)
511 class JoinUVs(Operator):
512 '''Copy UV Layout to objects with matching geometry'''
513 bl_idname = "object.join_uvs"
514 bl_label = "Join as UVs"
517 def poll(cls, context):
518 obj = context.active_object
519 return (obj and obj.type == 'MESH')
521 def _main(self, context):
523 obj = context.active_object
526 is_editmode = (obj.mode == 'EDIT')
528 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
530 if not mesh.uv_textures:
531 self.report({'WARNING'},
532 "Object: %s, Mesh: '%s' has no UVs"
533 % (obj.name, mesh.name))
535 len_faces = len(mesh.faces)
537 # seems to be the fastest way to create an array
538 uv_array = array.array('f', [0.0] * 8) * len_faces
539 mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
541 objects = context.selected_editable_objects[:]
543 for obj_other in objects:
544 if obj_other.type == 'MESH':
545 obj_other.data.tag = False
547 for obj_other in objects:
548 if obj_other != obj and obj_other.type == 'MESH':
549 mesh_other = obj_other.data
550 if mesh_other != mesh:
551 if mesh_other.tag == False:
552 mesh_other.tag = True
554 if len(mesh_other.faces) != len_faces:
555 self.report({'WARNING'}, "Object: %s, Mesh: "
556 "'%s' has %d faces, expected %d\n"
559 len(mesh_other.faces),
563 uv_other = mesh_other.uv_textures.active
565 # should return the texture it adds
566 uv_other = mesh_other.uv_textures.new()
568 # finally do the copy
569 uv_other.data.foreach_set("uv_raw", uv_array)
572 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
574 def execute(self, context):
579 class MakeDupliFace(Operator):
580 '''Make linked objects into dupli-faces'''
581 bl_idname = "object.make_dupli_face"
582 bl_label = "Make Dupli-Face"
585 def poll(cls, context):
586 obj = context.active_object
587 return (obj and obj.type == 'MESH')
589 def _main(self, context):
590 from mathutils import Vector
593 offset = 0.5 * SCALE_FAC
594 base_tri = (Vector((-offset, -offset, 0.0)),
595 Vector((+offset, -offset, 0.0)),
596 Vector((+offset, +offset, 0.0)),
597 Vector((-offset, +offset, 0.0)),
600 def matrix_to_quat(matrix):
601 # scale = matrix.median_scale
602 trans = matrix.to_translation()
603 rot = matrix.to_3x3() # also contains scale
605 return [(rot * b) + trans for b in base_tri]
606 scene = bpy.context.scene
608 for obj in bpy.context.selected_objects:
611 linked.setdefault(data, []).append(obj)
613 for data, objects in linked.items():
614 face_verts = [axis for obj in objects
615 for v in matrix_to_quat(obj.matrix_world)
618 faces = list(range(len(face_verts) // 3))
620 mesh = bpy.data.meshes.new(data.name + "_dupli")
622 mesh.vertices.add(len(face_verts) // 3)
623 mesh.faces.add(len(face_verts) // 12)
625 mesh.vertices.foreach_set("co", face_verts)
626 mesh.faces.foreach_set("vertices_raw", faces)
627 mesh.update() # generates edge data
629 # pick an object to use
632 ob_new = bpy.data.objects.new(mesh.name, mesh)
633 base = scene.objects.link(ob_new)
634 base.layers[:] = obj.layers
636 ob_inst = bpy.data.objects.new(data.name, data)
637 base = scene.objects.link(ob_inst)
638 base.layers[:] = obj.layers
641 scene.objects.unlink(obj)
643 ob_new.dupli_type = 'FACES'
644 ob_inst.parent = ob_new
645 ob_new.use_dupli_faces_scale = True
646 ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
648 def execute(self, context):
653 class IsolateTypeRender(Operator):
654 '''Hide unselected render objects of same type as active ''' \
655 '''by setting the hide render flag'''
656 bl_idname = "object.isolate_type_render"
657 bl_label = "Restrict Render Unselected"
658 bl_options = {'REGISTER', 'UNDO'}
660 def execute(self, context):
661 act_type = context.object.type
663 for obj in context.visible_objects:
666 obj.hide_render = False
668 if obj.type == act_type:
669 obj.hide_render = True
674 class ClearAllRestrictRender(Operator):
675 '''Reveal all render objects by setting the hide render flag'''
676 bl_idname = "object.hide_render_clear_all"
677 bl_label = "Clear All Restrict Render"
678 bl_options = {'REGISTER', 'UNDO'}
680 def execute(self, context):
681 for obj in context.scene.objects:
682 obj.hide_render = False