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.props import StringProperty, BoolProperty, EnumProperty, IntProperty
25 class SelectPattern(bpy.types.Operator):
26 '''Select object matching a naming pattern'''
27 bl_idname = "object.select_pattern"
28 bl_label = "Select Pattern"
29 bl_options = {'REGISTER', 'UNDO'}
31 pattern = StringProperty(name="Pattern", description="Name filter using '*' and '?' wildcard chars", maxlen=32, default="*")
32 case_sensitive = BoolProperty(name="Case Sensitive", description="Do a case sensitive compare", default=False)
33 extend = BoolProperty(name="Extend", description="Extend the existing selection", default=True)
35 def execute(self, context):
39 if self.case_sensitive:
40 pattern_match = fnmatch.fnmatchcase
42 pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper())
45 if obj and obj.mode == 'POSE':
46 items = obj.data.bones
47 elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
48 items = obj.data.edit_bones
50 items = context.visible_objects
52 # Can be pose bones or objects
54 if pattern_match(item.name, self.pattern):
61 def invoke(self, context, event):
62 wm = context.window_manager
63 return wm.invoke_props_popup(self, event)
65 def draw(self, context):
68 layout.prop(self, "pattern")
70 row.prop(self, "case_sensitive")
71 row.prop(self, "extend")
74 class SelectCamera(bpy.types.Operator):
75 '''Select object matching a naming pattern'''
76 bl_idname = "object.select_camera"
77 bl_label = "Select Camera"
78 bl_options = {'REGISTER', 'UNDO'}
81 def poll(cls, context):
82 return context.scene.camera is not None
84 def execute(self, context):
87 if camera.name not in scene.objects:
88 self.report({'WARNING'}, "Active camera is not in this scene")
90 context.scene.objects.active = camera
95 class SelectHierarchy(bpy.types.Operator):
96 '''Select object relative to the active objects position in the hierarchy'''
97 bl_idname = "object.select_hierarchy"
98 bl_label = "Select Hierarchy"
99 bl_options = {'REGISTER', 'UNDO'}
101 direction = EnumProperty(items=(
102 ('PARENT', "Parent", ""),
103 ('CHILD', "Child", "")),
105 description="Direction to select in the hierarchy",
108 extend = BoolProperty(name="Extend", description="Extend the existing selection", default=False)
111 def poll(cls, context):
112 return context.object
114 def execute(self, context):
118 selected_objects = context.selected_objects
119 obj_act = context.object
121 if context.object not in selected_objects:
122 selected_objects.append(context.object)
124 if self.direction == 'PARENT':
125 for obj in selected_objects:
132 select_new.append(parent)
135 for obj in selected_objects:
136 select_new.extend(obj.children)
139 select_new.sort(key=lambda obj_iter: obj_iter.name)
140 act_new = select_new[0]
142 # dont edit any object settings above this
145 bpy.ops.object.select_all(action='DESELECT')
147 for obj in select_new:
150 context.scene.objects.active = act_new
156 class SubdivisionSet(bpy.types.Operator):
157 '''Sets a Subdivision Surface Level (1-5)'''
159 bl_idname = "object.subdivision_set"
160 bl_label = "Subdivision Set"
161 bl_options = {'REGISTER', 'UNDO'}
163 level = IntProperty(name="Level",
164 default=1, min=-100, max=100, soft_min=-6, soft_max=6)
166 relative = BoolProperty(name="Relative", description="Apply the subsurf level as an offset relative to the current level", default=False)
169 def poll(cls, context):
170 obs = context.selected_editable_objects
171 return (obs is not None)
173 def execute(self, context):
175 relative = self.relative
177 if relative and level == 0:
178 return {'CANCELLED'} # nothing to do
180 if not relative and level < 0:
181 self.level = level = 0
183 def set_object_subd(obj):
184 for mod in obj.modifiers:
185 if mod.type == 'MULTIRES':
187 if level <= mod.total_levels:
188 if obj.mode == 'SCULPT':
189 if mod.sculpt_levels != level:
190 mod.sculpt_levels = level
191 elif obj.mode == 'OBJECT':
192 if mod.levels != level:
196 if obj.mode == 'SCULPT':
197 if mod.sculpt_levels + level <= mod.total_levels:
198 mod.sculpt_levels += level
199 elif obj.mode == 'OBJECT':
200 if mod.levels + level <= mod.total_levels:
204 elif mod.type == 'SUBSURF':
208 if mod.levels != level:
215 mod = obj.modifiers.new("Subsurf", 'SUBSURF')
218 self.report({'WARNING'}, "Modifiers cannot be added to object: " + obj.name)
220 for obj in context.selected_editable_objects:
226 class ShapeTransfer(bpy.types.Operator):
227 '''Copy another selected objects active shape to this one by applying the relative offsets'''
229 bl_idname = "object.shape_key_transfer"
230 bl_label = "Transfer Shape Key"
231 bl_options = {'REGISTER', 'UNDO'}
233 mode = EnumProperty(items=(
234 ('OFFSET', "Offset", "Apply the relative positional offset"),
235 ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."),
236 ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")),
237 name="Transformation Mode",
238 description="Method to apply relative shape positions to the new shape",
241 use_clamp = BoolProperty(name="Clamp Offset",
242 description="Clamp the transformation to the distance each vertex moves in the original shape.",
245 def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
248 return [v.normal.copy() for v in verts]
251 return [v.co.copy() for v in verts]
253 def ob_add_shape(ob, name):
255 key = ob.shape_key_add(from_mix=False)
256 if len(me.shape_keys.key_blocks) == 1:
258 key = ob.shape_key_add(from_mix=False) # we need a rest
260 ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
261 ob.show_only_shape_key = True
263 from mathutils.geometry import barycentric_transform
264 from mathutils import Vector
266 if use_clamp and mode == 'OFFSET':
270 orig_key_name = ob_act.active_shape_key.name
272 orig_shape_coords = me_cos(ob_act.active_shape_key.data)
274 orig_normals = me_nos(me.vertices)
275 # orig_coords = me_cos(me.vertices) # the actual mverts location isnt as relyable as the base shape :S
276 orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
278 for ob_other in objects:
279 me_other = ob_other.data
280 if len(me_other.vertices) != len(me.vertices):
281 self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
284 target_normals = me_nos(me_other.vertices)
285 if me_other.shape_keys:
286 target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
288 target_coords = me_cos(me_other.vertices)
290 ob_add_shape(ob_other, orig_key_name)
292 # editing the final coords, only list that stores wrapped coords
293 target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
295 median_coords = [[] for i in range(len(me.vertices))]
299 for i, vert_cos in enumerate(median_coords):
300 vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
302 elif mode == 'RELATIVE_FACE':
303 for face in me.faces:
304 i1, i2, i3, i4 = face.vertices_raw
306 pt = barycentric_transform(orig_shape_coords[i1],
307 orig_coords[i4], orig_coords[i1], orig_coords[i2],
308 target_coords[i4], target_coords[i1], target_coords[i2])
309 median_coords[i1].append(pt)
311 pt = barycentric_transform(orig_shape_coords[i2],
312 orig_coords[i1], orig_coords[i2], orig_coords[i3],
313 target_coords[i1], target_coords[i2], target_coords[i3])
314 median_coords[i2].append(pt)
316 pt = barycentric_transform(orig_shape_coords[i3],
317 orig_coords[i2], orig_coords[i3], orig_coords[i4],
318 target_coords[i2], target_coords[i3], target_coords[i4])
319 median_coords[i3].append(pt)
321 pt = barycentric_transform(orig_shape_coords[i4],
322 orig_coords[i3], orig_coords[i4], orig_coords[i1],
323 target_coords[i3], target_coords[i4], target_coords[i1])
324 median_coords[i4].append(pt)
327 pt = barycentric_transform(orig_shape_coords[i1],
328 orig_coords[i3], orig_coords[i1], orig_coords[i2],
329 target_coords[i3], target_coords[i1], target_coords[i2])
330 median_coords[i1].append(pt)
332 pt = barycentric_transform(orig_shape_coords[i2],
333 orig_coords[i1], orig_coords[i2], orig_coords[i3],
334 target_coords[i1], target_coords[i2], target_coords[i3])
335 median_coords[i2].append(pt)
337 pt = barycentric_transform(orig_shape_coords[i3],
338 orig_coords[i2], orig_coords[i3], orig_coords[i1],
339 target_coords[i2], target_coords[i3], target_coords[i1])
340 median_coords[i3].append(pt)
342 elif mode == 'RELATIVE_EDGE':
345 v1, v2 = orig_coords[i1], orig_coords[i2]
346 edge_length = (v1 - v2).length
347 n1loc = v1 + orig_normals[i1] * edge_length
348 n2loc = v2 + orig_normals[i2] * edge_length
350 # now get the target nloc's
351 v1_to, v2_to = target_coords[i1], target_coords[i2]
352 edlen_to = (v1_to - v2_to).length
353 n1loc_to = v1_to + target_normals[i1] * edlen_to
354 n2loc_to = v2_to + target_normals[i2] * edlen_to
356 pt = barycentric_transform(orig_shape_coords[i1],
358 v2_to, v1_to, n1loc_to)
359 median_coords[i1].append(pt)
361 pt = barycentric_transform(orig_shape_coords[i2],
363 v1_to, v2_to, n2loc_to)
364 median_coords[i2].append(pt)
366 # apply the offsets to the new shape
367 from functools import reduce
368 VectorAdd = Vector.__add__
370 for i, vert_cos in enumerate(median_coords):
372 co = reduce(VectorAdd, vert_cos) / len(vert_cos)
375 # clamp to the same movement as the original
376 # breaks copy between different scaled meshes.
377 len_from = (orig_shape_coords[i] - orig_coords[i]).length
378 ofs = co - target_coords[i]
379 ofs.length = len_from
380 co = target_coords[i] + ofs
382 target_shape_coords[i][:] = co
387 def poll(cls, context):
388 obj = context.active_object
389 return (obj and obj.mode != 'EDIT')
391 def execute(self, context):
393 ob_act = C.active_object
394 objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
396 if 1: # swap from/to, means we cant copy to many at once.
397 if len(objects) != 1:
398 self.report({'ERROR'}, "Expected one other selected mesh object to copy from")
400 ob_act, objects = objects[0], [ob_act]
402 if ob_act.type != 'MESH':
403 self.report({'ERROR'}, "Other object is not a mesh.")
406 if ob_act.active_shape_key is None:
407 self.report({'ERROR'}, "Other object has no shape key")
409 return self._main(ob_act, objects, self.mode, self.use_clamp)
412 class JoinUVs(bpy.types.Operator):
413 '''Copy UV Layout to objects with matching geometry'''
414 bl_idname = "object.join_uvs"
415 bl_label = "Join as UVs"
418 def poll(cls, context):
419 obj = context.active_object
420 return (obj and obj.type == 'MESH')
422 def _main(self, context):
424 obj = context.active_object
427 is_editmode = (obj.mode == 'EDIT')
429 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
431 if not mesh.uv_textures:
432 self.report({'WARNING'}, "Object: %s, Mesh: '%s' has no UVs\n" % (obj.name, mesh.name))
434 len_faces = len(mesh.faces)
436 uv_array = array.array('f', [0.0] * 8) * len_faces # seems to be the fastest way to create an array
437 mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
439 objects = context.selected_editable_objects[:]
441 for obj_other in objects:
442 if obj_other.type == 'MESH':
443 obj_other.data.tag = False
445 for obj_other in objects:
446 if obj_other != obj and obj_other.type == 'MESH':
447 mesh_other = obj_other.data
448 if mesh_other != mesh:
449 if mesh_other.tag == False:
450 mesh_other.tag = True
452 if len(mesh_other.faces) != len_faces:
453 self.report({'WARNING'}, "Object: %s, Mesh: '%s' has %d faces, expected %d\n" % (obj_other.name, mesh_other.name, len(mesh_other.faces), len_faces))
455 uv_other = mesh_other.uv_textures.active
457 uv_other = mesh_other.uv_textures.new() # should return the texture it adds
459 # finally do the copy
460 uv_other.data.foreach_set("uv_raw", uv_array)
463 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
465 def execute(self, context):
470 class MakeDupliFace(bpy.types.Operator):
471 '''Make linked objects into dupli-faces'''
472 bl_idname = "object.make_dupli_face"
473 bl_label = "Make Dupli-Face"
476 def poll(cls, context):
477 obj = context.active_object
478 return (obj and obj.type == 'MESH')
480 def _main(self, context):
481 from mathutils import Vector
484 offset = 0.5 * SCALE_FAC
485 base_tri = Vector((-offset, -offset, 0.0)), Vector((offset, -offset, 0.0)), Vector((offset, offset, 0.0)), Vector((-offset, offset, 0.0))
487 def matrix_to_quat(matrix):
488 # scale = matrix.median_scale
489 trans = matrix.to_translation()
490 rot = matrix.to_3x3() # also contains scale
492 return [(b * rot) + trans for b in base_tri]
493 scene = bpy.context.scene
495 for obj in bpy.context.selected_objects:
498 linked.setdefault(data, []).append(obj)
500 for data, objects in linked.items():
501 face_verts = [axis for obj in objects for v in matrix_to_quat(obj.matrix_world) for axis in v]
502 faces = list(range(len(face_verts) // 3))
504 mesh = bpy.data.meshes.new(data.name + "_dupli")
506 mesh.vertices.add(len(face_verts) // 3)
507 mesh.faces.add(len(face_verts) // 12)
509 mesh.vertices.foreach_set("co", face_verts)
510 mesh.faces.foreach_set("vertices_raw", faces)
511 mesh.update() # generates edge data
513 # pick an object to use
516 ob_new = bpy.data.objects.new(mesh.name, mesh)
517 base = scene.objects.link(ob_new)
518 base.layers[:] = obj.layers
520 ob_inst = bpy.data.objects.new(data.name, data)
521 base = scene.objects.link(ob_inst)
522 base.layers[:] = obj.layers
525 scene.objects.unlink(obj)
527 ob_new.dupli_type = 'FACES'
528 ob_inst.parent = ob_new
529 ob_new.use_dupli_faces_scale = True
530 ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
532 def execute(self, context):
537 class IsolateTypeRender(bpy.types.Operator):
538 '''Hide unselected render objects of same type as active by setting the hide render flag'''
539 bl_idname = "object.isolate_type_render"
540 bl_label = "Restrict Render Unselected"
541 bl_options = {'REGISTER', 'UNDO'}
543 def execute(self, context):
544 act_type = context.object.type
546 for obj in context.visible_objects:
549 obj.hide_render = False
551 if obj.type == act_type:
552 obj.hide_render = True
557 class ClearAllRestrictRender(bpy.types.Operator):
558 '''Reveal all render objects by setting the hide render flag'''
559 bl_idname = "object.hide_render_clear_all"
560 bl_label = "Clear All Restrict Render"
561 bl_options = {'REGISTER', 'UNDO'}
563 def execute(self, context):
564 for obj in context.scene.objects:
565 obj.hide_render = False