diff options
Diffstat (limited to 'release/scripts/startup/bl_operators/object.py')
-rw-r--r-- | release/scripts/startup/bl_operators/object.py | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py new file mode 100644 index 00000000000..db5eaccfc8f --- /dev/null +++ b/release/scripts/startup/bl_operators/object.py @@ -0,0 +1,563 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +import bpy +from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty + + +class SelectPattern(bpy.types.Operator): + '''Select object matching a naming pattern''' + bl_idname = "object.select_pattern" + bl_label = "Select Pattern" + bl_options = {'REGISTER', 'UNDO'} + + pattern = StringProperty(name="Pattern", description="Name filter using '*' and '?' wildcard chars", maxlen=32, default="*") + case_sensitive = BoolProperty(name="Case Sensitive", description="Do a case sensitive compare", default=False) + extend = BoolProperty(name="Extend", description="Extend the existing selection", default=True) + + def execute(self, context): + + import fnmatch + + if self.case_sensitive: + pattern_match = fnmatch.fnmatchcase + else: + pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper()) + + obj = context.object + if obj and obj.mode == 'POSE': + items = obj.data.bones + elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT': + items = obj.data.edit_bones + else: + items = context.visible_objects + + # Can be pose bones or objects + for item in items: + if pattern_match(item.name, self.pattern): + item.select = True + elif not self.extend: + item.select = False + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_popup(self, event) + + def draw(self, context): + layout = self.layout + + layout.prop(self, "pattern") + row = layout.row() + row.prop(self, "case_sensitive") + row.prop(self, "extend") + + +class SelectCamera(bpy.types.Operator): + '''Select object matching a naming pattern''' + bl_idname = "object.select_camera" + bl_label = "Select Camera" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.scene.camera is not None + + def execute(self, context): + scene = context.scene + camera = scene.camera + if camera.name not in scene.objects: + self.report({'WARNING'}, "Active camera is not in this scene") + + context.scene.objects.active = camera + camera.select = True + return {'FINISHED'} + + +class SelectHierarchy(bpy.types.Operator): + '''Select object relative to the active objects position in the hierarchy''' + bl_idname = "object.select_hierarchy" + bl_label = "Select Hierarchy" + bl_options = {'REGISTER', 'UNDO'} + + direction = EnumProperty(items=( + ('PARENT', "Parent", ""), + ('CHILD', "Child", "")), + name="Direction", + description="Direction to select in the hierarchy", + default='PARENT') + + extend = BoolProperty(name="Extend", description="Extend the existing selection", default=False) + + @classmethod + def poll(cls, context): + return context.object + + def execute(self, context): + select_new = [] + act_new = None + + selected_objects = context.selected_objects + obj_act = context.object + + if context.object not in selected_objects: + selected_objects.append(context.object) + + if self.direction == 'PARENT': + for obj in selected_objects: + parent = obj.parent + + if parent: + if obj_act == obj: + act_new = parent + + select_new.append(parent) + + else: + for obj in selected_objects: + select_new.extend(obj.children) + + if select_new: + select_new.sort(key=lambda obj_iter: obj_iter.name) + act_new = select_new[0] + + # dont edit any object settings above this + if select_new: + if not self.extend: + bpy.ops.object.select_all(action='DESELECT') + + for obj in select_new: + obj.select = True + + context.scene.objects.active = act_new + return {'FINISHED'} + + return {'CANCELLED'} + + +class SubdivisionSet(bpy.types.Operator): + '''Sets a Subdivision Surface Level (1-5)''' + + bl_idname = "object.subdivision_set" + bl_label = "Subdivision Set" + bl_options = {'REGISTER', 'UNDO'} + + level = IntProperty(name="Level", + default=1, min=-100, max=100, soft_min=-6, soft_max=6) + + relative = BoolProperty(name="Relative", description="Apply the subsurf level as an offset relative to the current level", default=False) + + @classmethod + def poll(cls, context): + obs = context.selected_editable_objects + return (obs is not None) + + def execute(self, context): + level = self.level + relative = self.relative + + if relative and level == 0: + return {'CANCELLED'} # nothing to do + + def set_object_subd(obj): + for mod in obj.modifiers: + if mod.type == 'MULTIRES': + if not relative: + if level <= mod.total_levels: + if obj.mode == 'SCULPT': + if mod.sculpt_levels != level: + mod.sculpt_levels = level + elif obj.mode == 'OBJECT': + if mod.levels != level: + mod.levels = level + return + else: + if obj.mode == 'SCULPT': + if mod.sculpt_levels + level <= mod.total_levels: + mod.sculpt_levels += level + elif obj.mode == 'OBJECT': + if mod.levels + level <= mod.total_levels: + mod.levels += level + return + + elif mod.type == 'SUBSURF': + if relative: + mod.levels += level + else: + if mod.levels != level: + mod.levels = level + + return + + # add a new modifier + try: + mod = obj.modifiers.new("Subsurf", 'SUBSURF') + mod.levels = level + except: + self.report({'WARNING'}, "Modifiers cannot be added to object: " + obj.name) + + for obj in context.selected_editable_objects: + set_object_subd(obj) + + return {'FINISHED'} + + +class ShapeTransfer(bpy.types.Operator): + '''Copy another selected objects active shape to this one by applying the relative offsets''' + + bl_idname = "object.shape_key_transfer" + bl_label = "Transfer Shape Key" + bl_options = {'REGISTER', 'UNDO'} + + mode = EnumProperty(items=( + ('OFFSET', "Offset", "Apply the relative positional offset"), + ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."), + ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")), + name="Transformation Mode", + description="Method to apply relative shape positions to the new shape", + default='OFFSET') + + use_clamp = BoolProperty(name="Clamp Offset", + description="Clamp the transformation to the distance each vertex moves in the original shape.", + default=False) + + def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False): + + def me_nos(verts): + return [v.normal.copy() for v in verts] + + def me_cos(verts): + return [v.co.copy() for v in verts] + + def ob_add_shape(ob, name): + me = ob.data + key = ob.shape_key_add(from_mix=False) + if len(me.shape_keys.keys) == 1: + key.name = "Basis" + key = ob.shape_key_add(from_mix=False) # we need a rest + key.name = name + ob.active_shape_key_index = len(me.shape_keys.keys) - 1 + ob.show_only_shape_key = True + + from mathutils.geometry import barycentric_transform + from mathutils import Vector + + if use_clamp and mode == 'OFFSET': + use_clamp = False + + me = ob_act.data + orig_key_name = ob_act.active_shape_key.name + + orig_shape_coords = me_cos(ob_act.active_shape_key.data) + + orig_normals = me_nos(me.vertices) + # orig_coords = me_cos(me.vertices) # the actual mverts location isnt as relyable as the base shape :S + orig_coords = me_cos(me.shape_keys.keys[0].data) + + for ob_other in objects: + me_other = ob_other.data + if len(me_other.vertices) != len(me.vertices): + self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name) + continue + + target_normals = me_nos(me_other.vertices) + if me_other.shape_keys: + target_coords = me_cos(me_other.shape_keys.keys[0].data) + else: + target_coords = me_cos(me_other.vertices) + + ob_add_shape(ob_other, orig_key_name) + + # editing the final coords, only list that stores wrapped coords + target_shape_coords = [v.co for v in ob_other.active_shape_key.data] + + median_coords = [[] for i in range(len(me.vertices))] + + # Method 1, edge + if mode == 'OFFSET': + for i, vert_cos in enumerate(median_coords): + vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i])) + + elif mode == 'RELATIVE_FACE': + for face in me.faces: + i1, i2, i3, i4 = face.vertices_raw + if i4 != 0: + pt = barycentric_transform(orig_shape_coords[i1], + orig_coords[i4], orig_coords[i1], orig_coords[i2], + target_coords[i4], target_coords[i1], target_coords[i2]) + median_coords[i1].append(pt) + + pt = barycentric_transform(orig_shape_coords[i2], + orig_coords[i1], orig_coords[i2], orig_coords[i3], + target_coords[i1], target_coords[i2], target_coords[i3]) + median_coords[i2].append(pt) + + pt = barycentric_transform(orig_shape_coords[i3], + orig_coords[i2], orig_coords[i3], orig_coords[i4], + target_coords[i2], target_coords[i3], target_coords[i4]) + median_coords[i3].append(pt) + + pt = barycentric_transform(orig_shape_coords[i4], + orig_coords[i3], orig_coords[i4], orig_coords[i1], + target_coords[i3], target_coords[i4], target_coords[i1]) + median_coords[i4].append(pt) + + else: + pt = barycentric_transform(orig_shape_coords[i1], + orig_coords[i3], orig_coords[i1], orig_coords[i2], + target_coords[i3], target_coords[i1], target_coords[i2]) + median_coords[i1].append(pt) + + pt = barycentric_transform(orig_shape_coords[i2], + orig_coords[i1], orig_coords[i2], orig_coords[i3], + target_coords[i1], target_coords[i2], target_coords[i3]) + median_coords[i2].append(pt) + + pt = barycentric_transform(orig_shape_coords[i3], + orig_coords[i2], orig_coords[i3], orig_coords[i1], + target_coords[i2], target_coords[i3], target_coords[i1]) + median_coords[i3].append(pt) + + elif mode == 'RELATIVE_EDGE': + for ed in me.edges: + i1, i2 = ed.vertices + v1, v2 = orig_coords[i1], orig_coords[i2] + edge_length = (v1 - v2).length + n1loc = v1 + orig_normals[i1] * edge_length + n2loc = v2 + orig_normals[i2] * edge_length + + # now get the target nloc's + v1_to, v2_to = target_coords[i1], target_coords[i2] + edlen_to = (v1_to - v2_to).length + n1loc_to = v1_to + target_normals[i1] * edlen_to + n2loc_to = v2_to + target_normals[i2] * edlen_to + + pt = barycentric_transform(orig_shape_coords[i1], + v2, v1, n1loc, + v2_to, v1_to, n1loc_to) + median_coords[i1].append(pt) + + pt = barycentric_transform(orig_shape_coords[i2], + v1, v2, n2loc, + v1_to, v2_to, n2loc_to) + median_coords[i2].append(pt) + + # apply the offsets to the new shape + from functools import reduce + VectorAdd = Vector.__add__ + + for i, vert_cos in enumerate(median_coords): + if vert_cos: + co = reduce(VectorAdd, vert_cos) / len(vert_cos) + + if use_clamp: + # clamp to the same movement as the original + # breaks copy between different scaled meshes. + len_from = (orig_shape_coords[i] - orig_coords[i]).length + ofs = co - target_coords[i] + ofs.length = len_from + co = target_coords[i] + ofs + + target_shape_coords[i][:] = co + + return {'FINISHED'} + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.mode != 'EDIT') + + def execute(self, context): + C = bpy.context + ob_act = C.active_object + objects = [ob for ob in C.selected_editable_objects if ob != ob_act] + + if 1: # swap from/to, means we cant copy to many at once. + if len(objects) != 1: + self.report({'ERROR'}, "Expected one other selected mesh object to copy from") + return {'CANCELLED'} + ob_act, objects = objects[0], [ob_act] + + if ob_act.type != 'MESH': + self.report({'ERROR'}, "Other object is not a mesh.") + return {'CANCELLED'} + + if ob_act.active_shape_key is None: + self.report({'ERROR'}, "Other object has no shape key") + return {'CANCELLED'} + return self._main(ob_act, objects, self.mode, self.use_clamp) + + +class JoinUVs(bpy.types.Operator): + '''Copy UV Layout to objects with matching geometry''' + bl_idname = "object.join_uvs" + bl_label = "Join as UVs" + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH') + + def _main(self, context): + import array + obj = context.active_object + mesh = obj.data + + is_editmode = (obj.mode == 'EDIT') + if is_editmode: + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + if not mesh.uv_textures: + self.report({'WARNING'}, "Object: %s, Mesh: '%s' has no UVs\n" % (obj.name, mesh.name)) + else: + len_faces = len(mesh.faces) + + uv_array = array.array('f', [0.0] * 8) * len_faces # seems to be the fastest way to create an array + mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array) + + objects = context.selected_editable_objects[:] + + for obj_other in objects: + if obj_other.type == 'MESH': + obj_other.data.tag = False + + for obj_other in objects: + if obj_other != obj and obj_other.type == 'MESH': + mesh_other = obj_other.data + if mesh_other != mesh: + if mesh_other.tag == False: + mesh_other.tag = True + + if len(mesh_other.faces) != len_faces: + 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)) + else: + uv_other = mesh_other.uv_textures.active + if not uv_other: + uv_other = mesh_other.uv_textures.new() # should return the texture it adds + + # finally do the copy + uv_other.data.foreach_set("uv_raw", uv_array) + + if is_editmode: + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + + def execute(self, context): + self._main(context) + return {'FINISHED'} + + +class MakeDupliFace(bpy.types.Operator): + '''Make linked objects into dupli-faces''' + bl_idname = "object.make_dupli_face" + bl_label = "Make Dupli-Face" + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH') + + def _main(self, context): + from mathutils import Vector + + SCALE_FAC = 0.01 + offset = 0.5 * SCALE_FAC + base_tri = Vector((-offset, -offset, 0.0)), Vector((offset, -offset, 0.0)), Vector((offset, offset, 0.0)), Vector((-offset, offset, 0.0)) + + def matrix_to_quat(matrix): + # scale = matrix.median_scale + trans = matrix.to_translation() + rot = matrix.to_3x3() # also contains scale + + return [(b * rot) + trans for b in base_tri] + scene = bpy.context.scene + linked = {} + for obj in bpy.context.selected_objects: + data = obj.data + if data: + linked.setdefault(data, []).append(obj) + + for data, objects in linked.items(): + face_verts = [axis for obj in objects for v in matrix_to_quat(obj.matrix_world) for axis in v] + faces = list(range(len(face_verts) // 3)) + + mesh = bpy.data.meshes.new(data.name + "_dupli") + + mesh.vertices.add(len(face_verts) // 3) + mesh.faces.add(len(face_verts) // 12) + + mesh.vertices.foreach_set("co", face_verts) + mesh.faces.foreach_set("vertices_raw", faces) + mesh.update() # generates edge data + + # pick an object to use + obj = objects[0] + + ob_new = bpy.data.objects.new(mesh.name, mesh) + base = scene.objects.link(ob_new) + base.layers[:] = obj.layers + + ob_inst = bpy.data.objects.new(data.name, data) + base = scene.objects.link(ob_inst) + base.layers[:] = obj.layers + + for obj in objects: + scene.objects.unlink(obj) + + ob_new.dupli_type = 'FACES' + ob_inst.parent = ob_new + ob_new.use_dupli_faces_scale = True + ob_new.dupli_faces_scale = 1.0 / SCALE_FAC + + def execute(self, context): + self._main(context) + return {'FINISHED'} + + +class IsolateTypeRender(bpy.types.Operator): + '''Hide unselected render objects of same type as active by setting the hide render flag''' + bl_idname = "object.isolate_type_render" + bl_label = "Restrict Render Unselected" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + act_type = context.object.type + + for obj in context.visible_objects: + + if obj.select: + obj.hide_render = False + else: + if obj.type == act_type: + obj.hide_render = True + + return {'FINISHED'} + + +class ClearAllRestrictRender(bpy.types.Operator): + '''Reveal all render objects by setting the hide render flag''' + bl_idname = "object.hide_render_clear_all" + bl_label = "Clear All Restrict Render" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + for obj in context.scene.objects: + obj.hide_render = False + return {'FINISHED'} |