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,
29 class SelectPattern(Operator):
30 """Select objects matching a naming pattern"""
31 bl_idname = "object.select_pattern"
32 bl_label = "Select Pattern"
33 bl_options = {'REGISTER', 'UNDO'}
35 pattern = StringProperty(
37 description="Name filter using '*', '?' and "
38 "'[abc]' unix style wildcards",
42 case_sensitive = BoolProperty(
43 name="Case Sensitive",
44 description="Do a case sensitive compare",
47 extend = BoolProperty(
49 description="Extend the existing selection",
53 def execute(self, context):
57 if self.case_sensitive:
58 pattern_match = fnmatch.fnmatchcase
60 pattern_match = (lambda a, b:
61 fnmatch.fnmatchcase(a.upper(), b.upper()))
64 if obj and obj.mode == 'POSE':
65 items = obj.data.bones
67 bpy.ops.pose.select_all(action='DESELECT')
68 elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
69 items = obj.data.edit_bones
71 bpy.ops.armature.select_all(action='DESELECT')
74 items = context.visible_objects
76 bpy.ops.object.select_all(action='DESELECT')
78 # Can be pose bones or objects
80 if pattern_match(item.name, self.pattern):
83 # hrmf, perhaps there should be a utility function for this.
85 item.select_head = True
86 item.select_tail = True
88 item_parent = item.parent
89 if item_parent is not None:
90 item_parent.select_tail = True
94 def invoke(self, context, event):
95 wm = context.window_manager
96 return wm.invoke_props_popup(self, event)
98 def draw(self, context):
101 layout.prop(self, "pattern")
103 row.prop(self, "case_sensitive")
104 row.prop(self, "extend")
107 class SelectCamera(Operator):
108 """Select the active camera"""
109 bl_idname = "object.select_camera"
110 bl_label = "Select Camera"
111 bl_options = {'REGISTER', 'UNDO'}
113 def execute(self, context):
114 scene = context.scene
115 view = context.space_data
116 if view.type == 'VIEW_3D' and not view.lock_camera_and_layers:
119 camera = scene.camera
122 self.report({'WARNING'}, "No camera found")
123 elif camera.name not in scene.objects:
124 self.report({'WARNING'}, "Active camera is not in this scene")
126 context.scene.objects.active = camera
133 class SelectHierarchy(Operator):
134 """Select object relative to the active object's position """ \
135 """in the hierarchy"""
136 bl_idname = "object.select_hierarchy"
137 bl_label = "Select Hierarchy"
138 bl_options = {'REGISTER', 'UNDO'}
140 direction = EnumProperty(
141 items=(('PARENT', "Parent", ""),
142 ('CHILD', "Child", ""),
145 description="Direction to select in the hierarchy",
148 extend = BoolProperty(
150 description="Extend the existing selection",
155 def poll(cls, context):
156 return context.object
158 def execute(self, context):
162 selected_objects = context.selected_objects
163 obj_act = context.object
165 if context.object not in selected_objects:
166 selected_objects.append(context.object)
168 if self.direction == 'PARENT':
169 for obj in selected_objects:
176 select_new.append(parent)
179 for obj in selected_objects:
180 select_new.extend(obj.children)
183 select_new.sort(key=lambda obj_iter: obj_iter.name)
184 act_new = select_new[0]
186 # don't edit any object settings above this
189 bpy.ops.object.select_all(action='DESELECT')
191 for obj in select_new:
194 context.scene.objects.active = act_new
200 class SubdivisionSet(Operator):
201 """Sets a Subdivision Surface Level (1-5)"""
203 bl_idname = "object.subdivision_set"
204 bl_label = "Subdivision Set"
205 bl_options = {'REGISTER', 'UNDO'}
210 soft_min=-6, soft_max=6,
214 relative = BoolProperty(
216 description=("Apply the subsurf level as an offset "
217 "relative to the current level"),
222 def poll(cls, context):
223 obs = context.selected_editable_objects
224 return (obs is not None)
226 def execute(self, context):
228 relative = self.relative
230 if relative and level == 0:
231 return {'CANCELLED'} # nothing to do
233 if not relative and level < 0:
234 self.level = level = 0
236 def set_object_subd(obj):
237 for mod in obj.modifiers:
238 if mod.type == 'MULTIRES':
240 if level <= mod.total_levels:
241 if obj.mode == 'SCULPT':
242 if mod.sculpt_levels != level:
243 mod.sculpt_levels = level
244 elif obj.mode == 'OBJECT':
245 if mod.levels != level:
249 if obj.mode == 'SCULPT':
250 if mod.sculpt_levels + level <= mod.total_levels:
251 mod.sculpt_levels += level
252 elif obj.mode == 'OBJECT':
253 if mod.levels + level <= mod.total_levels:
257 elif mod.type == 'SUBSURF':
261 if mod.levels != level:
268 mod = obj.modifiers.new("Subsurf", 'SUBSURF')
271 self.report({'WARNING'},
272 "Modifiers cannot be added to object: " + obj.name)
274 for obj in context.selected_editable_objects:
280 class ShapeTransfer(Operator):
281 """Copy another selected objects active shape to this one by """ \
282 """applying the relative offsets"""
284 bl_idname = "object.shape_key_transfer"
285 bl_label = "Transfer Shape Key"
286 bl_options = {'REGISTER', 'UNDO'}
291 "Apply the relative positional offset",
295 "Calculate relative position (using faces)",
299 "Calculate relative position (using edges)",
302 name="Transformation Mode",
303 description="Relative shape positions to the new shape method",
306 use_clamp = BoolProperty(
308 description=("Clamp the transformation to the distance each "
309 "vertex moves in the original shape"),
313 def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
316 return [v.normal.copy() for v in verts]
319 return [v.co.copy() for v in verts]
321 def ob_add_shape(ob, name):
323 key = ob.shape_key_add(from_mix=False)
324 if len(me.shape_keys.key_blocks) == 1:
326 key = ob.shape_key_add(from_mix=False) # we need a rest
328 ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
329 ob.show_only_shape_key = True
331 from mathutils.geometry import barycentric_transform
332 from mathutils import Vector
334 if use_clamp and mode == 'OFFSET':
338 orig_key_name = ob_act.active_shape_key.name
340 orig_shape_coords = me_cos(ob_act.active_shape_key.data)
342 orig_normals = me_nos(me.vertices)
343 # actual mesh vertex location isn't as reliable as the base shape :S
344 #~ orig_coords = me_cos(me.vertices)
345 orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
347 for ob_other in objects:
348 me_other = ob_other.data
349 if len(me_other.vertices) != len(me.vertices):
350 self.report({'WARNING'},
352 "vertex count differs") % ob_other.name)
355 target_normals = me_nos(me_other.vertices)
356 if me_other.shape_keys:
357 target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
359 target_coords = me_cos(me_other.vertices)
361 ob_add_shape(ob_other, orig_key_name)
363 # editing the final coords, only list that stores wrapped coords
364 target_shape_coords = [v.co for v in
365 ob_other.active_shape_key.data]
367 median_coords = [[] for i in range(len(me.vertices))]
371 for i, vert_cos in enumerate(median_coords):
372 vert_cos.append(target_coords[i] +
373 (orig_shape_coords[i] - orig_coords[i]))
375 elif mode == 'RELATIVE_FACE':
376 loops_vidxs = me.loops.foreach_get("vert_index")
377 for poly in me.polygons:
378 l_start = l_stop = poly.loop_start
379 l_stop += poly.loop_total
380 idxs = loops_vidxs[l_start:l_stop]
384 pt = barycentric_transform(orig_shape_coords[v],
385 orig_coords[v_before],
387 orig_coords[v_after],
388 target_coords[v_before],
390 target_coords[v_after],
392 median_coords[v].append(pt)
396 elif mode == 'RELATIVE_EDGE':
399 v1, v2 = orig_coords[i1], orig_coords[i2]
400 edge_length = (v1 - v2).length
401 n1loc = v1 + orig_normals[i1] * edge_length
402 n2loc = v2 + orig_normals[i2] * edge_length
404 # now get the target nloc's
405 v1_to, v2_to = target_coords[i1], target_coords[i2]
406 edlen_to = (v1_to - v2_to).length
407 n1loc_to = v1_to + target_normals[i1] * edlen_to
408 n2loc_to = v2_to + target_normals[i2] * edlen_to
410 pt = barycentric_transform(orig_shape_coords[i1],
412 v2_to, v1_to, n1loc_to)
413 median_coords[i1].append(pt)
415 pt = barycentric_transform(orig_shape_coords[i2],
417 v1_to, v2_to, n2loc_to)
418 median_coords[i2].append(pt)
420 # apply the offsets to the new shape
421 from functools import reduce
422 VectorAdd = Vector.__add__
424 for i, vert_cos in enumerate(median_coords):
426 co = reduce(VectorAdd, vert_cos) / len(vert_cos)
429 # clamp to the same movement as the original
430 # breaks copy between different scaled meshes.
431 len_from = (orig_shape_coords[i] -
432 orig_coords[i]).length
433 ofs = co - target_coords[i]
434 ofs.length = len_from
435 co = target_coords[i] + ofs
437 target_shape_coords[i][:] = co
442 def poll(cls, context):
443 obj = context.active_object
444 return (obj and obj.mode != 'EDIT')
446 def execute(self, context):
447 ob_act = context.active_object
448 objects = [ob for ob in context.selected_editable_objects
451 if 1: # swap from/to, means we cant copy to many at once.
452 if len(objects) != 1:
453 self.report({'ERROR'},
454 ("Expected one other selected "
455 "mesh object to copy from"))
458 ob_act, objects = objects[0], [ob_act]
460 if ob_act.type != 'MESH':
461 self.report({'ERROR'}, "Other object is not a mesh")
464 if ob_act.active_shape_key is None:
465 self.report({'ERROR'}, "Other object has no shape key")
467 return self._main(ob_act, objects, self.mode, self.use_clamp)
470 class JoinUVs(Operator):
471 """Copy UV Layout to objects with matching geometry"""
472 bl_idname = "object.join_uvs"
473 bl_label = "Join as UVs"
476 def poll(cls, context):
477 obj = context.active_object
478 return (obj and obj.type == 'MESH')
480 def _main(self, context):
482 obj = context.active_object
485 is_editmode = (obj.mode == 'EDIT')
487 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
489 if not mesh.uv_textures:
490 self.report({'WARNING'},
491 "Object: %s, Mesh: '%s' has no UVs"
492 % (obj.name, mesh.name))
494 nbr_loops = len(mesh.loops)
496 # seems to be the fastest way to create an array
497 uv_array = array.array('f', [0.0] * 2) * nbr_loops
498 mesh.uv_layers.active.data.foreach_get("uv", uv_array)
500 objects = context.selected_editable_objects[:]
502 for obj_other in objects:
503 if obj_other.type == 'MESH':
504 obj_other.data.tag = False
506 for obj_other in objects:
507 if obj_other != obj and obj_other.type == 'MESH':
508 mesh_other = obj_other.data
509 if mesh_other != mesh:
510 if mesh_other.tag == False:
511 mesh_other.tag = True
513 if len(mesh_other.loops) != nbr_loops:
514 self.report({'WARNING'}, "Object: %s, Mesh: "
515 "'%s' has %d loops (for %d faces),"
519 len(mesh_other.loops),
520 len(mesh_other.polygons),
525 uv_other = mesh_other.uv_layers.active
527 mesh_other.uv_textures.new()
528 uv_other = mesh_other.uv_layers.active
530 self.report({'ERROR'}, "Could not add "
531 "a new UV map tp object "
538 # finally do the copy
539 uv_other.data.foreach_set("uv", uv_array)
542 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
544 def execute(self, context):
549 class MakeDupliFace(Operator):
550 """Make linked objects into dupli-faces"""
551 bl_idname = "object.make_dupli_face"
552 bl_label = "Make Dupli-Face"
554 def _main(self, context):
555 from mathutils import Vector
558 offset = 0.5 * SCALE_FAC
559 base_tri = (Vector((-offset, -offset, 0.0)),
560 Vector((+offset, -offset, 0.0)),
561 Vector((+offset, +offset, 0.0)),
562 Vector((-offset, +offset, 0.0)),
565 def matrix_to_quad(matrix):
566 # scale = matrix.median_scale
567 trans = matrix.to_translation()
568 rot = matrix.to_3x3() # also contains scale
570 return [(rot * b) + trans for b in base_tri]
571 scene = context.scene
573 for obj in context.selected_objects:
576 linked.setdefault(data, []).append(obj)
578 for data, objects in linked.items():
579 face_verts = [axis for obj in objects
580 for v in matrix_to_quad(obj.matrix_world)
582 nbr_verts = len(face_verts) // 3
583 nbr_faces = nbr_verts // 4
585 faces = list(range(nbr_verts))
587 mesh = bpy.data.meshes.new(data.name + "_dupli")
589 mesh.vertices.add(nbr_verts)
590 mesh.loops.add(nbr_faces * 4) # Safer than nbr_verts.
591 mesh.polygons.add(nbr_faces)
593 mesh.vertices.foreach_set("co", face_verts)
594 mesh.loops.foreach_set("vertex_index", faces)
595 mesh.polygons.foreach_set("loop_start", range(0, nbr_faces * 4, 4))
596 mesh.polygons.foreach_set("loop_total", (4,) * nbr_faces)
597 mesh.update() # generates edge data
599 # pick an object to use
602 ob_new = bpy.data.objects.new(mesh.name, mesh)
603 base = scene.objects.link(ob_new)
604 base.layers[:] = obj.layers
606 ob_inst = bpy.data.objects.new(data.name, data)
607 base = scene.objects.link(ob_inst)
608 base.layers[:] = obj.layers
611 scene.objects.unlink(obj)
613 ob_new.dupli_type = 'FACES'
614 ob_inst.parent = ob_new
615 ob_new.use_dupli_faces_scale = True
616 ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
618 def execute(self, context):
623 class IsolateTypeRender(Operator):
624 """Hide unselected render objects of same type as active """ \
625 """by setting the hide render flag"""
626 bl_idname = "object.isolate_type_render"
627 bl_label = "Restrict Render Unselected"
628 bl_options = {'REGISTER', 'UNDO'}
630 def execute(self, context):
631 act_type = context.object.type
633 for obj in context.visible_objects:
636 obj.hide_render = False
638 if obj.type == act_type:
639 obj.hide_render = True
644 class ClearAllRestrictRender(Operator):
645 """Reveal all render objects by setting the hide render flag"""
646 bl_idname = "object.hide_render_clear_all"
647 bl_label = "Clear All Restrict Render"
648 bl_options = {'REGISTER', 'UNDO'}
650 def execute(self, context):
651 for obj in context.scene.objects:
652 obj.hide_render = False
656 class TransformsToDeltasAnim(Operator):
657 """Convert object animation for normal transforms to delta transforms"""
658 bl_idname = "object.anim_transforms_to_deltas"
659 bl_label = "Animated Transforms to Deltas"
660 bl_options = {'REGISTER', 'UNDO'}
663 def poll(cls, context):
664 obs = context.selected_editable_objects
665 return (obs is not None)
667 def execute(self, context):
668 for obj in context.selected_editable_objects:
670 adt = obj.animation_data
671 if (adt is None) or (adt.action is None):
672 self.report({'WARNING'},
673 "No animation data to convert on object: %r" %
677 # if F-Curve uses standard transform path
678 # just append "delta_" to this path
679 for fcu in adt.action.fcurves:
680 if fcu.data_path == "location":
681 fcu.data_path = "delta_location"
683 elif fcu.data_path == "rotation_euler":
684 fcu.data_path = "delta_rotation_euler"
685 obj.rotation_euler.zero()
686 elif fcu.data_path == "rotation_quaternion":
687 fcu.data_path = "delta_rotation_quaternion"
688 obj.rotation_quaternion.identity()
689 # XXX: currently not implemented
690 #~ elif fcu.data_path == "rotation_axis_angle":
691 #~ fcu.data_path = "delta_rotation_axis_angle"
692 elif fcu.data_path == "scale":
693 fcu.data_path = "delta_scale"
694 obj.scale = 1.0, 1.0, 1.0
696 # hack: force animsys flush by changing frame, so that deltas get run
697 context.scene.frame_set(context.scene.frame_current)
702 class DupliOffsetFromCursor(Operator):
703 """Set offset used for DupliGroup based on cursor position"""
704 bl_idname = "object.dupli_offset_from_cursor"
705 bl_label = "Set Offset From Cursor"
706 bl_options = {'REGISTER', 'UNDO'}
710 description="Group index to set offset for",
715 def poll(cls, context):
716 return context.active_object is not None
718 def execute(self, context):
719 scene = context.scene
720 ob = context.active_object
723 ob.users_group[group].dupli_offset = scene.cursor_location