# ##### 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 ##### # import bpy from bpy.types import Operator from bpy.props import ( BoolProperty, EnumProperty, IntProperty, StringProperty, ) class SelectPattern(Operator): """Select objects 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 " "'[abc]' unix style wildcards", maxlen=64, 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())) is_ebone = False is_pbone = False obj = context.object if obj and obj.mode == 'POSE': items = obj.data.bones if not self.extend: bpy.ops.pose.select_all(action='DESELECT') is_pbone = True elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT': items = obj.data.edit_bones if not self.extend: bpy.ops.armature.select_all(action='DESELECT') is_ebone = True else: items = context.visible_objects if not self.extend: bpy.ops.object.select_all(action='DESELECT') # Can be pose bones, edit bones or objects for item in items: if pattern_match(item.name, self.pattern): # hrmf, perhaps there should be a utility function for this. if is_ebone: item.select = True item.select_head = True item.select_tail = True if item.use_connect: item_parent = item.parent if item_parent is not None: item_parent.select_tail = True elif is_pbone: item.select = True else: item.select_set(True) 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") @classmethod def poll(cls, context): obj = context.object return (not obj) or (obj.mode == 'OBJECT') or (obj.type == 'ARMATURE') class SelectCamera(Operator): """Select the active camera""" bl_idname = "object.select_camera" bl_label = "Select Camera" bl_options = {'REGISTER', 'UNDO'} extend: BoolProperty( name="Extend", description="Extend the selection", default=False, ) def execute(self, context): scene = context.scene view_layer = context.view_layer view = context.space_data if view.type == 'VIEW_3D' and view.use_local_camera: camera = view.camera else: camera = scene.camera if camera is None: self.report({'WARNING'}, "No camera found") elif camera.name not in scene.objects: self.report({'WARNING'}, "Active camera is not in this scene") else: if not self.extend: bpy.ops.object.select_all(action='DESELECT') view_layer.objects.active = camera # camera.hide = False # XXX TODO where is this now? camera.select_set(True) return {'FINISHED'} return {'CANCELLED'} class SelectHierarchy(Operator): """Select object relative to the active object's 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): view_layer = context.view_layer 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] # don't 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_set(True) view_layer.objects.active = act_new return {'FINISHED'} return {'CANCELLED'} class SubdivisionSet(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", min=-100, max=100, soft_min=-6, soft_max=6, default=1, ) 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 if not relative and level < 0: self.level = level = 0 def set_object_subd(obj): for mod in obj.modifiers: if mod.type == 'MULTIRES': if not relative: if level > mod.total_levels: sub = level - mod.total_levels for _ in range(sub): bpy.ops.object.multires_subdivide(modifier="Multires") 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: if obj.mode == 'SCULPT': mod = obj.modifiers.new("Multires", 'MULTIRES') if level > 0: for _ in range(level): bpy.ops.object.multires_subdivide(modifier="Multires") else: mod = obj.modifiers.new("Subdivision", '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(Operator): """Copy the active shape key of another selected object to this one""" 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 relative position (using faces)", ), ('RELATIVE_EDGE', "Relative Edge", "Calculate relative position (using edges)", ), ), name="Transformation Mode", description="Relative shape positions to the new shape method", 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.key_blocks) == 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.key_blocks) - 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) # actual mesh vertex location isn't as reliable as the base shape :S # orig_coords = me_cos(me.vertices) orig_coords = me_cos(me.shape_keys.key_blocks[0].data) for ob_other in objects: if ob_other.type != 'MESH': self.report({'WARNING'}, ("Skipping '%s', " "not a mesh") % ob_other.name) continue 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.key_blocks[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 poly in me.polygons: idxs = poly.vertices[:] v_before = idxs[-2] v = idxs[-1] for v_after in idxs: pt = barycentric_transform(orig_shape_coords[v], orig_coords[v_before], orig_coords[v], orig_coords[v_after], target_coords[v_before], target_coords[v], target_coords[v_after], ) median_coords[v].append(pt) v_before = v v = v_after 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): ob_act = context.active_object objects = [ob for ob in context.selected_editable_objects if ob != ob_act] if 1: # swap from/to, means we can't 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(Operator): """Transfer UV Maps from active to selected objects """ \ """(needs matching geometry)""" bl_idname = "object.join_uvs" bl_label = "Transfer UV Maps" bl_options = {'REGISTER', 'UNDO'} @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_layers: self.report({'WARNING'}, "Object: %s, Mesh: '%s' has no UVs" % (obj.name, mesh.name)) else: nbr_loops = len(mesh.loops) # seems to be the fastest way to create an array uv_array = array.array('f', [0.0] * 2) * nbr_loops mesh.uv_layers.active.data.foreach_get("uv", 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 is False: mesh_other.tag = True if len(mesh_other.loops) != nbr_loops: self.report({'WARNING'}, "Object: %s, Mesh: " "'%s' has %d loops (for %d faces)," " expected %d\n" % (obj_other.name, mesh_other.name, len(mesh_other.loops), len(mesh_other.polygons), nbr_loops, ), ) else: uv_other = mesh_other.uv_layers.active if not uv_other: mesh_other.uv_layers.new() uv_other = mesh_other.uv_layers.active if not uv_other: self.report({'ERROR'}, "Could not add " "a new UV map tp object " "'%s' (Mesh '%s')\n" % (obj_other.name, mesh_other.name, ), ) # finally do the copy uv_other.data.foreach_set("uv", 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(Operator): """Convert objects into instanced faces""" bl_idname = "object.make_dupli_face" bl_label = "Make Instance Face" bl_options = {'REGISTER', 'UNDO'} @staticmethod def _main(context): from mathutils import Vector from collections import defaultdict 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_quad(matrix): # scale = matrix.median_scale trans = matrix.to_translation() rot = matrix.to_3x3() # also contains scale return [(rot @ b) + trans for b in base_tri] linked = defaultdict(list) for obj in context.selected_objects: if obj.type == 'MESH': linked[obj.data].append(obj) for data, objects in linked.items(): face_verts = [axis for obj in objects for v in matrix_to_quad(obj.matrix_world) for axis in v] nbr_verts = len(face_verts) // 3 nbr_faces = nbr_verts // 4 faces = list(range(nbr_verts)) mesh = bpy.data.meshes.new(data.name + "_dupli") mesh.vertices.add(nbr_verts) mesh.loops.add(nbr_faces * 4) # Safer than nbr_verts. mesh.polygons.add(nbr_faces) mesh.vertices.foreach_set("co", face_verts) mesh.loops.foreach_set("vertex_index", faces) mesh.polygons.foreach_set("loop_start", range(0, nbr_faces * 4, 4)) mesh.polygons.foreach_set("loop_total", (4,) * nbr_faces) mesh.update() # generates edge data ob_new = bpy.data.objects.new(mesh.name, mesh) context.collection.objects.link(ob_new) ob_inst = bpy.data.objects.new(data.name, data) context.collection.objects.link(ob_inst) ob_new.instance_type = 'FACES' ob_inst.parent = ob_new ob_new.use_instance_faces_scale = True ob_new.instance_faces_scale = 1.0 / SCALE_FAC ob_inst.select_set(True) ob_new.select_set(True) for obj in objects: for collection in obj.users_collection: collection.objects.unlink(obj) def execute(self, context): self._main(context) return {'FINISHED'} class IsolateTypeRender(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_get(): obj.hide_render = False else: if obj.type == act_type: obj.hide_render = True return {'FINISHED'} class ClearAllRestrictRender(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'} class TransformsToDeltas(Operator): """Convert normal object transforms to delta transforms, """ \ """any existing delta transforms will be included as well""" bl_idname = "object.transforms_to_deltas" bl_label = "Transforms to Deltas" bl_options = {'REGISTER', 'UNDO'} mode: EnumProperty( items=( ('ALL', "All Transforms", "Transfer location, rotation, and scale transforms"), ('LOC', "Location", "Transfer location transforms only"), ('ROT', "Rotation", "Transfer rotation transforms only"), ('SCALE', "Scale", "Transfer scale transforms only"), ), name="Mode", description="Which transforms to transfer", default='ALL', ) reset_values: BoolProperty( name="Reset Values", description=("Clear transform values after transferring to deltas"), default=True, ) @classmethod def poll(cls, context): obs = context.selected_editable_objects return (obs is not None) def execute(self, context): for obj in context.selected_editable_objects: if self.mode in {'ALL', 'LOC'}: self.transfer_location(obj) if self.mode in {'ALL', 'ROT'}: self.transfer_rotation(obj) if self.mode in {'ALL', 'SCALE'}: self.transfer_scale(obj) return {'FINISHED'} def transfer_location(self, obj): obj.delta_location += obj.location if self.reset_values: obj.location.zero() def transfer_rotation(self, obj): # TODO: add transforms together... if obj.rotation_mode == 'QUATERNION': obj.delta_rotation_quaternion += obj.rotation_quaternion if self.reset_values: obj.rotation_quaternion.identity() elif obj.rotation_mode == 'AXIS_ANGLE': pass # Unsupported else: delta = obj.delta_rotation_euler.copy() obj.delta_rotation_euler = obj.rotation_euler obj.delta_rotation_euler.rotate(delta) if self.reset_values: obj.rotation_euler.zero() def transfer_scale(self, obj): obj.delta_scale[0] *= obj.scale[0] obj.delta_scale[1] *= obj.scale[1] obj.delta_scale[2] *= obj.scale[2] if self.reset_values: obj.scale[:] = (1, 1, 1) class TransformsToDeltasAnim(Operator): """Convert object animation for normal transforms to delta transforms""" bl_idname = "object.anim_transforms_to_deltas" bl_label = "Animated Transforms to Deltas" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): obs = context.selected_editable_objects return (obs is not None) def execute(self, context): # map from standard transform paths to "new" transform paths STANDARD_TO_DELTA_PATHS = { "location": "delta_location", "rotation_euler": "delta_rotation_euler", "rotation_quaternion": "delta_rotation_quaternion", # "rotation_axis_angle" : "delta_rotation_axis_angle", "scale": "delta_scale" } DELTA_PATHS = STANDARD_TO_DELTA_PATHS.values() # try to apply on each selected object for obj in context.selected_editable_objects: adt = obj.animation_data if (adt is None) or (adt.action is None): self.report({'WARNING'}, "No animation data to convert on object: %r" % obj.name) continue # first pass over F-Curves: ensure that we don't have conflicting # transforms already (e.g. if this was applied already) [#29110] existingFCurves = {} for fcu in adt.action.fcurves: # get "delta" path - i.e. the final paths which may clash path = fcu.data_path if path in STANDARD_TO_DELTA_PATHS: # to be converted - conflicts may exist... dpath = STANDARD_TO_DELTA_PATHS[path] elif path in DELTA_PATHS: # already delta - check for conflicts... dpath = path else: # non-transform - ignore continue # a delta path like this for the same index shouldn't # exist already, otherwise we've got a conflict if dpath in existingFCurves: # ensure that this index hasn't occurred before if fcu.array_index in existingFCurves[dpath]: # conflict self.report({'ERROR'}, "Object '%r' already has '%r' F-Curve(s). " "Remove these before trying again" % (obj.name, dpath)) return {'CANCELLED'} else: # no conflict here existingFCurves[dpath] += [fcu.array_index] else: # no conflict yet existingFCurves[dpath] = [fcu.array_index] # if F-Curve uses standard transform path # just append "delta_" to this path for fcu in adt.action.fcurves: if fcu.data_path == "location": fcu.data_path = "delta_location" obj.location.zero() elif fcu.data_path == "rotation_euler": fcu.data_path = "delta_rotation_euler" obj.rotation_euler.zero() elif fcu.data_path == "rotation_quaternion": fcu.data_path = "delta_rotation_quaternion" obj.rotation_quaternion.identity() # XXX: currently not implemented # ~ elif fcu.data_path == "rotation_axis_angle": # ~ fcu.data_path = "delta_rotation_axis_angle" elif fcu.data_path == "scale": fcu.data_path = "delta_scale" obj.scale = 1.0, 1.0, 1.0 # hack: force animsys flush by changing frame, so that deltas get run context.scene.frame_set(context.scene.frame_current) return {'FINISHED'} class DupliOffsetFromCursor(Operator): """Set offset used for collection instances based on cursor position""" bl_idname = "object.instance_offset_from_cursor" bl_label = "Set Offset From Cursor" bl_options = {'INTERNAL', 'UNDO'} @classmethod def poll(cls, context): return (context.active_object is not None) def execute(self, context): scene = context.scene collection = context.collection collection.instance_offset = scene.cursor.location return {'FINISHED'} class LoadImageAsEmpty: bl_options = {'REGISTER', 'UNDO'} filepath: StringProperty( subtype='FILE_PATH' ) filter_image: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) filter_folder: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) view_align: BoolProperty( name="Align to view", default=True, ) @classmethod def poll(cls, context): return context.mode == 'OBJECT' def invoke(self, context, _event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def execute(self, context): scene = context.scene cursor = scene.cursor.location try: image = bpy.data.images.load(self.filepath, check_existing=True) except RuntimeError as ex: self.report({'ERROR'}, str(ex)) return {'CANCELLED'} bpy.ops.object.empty_add( 'INVOKE_REGION_WIN', type='IMAGE', location=cursor, align=('VIEW' if self.view_align else 'WORLD'), ) view_layer = context.view_layer obj = view_layer.objects.active obj.data = image obj.empty_display_size = 5.0 self.set_settings(context, obj) return {'FINISHED'} def set_settings(self, context, obj): pass class LoadBackgroundImage(LoadImageAsEmpty, Operator): """Add a reference image into the background behind objects""" bl_idname = "object.load_background_image" bl_label = "Load Background Image" def set_settings(self, context, obj): obj.empty_image_depth = 'BACK' obj.empty_image_side = 'FRONT' if context.space_data.type == 'VIEW_3D': if not context.space_data.region_3d.is_perspective: obj.show_empty_image_perspective = False class LoadReferenceImage(LoadImageAsEmpty, Operator): """Add a reference image into the scene between objects""" bl_idname = "object.load_reference_image" bl_label = "Load Reference Image" def set_settings(self, context, obj): pass class OBJECT_OT_assign_property_defaults(Operator): """Assign the current values of custom properties as their defaults, """ \ """for use as part of the rest pose state in NLA track mixing""" bl_idname = "object.assign_property_defaults" bl_label = "Assign Custom Property Values as Default" bl_options = {'UNDO', 'REGISTER'} process_data: BoolProperty(name="Process data properties", default=True) process_bones: BoolProperty(name="Process bone properties", default=True) @classmethod def poll(cls, context): obj = context.active_object return obj is not None and obj.library is None and obj.mode in {'POSE', 'OBJECT'} @staticmethod def assign_defaults(obj): from rna_prop_ui import rna_idprop_ui_prop_default_set rna_properties = {'_RNA_UI'} | {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime} for prop, value in obj.items(): if prop not in rna_properties: rna_idprop_ui_prop_default_set(obj, prop, value) def execute(self, context): obj = context.active_object self.assign_defaults(obj) if self.process_bones and obj.pose: for pbone in obj.pose.bones: self.assign_defaults(pbone) if self.process_data and obj.data and obj.data.library is None: self.assign_defaults(obj.data) if self.process_bones and isinstance(obj.data, bpy.types.Armature): for bone in obj.data.bones: self.assign_defaults(bone) return {'FINISHED'} classes = ( ClearAllRestrictRender, DupliOffsetFromCursor, IsolateTypeRender, JoinUVs, LoadBackgroundImage, LoadReferenceImage, MakeDupliFace, SelectCamera, SelectHierarchy, SelectPattern, ShapeTransfer, SubdivisionSet, TransformsToDeltas, TransformsToDeltasAnim, OBJECT_OT_assign_property_defaults, )