diff options
Diffstat (limited to 'release/scripts/startup/bl_operators')
25 files changed, 689 insertions, 226 deletions
diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index a696410ca1c..1e0dbe6925e 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -21,8 +21,7 @@ # support reloading sub-modules if "bpy" in locals(): from importlib import reload - for val in _modules_loaded: - reload(val) + _modules_loaded[:] = [reload(val) for val in _modules_loaded] del reload _modules = [ @@ -63,8 +62,15 @@ del _namespace def register(): - bpy.utils.register_module(__name__) + from bpy.utils import register_class + for mod in _modules_loaded: + for cls in mod.classes: + register_class(cls) def unregister(): - bpy.utils.unregister_module(__name__) + from bpy.utils import unregister_class + for mod in reversed(_modules_loaded): + for cls in reversed(mod.classes): + if cls.is_registered: + unregister_class(cls) diff --git a/release/scripts/startup/bl_operators/add_mesh_torus.py b/release/scripts/startup/bl_operators/add_mesh_torus.py index f12e7484e64..0e5acea94f9 100644 --- a/release/scripts/startup/bl_operators/add_mesh_torus.py +++ b/release/scripts/startup/bl_operators/add_mesh_torus.py @@ -32,25 +32,24 @@ from bpy_extras import object_utils def add_torus(major_rad, minor_rad, major_seg, minor_seg): from math import cos, sin, pi - from mathutils import Vector, Quaternion + from mathutils import Vector, Matrix - PI_2 = pi * 2.0 - z_axis = 0.0, 0.0, 1.0 + pi_2 = pi * 2.0 verts = [] faces = [] i1 = 0 tot_verts = major_seg * minor_seg for major_index in range(major_seg): - quat = Quaternion(z_axis, (major_index / major_seg) * PI_2) + matrix = Matrix.Rotation((major_index / major_seg) * pi_2, 3, 'Z') for minor_index in range(minor_seg): - angle = 2 * pi * minor_index / minor_seg + angle = pi_2 * minor_index / minor_seg - vec = quat * Vector((major_rad + (cos(angle) * minor_rad), - 0.0, - (sin(angle) * minor_rad), - )) + vec = matrix * Vector((major_rad + (cos(angle) * minor_rad), + 0.0, + sin(angle) * minor_rad, + )) verts.extend(vec[:]) @@ -58,7 +57,6 @@ def add_torus(major_rad, minor_rad, major_seg, minor_seg): i2 = (major_index) * minor_seg i3 = i1 + minor_seg i4 = i2 + minor_seg - else: i2 = i1 + 1 i3 = i1 + minor_seg @@ -71,11 +69,7 @@ def add_torus(major_rad, minor_rad, major_seg, minor_seg): if i4 >= tot_verts: i4 = i4 - tot_verts - # stupid eekadoodle - if i2: - faces.extend([i1, i3, i4, i2]) - else: - faces.extend([i2, i1, i3, i4]) + faces.extend([i1, i3, i4, i2]) i1 += 1 @@ -83,31 +77,56 @@ def add_torus(major_rad, minor_rad, major_seg, minor_seg): def add_uvs(mesh, minor_seg, major_seg): + from math import fmod + mesh.uv_textures.new() uv_data = mesh.uv_layers.active.data polygons = mesh.polygons u_step = 1.0 / major_seg v_step = 1.0 / minor_seg + + # Round UV's, needed when segments aren't divisible by 4. + u_init = 0.5 + fmod(0.5, u_step) + v_init = 0.5 + fmod(0.5, v_step) + + # Calculate wrapping value under 1.0 to prevent + # float precision errors wrapping at the wrong step. + u_wrap = 1.0 - (u_step / 2.0) + v_wrap = 1.0 - (v_step / 2.0) + vertex_index = 0 - u = 0.5 + u_prev = u_init + u_next = u_prev + u_step for major_index in range(major_seg): - v = 0.5 + v_prev = v_init + v_next = v_prev + v_step for minor_index in range(minor_seg): loops = polygons[vertex_index].loop_indices if minor_index == minor_seg - 1 and major_index == 0: - uv_data[loops[1]].uv = (u, v) - uv_data[loops[2]].uv = (u + u_step, v) - uv_data[loops[0]].uv = (u, v + v_step) - uv_data[loops[3]].uv = (u + u_step, v + v_step) + uv_data[loops[1]].uv = u_prev, v_prev + uv_data[loops[2]].uv = u_next, v_prev + uv_data[loops[0]].uv = u_prev, v_next + uv_data[loops[3]].uv = u_next, v_next else: - uv_data[loops[0]].uv = (u, v) - uv_data[loops[1]].uv = (u + u_step, v) - uv_data[loops[3]].uv = (u, v + v_step) - uv_data[loops[2]].uv = (u + u_step, v + v_step) - v = (v + v_step) % 1.0 + uv_data[loops[0]].uv = u_prev, v_prev + uv_data[loops[1]].uv = u_next, v_prev + uv_data[loops[3]].uv = u_prev, v_next + uv_data[loops[2]].uv = u_next, v_next + + if v_next > v_wrap: + v_prev = v_next - 1.0 + else: + v_prev = v_next + v_next = v_prev + v_step + vertex_index += 1 - u = (u + u_step) % 1.0 + + if u_next > u_wrap: + u_prev = u_next - 1.0 + else: + u_prev = u_next + u_next = u_prev + u_step class AddTorus(Operator, object_utils.AddObjectHelper): @@ -263,3 +282,8 @@ class AddTorus(Operator, object_utils.AddObjectHelper): object_utils.object_data_add(context, mesh, operator=self) return {'FINISHED'} + + +classes = ( + AddTorus, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/anim.py b/release/scripts/startup/bl_operators/anim.py index c20d591241c..0632f9bc3ca 100644 --- a/release/scripts/startup/bl_operators/anim.py +++ b/release/scripts/startup/bl_operators/anim.py @@ -28,15 +28,15 @@ if "bpy" in locals(): import bpy from bpy.types import Operator from bpy.props import ( - IntProperty, - BoolProperty, - EnumProperty, - StringProperty, - ) + IntProperty, + BoolProperty, + EnumProperty, + StringProperty, +) class ANIM_OT_keying_set_export(Operator): - "Export Keying Set to a python script" + """Export Keying Set to a python script""" bl_idname = "anim.keying_set_export" bl_label = "Export Keying Set..." @@ -102,43 +102,44 @@ class ANIM_OT_keying_set_export(Operator): if ksp.id in id_to_paths_cache: continue - """ - - idtype_list is used to get the list of id-datablocks from - bpy.data.* since this info isn't available elsewhere - - id.bl_rna.name gives a name suitable for UI, - with a capitalised first letter, but we need - the plural form that's all lower case - - special handling is needed for "nested" ID-blocks - (e.g. nodetree in Material) - """ + # - idtype_list is used to get the list of id-datablocks from + # bpy.data.* since this info isn't available elsewhere + # - id.bl_rna.name gives a name suitable for UI, + # with a capitalised first letter, but we need + # the plural form that's all lower case + # - special handling is needed for "nested" ID-blocks + # (e.g. nodetree in Material) if ksp.id.bl_rna.identifier.startswith("ShaderNodeTree"): # Find material or lamp using this node tree... id_bpy_path = "bpy.data.nodes[\"%s\"]" found = False - + for mat in bpy.data.materials: if mat.node_tree == ksp.id: id_bpy_path = "bpy.data.materials[\"%s\"].node_tree" % (mat.name) found = True - break; - + break + if not found: for lamp in bpy.data.lamps: if lamp.node_tree == ksp.id: id_bpy_path = "bpy.data.lamps[\"%s\"].node_tree" % (lamp.name) found = True - break; - + break + if not found: - self.report({'WARN'}, "Could not find material or lamp using Shader Node Tree - %s" % (ksp.id)) + self.report({'WARN'}, "Could not find material or lamp using Shader Node Tree - %s" % (ksp.id)) elif ksp.id.bl_rna.identifier.startswith("CompositorNodeTree"): # Find compositor nodetree using this node tree... for scene in bpy.data.scenes: if scene.node_tree == ksp.id: id_bpy_path = "bpy.data.scenes[\"%s\"].node_tree" % (scene.name) - break; + break else: - self.report({'WARN'}, "Could not find scene using Compositor Node Tree - %s" % (ksp.id)) + self.report({'WARN'}, "Could not find scene using Compositor Node Tree - %s" % (ksp.id)) + elif ksp.id.bl_rna.name == "Key": + # "keys" conflicts with a Python keyword, hence the simple solution won't work + id_bpy_path = "bpy.data.shape_keys[\"%s\"]" % (ksp.id.name) else: idtype_list = ksp.id.bl_rna.name.lower() + "s" id_bpy_path = "bpy.data.%s[\"%s\"]" % (idtype_list, ksp.id.name) @@ -197,7 +198,7 @@ class ANIM_OT_keying_set_export(Operator): class BakeAction(Operator): - """Bake object/pose loc/scale/rotation animation to a new action""" + """Bake all selected objects loc/scale/rotation animation to an action""" bl_idname = "nla.bake" bl_label = "Bake Action" bl_options = {'REGISTER', 'UNDO'} @@ -221,7 +222,7 @@ class BakeAction(Operator): default=1, ) only_selected = BoolProperty( - name="Only Selected", + name="Only Selected Bones", description="Only key selected bones (Pose baking only)", default=True, ) @@ -257,29 +258,27 @@ class BakeAction(Operator): ) def execute(self, context): - from bpy_extras import anim_utils + objects = context.selected_editable_objects + object_action_pairs = ( + [(obj, getattr(obj.animation_data, "action", None)) for obj in objects] + if self.use_current_action else + [(obj, None) for obj in objects] + ) - action = None - if self.use_current_action: - obj = context.object - if obj.animation_data: - action = obj.animation_data.action - - action = anim_utils.bake_action(self.frame_start, - self.frame_end, - frame_step=self.step, - only_selected=self.only_selected, - do_pose='POSE' in self.bake_types, - do_object='OBJECT' in self.bake_types, - do_visual_keying=self.visual_keying, - do_constraint_clear=self.clear_constraints, - do_parents_clear=self.clear_parents, - do_clean=True, - action=action, - ) - - if action is None: + actions = anim_utils.bake_action_objects( + object_action_pairs, + frames=range(self.frame_start, self.frame_end + 1, self.step), + only_selected=self.only_selected, + do_pose='POSE' in self.bake_types, + do_object='OBJECT' in self.bake_types, + do_visual_keying=self.visual_keying, + do_constraint_clear=self.clear_constraints, + do_parents_clear=self.clear_parents, + do_clean=True, + ) + + if not any(actions): self.report({'INFO'}, "Nothing to bake") return {'CANCELLED'} @@ -302,9 +301,11 @@ class ClearUselessActions(Operator): bl_label = "Clear Useless Actions" bl_options = {'REGISTER', 'UNDO'} - only_unused = BoolProperty(name="Only Unused", + only_unused = BoolProperty( + name="Only Unused", description="Only unused (Fake User only) actions get considered", - default=True) + default=True, + ) @classmethod def poll(cls, context): @@ -393,7 +394,7 @@ class UpdateAnimatedTransformConstraint(Operator): except: pass ret = (data, new_path) - #print(ret) + # print(ret) return ret @@ -412,3 +413,11 @@ class UpdateAnimatedTransformConstraint(Operator): text.from_string(log) self.report({'INFO'}, "Complete report available on '%s' text datablock" % text.name) return {'FINISHED'} + + +classes = ( + ANIM_OT_keying_set_export, + BakeAction, + ClearUselessActions, + UpdateAnimatedTransformConstraint, +) diff --git a/release/scripts/startup/bl_operators/clip.py b/release/scripts/startup/bl_operators/clip.py index 0c77ea2ab7e..f63b0495d02 100644 --- a/release/scripts/startup/bl_operators/clip.py +++ b/release/scripts/startup/bl_operators/clip.py @@ -21,7 +21,10 @@ import bpy import os from bpy.types import Operator from bpy.props import FloatProperty -from mathutils import Vector, Matrix +from mathutils import ( + Vector, + Matrix, +) def CLIP_spaces_walk(context, all_screens, tarea, tspace, callback, *args): @@ -207,7 +210,7 @@ class CLIP_OT_set_active_clip(bpy.types.Operator): @classmethod def poll(cls, context): space = context.space_data - return space.type == 'CLIP_EDITOR' + return space.type == 'CLIP_EDITOR' and space.clip def execute(self, context): clip = context.space_data.clip @@ -251,6 +254,11 @@ class CLIP_OT_track_to_empty(Operator): constraint.object = tracking_object.name constraint.camera = CLIP_camera_for_clip(context, clip) + @classmethod + def poll(cls, context): + space = context.space_data + return space.type == 'CLIP_EDITOR' and space.clip + def execute(self, context): sc = context.space_data clip = sc.clip @@ -779,8 +787,8 @@ class CLIP_OT_setup_tracking_scene(Operator): tree.links.new(mul_shadow.outputs["Image"], mul_image.inputs[2]) tree.links.new(rlayer_fg.outputs["Image"], vector_blur.inputs["Image"]) - tree.links.new(rlayer_fg.outputs["Z"], vector_blur.inputs["Z"]) - tree.links.new(rlayer_fg.outputs["Speed"], vector_blur.inputs["Speed"]) + tree.links.new(rlayer_fg.outputs["Depth"], vector_blur.inputs["Z"]) + tree.links.new(rlayer_fg.outputs["Vector"], vector_blur.inputs["Speed"]) tree.links.new(mul_image.outputs["Image"], alphaover.inputs[1]) tree.links.new(vector_blur.outputs["Image"], alphaover.inputs[2]) @@ -1071,3 +1079,17 @@ class CLIP_OT_track_settings_to_track(bpy.types.Operator): setattr(marker_selected, attr, getattr(marker, attr)) return {'FINISHED'} + + +classes = ( + CLIP_OT_bundles_to_mesh, + CLIP_OT_constraint_to_fcurve, + CLIP_OT_delete_proxy, + CLIP_OT_filter_tracks, + CLIP_OT_set_active_clip, + CLIP_OT_set_viewport_background, + CLIP_OT_setup_tracking_scene, + CLIP_OT_track_settings_as_default, + CLIP_OT_track_settings_to_track, + CLIP_OT_track_to_empty, +) diff --git a/release/scripts/startup/bl_operators/console.py b/release/scripts/startup/bl_operators/console.py index 8cfc977294a..fb36f80239e 100644 --- a/release/scripts/startup/bl_operators/console.py +++ b/release/scripts/startup/bl_operators/console.py @@ -159,3 +159,12 @@ class ConsoleLanguage(Operator): remove_duplicates=True) return {'FINISHED'} + + +classes = ( + ConsoleAutocomplete, + ConsoleBanner, + ConsoleCopyAsScript, + ConsoleExec, + ConsoleLanguage, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/file.py b/release/scripts/startup/bl_operators/file.py index 51e079164b6..d710b9af715 100644 --- a/release/scripts/startup/bl_operators/file.py +++ b/release/scripts/startup/bl_operators/file.py @@ -248,3 +248,8 @@ class WM_OT_previews_batch_clear(Operator): return {'FINISHED'} + +classes = ( + WM_OT_previews_batch_clear, + WM_OT_previews_batch_generate, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/freestyle.py b/release/scripts/startup/bl_operators/freestyle.py index edda92284d2..2e46160aeeb 100644 --- a/release/scripts/startup/bl_operators/freestyle.py +++ b/release/scripts/startup/bl_operators/freestyle.py @@ -16,13 +16,15 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + import bpy from bpy.props import ( - BoolProperty, - EnumProperty, - StringProperty, - ) + BoolProperty, + EnumProperty, + StringProperty, +) class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator): @@ -62,28 +64,66 @@ class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator): m = linestyle.alpha_modifiers[self.name] else: m = linestyle.thickness_modifiers[self.name] - # Find the source object + # Find the reference object if m.type == 'DISTANCE_FROM_CAMERA': - source = scene.camera + ref = scene.camera + matrix_to_camera = ref.matrix_world.inverted() elif m.type == 'DISTANCE_FROM_OBJECT': if m.target is None: self.report({'ERROR'}, "Target object not specified") return {'CANCELLED'} - source = m.target + ref = m.target + target_location = ref.location else: self.report({'ERROR'}, "Unexpected modifier type: " + m.type) return {'CANCELLED'} - # Find selected mesh objects - selection = [ob for ob in scene.objects if ob.select and ob.type == 'MESH' and ob.name != source.name] - if selection: - # Compute the min/max distance between selected mesh objects and the source + # Find selected vertices in editmesh + ob = bpy.context.active_object + if ob.type == 'MESH' and ob.mode == 'EDIT' and ob.name != ref.name: + bpy.ops.object.mode_set(mode='OBJECT') + selected_verts = [v for v in bpy.context.active_object.data.vertices if v.select] + bpy.ops.object.mode_set(mode='EDIT') + # Compute the min/max distance from the reference to mesh vertices min_dist = sys.float_info.max max_dist = -min_dist - for ob in selection: - for vert in ob.data.vertices: - dist = (ob.matrix_world * vert.co - source.location).length + if m.type == 'DISTANCE_FROM_CAMERA': + ob_to_cam = matrix_to_camera * ob.matrix_world + for vert in selected_verts: + # dist in the camera space + dist = (ob_to_cam * vert.co).length min_dist = min(dist, min_dist) max_dist = max(dist, max_dist) + elif m.type == 'DISTANCE_FROM_OBJECT': + for vert in selected_verts: + # dist in the world space + dist = (ob.matrix_world * vert.co - target_location).length + min_dist = min(dist, min_dist) + max_dist = max(dist, max_dist) + # Fill the Range Min/Max entries with the computed distances + m.range_min = min_dist + m.range_max = max_dist + return {'FINISHED'} + # Find selected mesh objects + selection = [ob for ob in scene.objects if ob.select and ob.type == 'MESH' and ob.name != ref.name] + if selection: + # Compute the min/max distance from the reference to mesh vertices + min_dist = sys.float_info.max + max_dist = -min_dist + if m.type == 'DISTANCE_FROM_CAMERA': + for ob in selection: + ob_to_cam = matrix_to_camera * ob.matrix_world + for vert in ob.data.vertices: + # dist in the camera space + dist = (ob_to_cam * vert.co).length + min_dist = min(dist, min_dist) + max_dist = max(dist, max_dist) + elif m.type == 'DISTANCE_FROM_OBJECT': + for ob in selection: + for vert in ob.data.vertices: + # dist in the world space + dist = (ob.matrix_world * vert.co - target_location).length + min_dist = min(dist, min_dist) + max_dist = max(dist, max_dist) # Fill the Range Min/Max entries with the computed distances m.range_min = min_dist m.range_max = max_dist @@ -180,3 +220,11 @@ class SCENE_OT_freestyle_module_open(bpy.types.Operator): text = bpy.data.texts.load(self.filepath, self.make_internal) self.freestyle_module.script = text return {'FINISHED'} + + +classes = ( + SCENE_OT_freestyle_add_edge_marks_to_keying_set, + SCENE_OT_freestyle_add_face_marks_to_keying_set, + SCENE_OT_freestyle_fill_range_by_selection, + SCENE_OT_freestyle_module_open, +) diff --git a/release/scripts/startup/bl_operators/image.py b/release/scripts/startup/bl_operators/image.py index f00f5d97c5e..6a538f0ae33 100644 --- a/release/scripts/startup/bl_operators/image.py +++ b/release/scripts/startup/bl_operators/image.py @@ -82,8 +82,8 @@ class EditExternally(Operator): import traceback traceback.print_exc() self.report({'ERROR'}, - "Image editor not found, " - "please specify in User Preferences > File") + "Image editor could not be launched, please ensure that " + "the path in User Preferences > File is valid, and Blender has rights to launch it") return {'CANCELLED'} @@ -242,3 +242,11 @@ class ProjectApply(Operator): bpy.ops.paint.project_image(image=image_name) return {'FINISHED'} + + +classes = ( + EditExternally, + ProjectApply, + ProjectEdit, + SaveDirty, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/mask.py b/release/scripts/startup/bl_operators/mask.py index aa984659430..78a4bd9af27 100644 --- a/release/scripts/startup/bl_operators/mask.py +++ b/release/scripts/startup/bl_operators/mask.py @@ -31,3 +31,8 @@ class MASK_MT_add(Menu): layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("mask.primitive_circle_add", text="Circle", icon='MESH_CIRCLE') layout.operator("mask.primitive_square_add", text="Square", icon='MESH_PLANE') + + +classes = ( + MASK_MT_add, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py index 58eab5436e6..4edefd7bf9b 100644 --- a/release/scripts/startup/bl_operators/mesh.py +++ b/release/scripts/startup/bl_operators/mesh.py @@ -21,7 +21,10 @@ import bpy from bpy.types import Operator -from bpy.props import EnumProperty, IntProperty +from bpy.props import ( + EnumProperty, + IntProperty, +) class MeshMirrorUV(Operator): @@ -248,3 +251,10 @@ class MehsSetNormalsFromFaces(Operator): return {'FINISHED'} + +classes = ( + MehsSetNormalsFromFaces, + MeshMirrorUV, + MeshSelectNext, + MeshSelectPrev, +) diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py index acff259e503..40876e2b069 100644 --- a/release/scripts/startup/bl_operators/node.py +++ b/release/scripts/startup/bl_operators/node.py @@ -21,16 +21,16 @@ import bpy import nodeitems_utils from bpy.types import ( - Operator, - PropertyGroup, - ) + Operator, + PropertyGroup, +) from bpy.props import ( - BoolProperty, - CollectionProperty, - EnumProperty, - IntProperty, - StringProperty, - ) + BoolProperty, + CollectionProperty, + EnumProperty, + IntProperty, + StringProperty, +) class NodeSetting(PropertyGroup): @@ -295,3 +295,14 @@ class NODE_OT_tree_path_parent(Operator): space.path.pop() return {'FINISHED'} + + +classes = ( + NodeSetting, + + NODE_OT_add_and_link_node, + NODE_OT_add_node, + NODE_OT_add_search, + NODE_OT_collapse_hide_unused_toggle, + NODE_OT_tree_path_parent, +) diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py index 6356da216b1..307764205e6 100644 --- a/release/scripts/startup/bl_operators/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -21,12 +21,12 @@ import bpy from bpy.types import Operator from bpy.props import ( - StringProperty, - BoolProperty, - EnumProperty, - IntProperty, - FloatProperty, - ) + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, + StringProperty, +) class SelectPattern(Operator): @@ -864,7 +864,7 @@ class DupliOffsetFromCursor(Operator): """Set offset used for DupliGroup based on cursor position""" bl_idname = "object.dupli_offset_from_cursor" bl_label = "Set Offset From Cursor" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'INTERNAL', 'UNDO'} @classmethod def poll(cls, context): @@ -1034,3 +1034,22 @@ class LodGenerate(Operator): scene.objects.active = ob return {'FINISHED'} + + +classes = ( + ClearAllRestrictRender, + DupliOffsetFromCursor, + IsolateTypeRender, + JoinUVs, + LodByName, + LodClearAll, + LodGenerate, + MakeDupliFace, + SelectCamera, + SelectHierarchy, + SelectPattern, + ShapeTransfer, + SubdivisionSet, + TransformsToDeltas, + TransformsToDeltasAnim, +) diff --git a/release/scripts/startup/bl_operators/object_align.py b/release/scripts/startup/bl_operators/object_align.py index a6ee16e6b71..b7d3866989d 100644 --- a/release/scripts/startup/bl_operators/object_align.py +++ b/release/scripts/startup/bl_operators/object_align.py @@ -26,13 +26,14 @@ from mathutils import Vector def GlobalBB_LQ(bb_world): # Initialize the variables with the 8th vertex - left, right, front, back, down, up = (bb_world[7][0], - bb_world[7][0], - bb_world[7][1], - bb_world[7][1], - bb_world[7][2], - bb_world[7][2], - ) + left, right, front, back, down, up = ( + bb_world[7][0], + bb_world[7][0], + bb_world[7][1], + bb_world[7][1], + bb_world[7][2], + bb_world[7][2], + ) # Test against the other 7 verts for i in range(7): @@ -130,6 +131,11 @@ def align_objects(context, cursor = (space if space and space.type == 'VIEW_3D' else scene).cursor_location + # We are accessing runtime data such as evaluated bounding box, so we need to + # be sure it is properly updated and valid (bounding box might be lost on operator + # redo). + scene.update() + Left_Front_Up_SEL = [0.0, 0.0, 0.0] Right_Back_Down_SEL = [0.0, 0.0, 0.0] @@ -398,16 +404,23 @@ class AlignObjects(Operator): def execute(self, context): align_axis = self.align_axis - ret = align_objects(context, - 'X' in align_axis, - 'Y' in align_axis, - 'Z' in align_axis, - self.align_mode, - self.relative_to, - self.bb_quality) + ret = align_objects( + context, + 'X' in align_axis, + 'Y' in align_axis, + 'Z' in align_axis, + self.align_mode, + self.relative_to, + self.bb_quality, + ) if not ret: self.report({'WARNING'}, "No objects with bound-box selected") return {'CANCELLED'} else: return {'FINISHED'} + + +classes = ( + AlignObjects, +) diff --git a/release/scripts/startup/bl_operators/object_quick_effects.py b/release/scripts/startup/bl_operators/object_quick_effects.py index 414855c7e35..16f29c77bb9 100644 --- a/release/scripts/startup/bl_operators/object_quick_effects.py +++ b/release/scripts/startup/bl_operators/object_quick_effects.py @@ -22,12 +22,12 @@ from mathutils import Vector import bpy from bpy.types import Operator from bpy.props import ( - BoolProperty, - EnumProperty, - IntProperty, - FloatProperty, - FloatVectorProperty, - ) + BoolProperty, + EnumProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, +) def object_ensure_material(obj, mat_name): @@ -210,8 +210,9 @@ class QuickExplode(Operator): settings = obj.particle_systems[-1].settings settings.count = self.amount - settings.frame_start = self.frame_start + # first set frame end, to prevent frame start clamping settings.frame_end = self.frame_end - self.frame_duration + settings.frame_start = self.frame_start settings.lifetime = self.frame_duration settings.normal_factor = self.velocity settings.render_type = 'NONE' @@ -317,6 +318,10 @@ class QuickSmoke(Operator): ) def execute(self, context): + if not bpy.app.build_options.mod_smoke: + self.report({'ERROR'}, "Built without Smoke modifier support") + return {'CANCELLED'} + fake_context = context.copy() mesh_objects = [obj for obj in context.selected_objects if obj.type == 'MESH'] @@ -562,6 +567,10 @@ class QuickFluid(Operator): ) def execute(self, context): + if not bpy.app.build_options.mod_fluid: + self.report({'ERROR'}, "Built without Fluid modifier support") + return {'CANCELLED'} + fake_context = context.copy() mesh_objects = [obj for obj in context.selected_objects if (obj.type == 'MESH' and 0.0 not in obj.dimensions)] @@ -636,3 +645,11 @@ class QuickFluid(Operator): bpy.ops.fluid.bake('INVOKE_DEFAULT') return {'FINISHED'} + + +classes = ( + QuickExplode, + QuickFluid, + QuickFur, + QuickSmoke, +) diff --git a/release/scripts/startup/bl_operators/object_randomize_transform.py b/release/scripts/startup/bl_operators/object_randomize_transform.py index 38110328603..f856b85844e 100644 --- a/release/scripts/startup/bl_operators/object_randomize_transform.py +++ b/release/scripts/startup/bl_operators/object_randomize_transform.py @@ -185,3 +185,8 @@ class RandomizeLocRotSize(Operator): loc, rot, scale, scale_even, scale_min) return {'FINISHED'} + + +classes = ( + RandomizeLocRotSize, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/presets.py b/release/scripts/startup/bl_operators/presets.py index e01e509b292..fdacf24f6e0 100644 --- a/release/scripts/startup/bl_operators/presets.py +++ b/release/scripts/startup/bl_operators/presets.py @@ -135,7 +135,7 @@ class AddPresetBase: file_preset.write("%s = %r\n" % (rna_path_step, value)) - file_preset = open(filepath, 'w') + file_preset = open(filepath, 'w', encoding="utf-8") file_preset.write("import bpy\n") if hasattr(self, "preset_defines"): @@ -680,3 +680,26 @@ class AddPresetUnitsLength(AddPresetBase, Operator): ] preset_subdir = "units_length" + + +classes = ( + AddPresetCamera, + AddPresetCloth, + AddPresetFluid, + AddPresetHairDynamics, + AddPresetInteraction, + AddPresetInterfaceTheme, + AddPresetKeyconfig, + AddPresetNodeColor, + AddPresetOperator, + AddPresetRender, + AddPresetSSS, + AddPresetSafeAreas, + AddPresetSunSky, + AddPresetTrackingCamera, + AddPresetTrackingSettings, + AddPresetTrackingTrackColor, + AddPresetUnitsLength, + ExecutePreset, + WM_MT_operator_presets, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/rigidbody.py b/release/scripts/startup/bl_operators/rigidbody.py index 750a5b0bf0f..6792d525683 100644 --- a/release/scripts/startup/bl_operators/rigidbody.py +++ b/release/scripts/startup/bl_operators/rigidbody.py @@ -20,8 +20,10 @@ import bpy from bpy.types import Operator -from bpy.props import IntProperty -from bpy.props import EnumProperty +from bpy.props import ( + EnumProperty, + IntProperty, +) class CopyRigidbodySettings(Operator): @@ -309,3 +311,10 @@ class ConnectRigidBodies(Operator): else: self.report({'WARNING'}, "No other objects selected") return {'CANCELLED'} + + +classes = ( + BakeToKeyframes, + ConnectRigidBodies, + CopyRigidbodySettings, +) diff --git a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py index a5565699364..f4d6c7065a9 100644 --- a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py +++ b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py @@ -180,3 +180,8 @@ class PlayRenderedAnim(Operator): return {'CANCELLED'} return {'FINISHED'} + + +classes = ( + PlayRenderedAnim, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/sequencer.py b/release/scripts/startup/bl_operators/sequencer.py index 31ca4249a9d..7209b6b478f 100644 --- a/release/scripts/startup/bl_operators/sequencer.py +++ b/release/scripts/startup/bl_operators/sequencer.py @@ -134,3 +134,10 @@ class SequencerDeinterlaceSelectedMovies(Operator): s.use_deinterlace = True return {'FINISHED'} + + +classes = ( + SequencerCrossfadeSounds, + SequencerCutMulticam, + SequencerDeinterlaceSelectedMovies, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/uvcalc_follow_active.py b/release/scripts/startup/bl_operators/uvcalc_follow_active.py index d1ac9e0b586..25ee5cafe81 100644 --- a/release/scripts/startup/bl_operators/uvcalc_follow_active.py +++ b/release/scripts/startup/bl_operators/uvcalc_follow_active.py @@ -248,3 +248,8 @@ class FollowActiveQuads(Operator): def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) + + +classes = ( + FollowActiveQuads, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/uvcalc_lightmap.py b/release/scripts/startup/bl_operators/uvcalc_lightmap.py index 3b095c883a3..8ee29d15d1b 100644 --- a/release/scripts/startup/bl_operators/uvcalc_lightmap.py +++ b/release/scripts/startup/bl_operators/uvcalc_lightmap.py @@ -668,3 +668,8 @@ class LightMapPack(Operator): def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) + + +classes = ( + LightMapPack, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/uvcalc_smart_project.py b/release/scripts/startup/bl_operators/uvcalc_smart_project.py index 52e7b0e0ae4..cc590ac9502 100644 --- a/release/scripts/startup/bl_operators/uvcalc_smart_project.py +++ b/release/scripts/startup/bl_operators/uvcalc_smart_project.py @@ -18,7 +18,11 @@ # TODO <pep8 compliant> -from mathutils import Matrix, Vector, geometry +from mathutils import ( + Matrix, + Vector, + geometry, +) import bpy from bpy.types import Operator @@ -719,7 +723,7 @@ def main(context, global USER_FILL_HOLES_QUALITY global USER_STRETCH_ASPECT global USER_ISLAND_MARGIN - + from math import cos import time @@ -743,7 +747,7 @@ def main(context, USER_FILL_HOLES = 0 USER_FILL_HOLES_QUALITY = 50 # Only for hole filling. USER_VIEW_INIT = 0 # Only for hole filling. - + is_editmode = (context.active_object.mode == 'EDIT') if is_editmode: obList = [ob for ob in [context.active_object] if ob and ob.type == 'MESH'] @@ -1100,3 +1104,8 @@ class SmartProject(Operator): def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) + + +classes = ( + SmartProject, +) diff --git a/release/scripts/startup/bl_operators/vertexpaint_dirt.py b/release/scripts/startup/bl_operators/vertexpaint_dirt.py index 892e1822d68..c006e8e6e92 100644 --- a/release/scripts/startup/bl_operators/vertexpaint_dirt.py +++ b/release/scripts/startup/bl_operators/vertexpaint_dirt.py @@ -182,3 +182,8 @@ class VertexPaintDirt(Operator): ret = applyVertexDirt(mesh, self.blur_iterations, self.blur_strength, self.dirt_angle, self.clean_angle, self.dirt_only) return ret + + +classes = ( + VertexPaintDirt, +)
\ No newline at end of file diff --git a/release/scripts/startup/bl_operators/view3d.py b/release/scripts/startup/bl_operators/view3d.py index df4a93bb87f..18f91110053 100644 --- a/release/scripts/startup/bl_operators/view3d.py +++ b/release/scripts/startup/bl_operators/view3d.py @@ -24,7 +24,7 @@ from bpy.props import BoolProperty class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator): - "Extrude individual elements and move" + """Extrude individual elements and move""" bl_label = "Extrude Individual and Move" bl_idname = "view3d.edit_mesh_extrude_individual_move" @@ -62,7 +62,7 @@ class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator): class VIEW3D_OT_edit_mesh_extrude_move(Operator): - "Extrude and move along normals" + """Extrude and move along normals""" bl_label = "Extrude and Move on Normals" bl_idname = "view3d.edit_mesh_extrude_move_normal" @@ -111,7 +111,7 @@ class VIEW3D_OT_edit_mesh_extrude_move(Operator): class VIEW3D_OT_edit_mesh_extrude_shrink_fatten(Operator): - "Extrude and move along individual normals" + """Extrude and move along individual normals""" bl_label = "Extrude and Move on Individual Normals" bl_idname = "view3d.edit_mesh_extrude_move_shrink_fatten" @@ -128,7 +128,7 @@ class VIEW3D_OT_edit_mesh_extrude_shrink_fatten(Operator): class VIEW3D_OT_select_or_deselect_all(Operator): - "Select element under the mouse, deselect everything is there's nothing under the mouse" + """Select element under the mouse, deselect everything is there's nothing under the mouse""" bl_label = "Select or Deselect All" bl_idname = "view3d.select_or_deselect_all" bl_options = {'UNDO'} @@ -213,3 +213,11 @@ class VIEW3D_OT_select_or_deselect_all(Operator): enumerate=self.enumerate, object=self.object, location=(x, y)) + + +classes = ( + VIEW3D_OT_edit_mesh_extrude_individual_move, + VIEW3D_OT_edit_mesh_extrude_move, + VIEW3D_OT_edit_mesh_extrude_shrink_fatten, + VIEW3D_OT_select_or_deselect_all, +) diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 68a25acc2db..20586b727d5 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -21,12 +21,12 @@ import bpy from bpy.types import Operator from bpy.props import ( - StringProperty, - BoolProperty, - IntProperty, - FloatProperty, - EnumProperty, - ) + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + EnumProperty, +) from bpy.app.translations import pgettext_tip as tip_ @@ -130,6 +130,20 @@ def execute_context_assign(self, context): return operator_path_undo_return(context, data_path) +def module_filesystem_remove(path_base, module_name): + import os + module_name = os.path.splitext(module_name)[0] + for f in os.listdir(path_base): + f_base = os.path.splitext(f)[0] + if f_base == module_name: + f_full = os.path.join(path_base, f) + + if os.path.isdir(f_full): + os.rmdir(f_full) + else: + os.remove(f_full) + + class BRUSH_OT_active_index_set(Operator): """Set active sculpt/paint brush from it's number""" bl_idname = "brush.active_index_set" @@ -656,11 +670,6 @@ doc_id = StringProperty( options={'HIDDEN'}, ) -doc_new = StringProperty( - name="Edit Description", - maxlen=1024, - ) - data_path_iter = StringProperty( description="The data path relative to the context, must point to an iterable") @@ -836,7 +845,7 @@ class WM_OT_context_modal_mouse(Operator): class WM_OT_url_open(Operator): - "Open a website in the web-browser" + """Open a website in the web-browser""" bl_idname = "wm.url_open" bl_label = "" bl_options = {'INTERNAL'} @@ -853,7 +862,7 @@ class WM_OT_url_open(Operator): class WM_OT_path_open(Operator): - "Open a path in a file browser" + """Open a path in a file browser""" bl_idname = "wm.path_open" bl_label = "" bl_options = {'INTERNAL'} @@ -912,7 +921,10 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""): # an operator (common case - just button referencing an op) if hasattr(bpy.types, class_name.upper() + "_OT_" + class_prop): if do_url: - url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" % (url_prefix, class_name, class_name, class_prop)) + url = ( + "%s/bpy.ops.%s.html#bpy.ops.%s.%s" % + (url_prefix, class_name, class_name, class_prop) + ) else: rna = "bpy.ops.%s.%s" % (class_name, class_prop) else: @@ -927,7 +939,10 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""): class_name, class_prop = class_name.split("_OT_", 1) class_name = class_name.lower() if do_url: - url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" % (url_prefix, class_name, class_name, class_prop)) + url = ( + "%s/bpy.ops.%s.html#bpy.ops.%s.%s" % + (url_prefix, class_name, class_name, class_prop) + ) else: rna = "bpy.ops.%s.%s" % (class_name, class_prop) else: @@ -943,9 +958,12 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""): rna_parent = rna_parent.base if do_url: - url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" % (url_prefix, class_name, class_name, class_prop)) + url = ( + "%s/bpy.types.%s.html#bpy.types.%s.%s" % + (url_prefix, class_name, class_name, class_prop) + ) else: - rna = ("bpy.types.%s.%s" % (class_name, class_prop)) + rna = "bpy.types.%s.%s" % (class_name, class_prop) else: # We assume this is custom property, only try to generate generic url/rna_id... if do_url: @@ -1013,11 +1031,9 @@ class WM_OT_doc_view(Operator): doc_id = doc_id if bpy.app.version_cycle == "release": - _prefix = ("https://www.blender.org/api/blender_python_api_%s%s_release" % - ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char)) + _prefix = ("https://docs.blender.org/api/blender_python_api_current") else: - _prefix = ("https://www.blender.org/api/blender_python_api_%s" % - "_".join(str(v) for v in bpy.app.version)) + _prefix = ("https://docs.blender.org/api/blender_python_api_master") def execute(self, context): url = _wm_doc_get_id(self.doc_id, do_url=True, url_prefix=self._prefix) @@ -1094,10 +1110,10 @@ class WM_OT_properties_edit(Operator): def execute(self, context): from rna_prop_ui import ( - rna_idprop_ui_prop_get, - rna_idprop_ui_prop_clear, - rna_idprop_ui_prop_update, - ) + rna_idprop_ui_prop_get, + rna_idprop_ui_prop_clear, + rna_idprop_ui_prop_update, + ) data_path = self.data_path value = self.value @@ -1274,9 +1290,9 @@ class WM_OT_properties_add(Operator): def execute(self, context): from rna_prop_ui import ( - rna_idprop_ui_prop_get, - rna_idprop_ui_prop_update, - ) + rna_idprop_ui_prop_get, + rna_idprop_ui_prop_update, + ) data_path = self.data_path item = eval("context.%s" % data_path) @@ -1291,10 +1307,10 @@ class WM_OT_properties_add(Operator): return prop_new - prop = unique_name( - {*item.keys(), - *type(item).bl_rna.properties.keys(), - }) + prop = unique_name({ + *item.keys(), + *type(item).bl_rna.properties.keys(), + }) item[prop] = 1.0 rna_idprop_ui_prop_update(item, prop) @@ -1308,7 +1324,7 @@ class WM_OT_properties_add(Operator): class WM_OT_properties_context_change(Operator): - "Jump to a different tab inside the properties editor" + """Jump to a different tab inside the properties editor""" bl_idname = "wm.properties_context_change" bl_label = "" bl_options = {'INTERNAL'} @@ -1334,9 +1350,9 @@ class WM_OT_properties_remove(Operator): def execute(self, context): from rna_prop_ui import ( - rna_idprop_ui_prop_clear, - rna_idprop_ui_prop_update, - ) + rna_idprop_ui_prop_clear, + rna_idprop_ui_prop_update, + ) data_path = self.data_path item = eval("context.%s" % data_path) prop = self.property @@ -1374,7 +1390,10 @@ class WM_OT_appconfig_default(Operator): filepath = os.path.join(bpy.utils.preset_paths("interaction")[0], "blender.py") if os.path.exists(filepath): - bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets") + bpy.ops.script.execute_preset( + filepath=filepath, + menu_idname="USERPREF_MT_interaction_presets", + ) return {'FINISHED'} @@ -1394,7 +1413,10 @@ class WM_OT_appconfig_activate(Operator): filepath = self.filepath.replace("keyconfig", "interaction") if os.path.exists(filepath): - bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets") + bpy.ops.script.execute_preset( + filepath=filepath, + menu_idname="USERPREF_MT_interaction_presets", + ) return {'FINISHED'} @@ -1499,7 +1521,7 @@ class WM_OT_blenderplayer_start(Operator): "-g", "show_profile", "=", "%d" % gs.show_framerate_profile, "-g", "show_properties", "=", "%d" % gs.show_debug_properties, "-g", "ignore_deprecation_warnings", "=", "%d" % (not gs.use_deprecation_warnings), - ]) + ]) # finish the call with the path to the blend file args.append(filepath) @@ -1510,7 +1532,7 @@ class WM_OT_blenderplayer_start(Operator): class WM_OT_keyconfig_test(Operator): - "Test key-config for conflicts" + """Test key-config for conflicts""" bl_idname = "wm.keyconfig_test" bl_label = "Test Key Configuration for Conflicts" @@ -1527,7 +1549,7 @@ class WM_OT_keyconfig_test(Operator): class WM_OT_keyconfig_import(Operator): - "Import key configuration from a python script" + """Import key configuration from a python script""" bl_idname = "wm.keyconfig_import" bl_label = "Import Key Configuration..." @@ -1594,7 +1616,7 @@ class WM_OT_keyconfig_import(Operator): class WM_OT_keyconfig_export(Operator): - "Export key configuration to a python script" + """Export key configuration to a python script""" bl_idname = "wm.keyconfig_export" bl_label = "Export Key Configuration..." @@ -1629,10 +1651,11 @@ class WM_OT_keyconfig_export(Operator): wm = context.window_manager - keyconfig_utils.keyconfig_export(wm, - wm.keyconfigs.active, - self.filepath, - ) + keyconfig_utils.keyconfig_export( + wm, + wm.keyconfigs.active, + self.filepath, + ) return {'FINISHED'} @@ -1643,7 +1666,7 @@ class WM_OT_keyconfig_export(Operator): class WM_OT_keymap_restore(Operator): - "Restore key map(s)" + """Restore key map(s)""" bl_idname = "wm.keymap_restore" bl_label = "Restore Key Map(s)" @@ -1666,7 +1689,7 @@ class WM_OT_keymap_restore(Operator): class WM_OT_keyitem_restore(Operator): - "Restore key map item" + """Restore key map item""" bl_idname = "wm.keyitem_restore" bl_label = "Restore Key Map Item" @@ -1691,7 +1714,7 @@ class WM_OT_keyitem_restore(Operator): class WM_OT_keyitem_add(Operator): - "Add key map item" + """Add key map item""" bl_idname = "wm.keyitem_add" bl_label = "Add Key Map Item" @@ -1713,7 +1736,7 @@ class WM_OT_keyitem_add(Operator): class WM_OT_keyitem_remove(Operator): - "Remove key map item" + """Remove key map item""" bl_idname = "wm.keyitem_remove" bl_label = "Remove Key Map Item" @@ -1734,7 +1757,7 @@ class WM_OT_keyitem_remove(Operator): class WM_OT_keyconfig_remove(Operator): - "Remove key config" + """Remove key config""" bl_idname = "wm.keyconfig_remove" bl_label = "Remove Key Config" @@ -1752,6 +1775,7 @@ class WM_OT_keyconfig_remove(Operator): class WM_OT_operator_cheat_sheet(Operator): + """List all the Operators in a text-block, useful for scripting""" bl_idname = "wm.operator_cheat_sheet" bl_label = "Operator Cheat Sheet" @@ -1780,7 +1804,7 @@ class WM_OT_operator_cheat_sheet(Operator): # Add-on Operators class WM_OT_addon_enable(Operator): - "Enable an add-on" + """Enable an add-on""" bl_idname = "wm.addon_enable" bl_label = "Enable Add-on" @@ -1824,7 +1848,7 @@ class WM_OT_addon_enable(Operator): class WM_OT_addon_disable(Operator): - "Disable an add-on" + """Disable an add-on""" bl_idname = "wm.addon_disable" bl_label = "Disable Add-on" @@ -1853,7 +1877,7 @@ class WM_OT_addon_disable(Operator): class WM_OT_theme_install(Operator): - "Load and apply a Blender XML theme file" + """Load and apply a Blender XML theme file""" bl_idname = "wm.theme_install" bl_label = "Install Theme..." @@ -1897,7 +1921,10 @@ class WM_OT_theme_install(Operator): try: shutil.copyfile(xmlfile, path_dest) - bpy.ops.script.execute_preset(filepath=path_dest, menu_idname="USERPREF_MT_interface_theme_presets") + bpy.ops.script.execute_preset( + filepath=path_dest, + menu_idname="USERPREF_MT_interface_theme_presets", + ) except: traceback.print_exc() @@ -1912,7 +1939,7 @@ class WM_OT_theme_install(Operator): class WM_OT_addon_refresh(Operator): - "Scan add-on directories for new modules" + """Scan add-on directories for new modules""" bl_idname = "wm.addon_refresh" bl_label = "Refresh" @@ -1924,10 +1951,12 @@ class WM_OT_addon_refresh(Operator): return {'FINISHED'} +# Note: shares some logic with WM_OT_app_template_install +# but not enough to de-duplicate. Fixed here may apply to both. class WM_OT_addon_install(Operator): - "Install an add-on" + """Install an add-on""" bl_idname = "wm.addon_install" - bl_label = "Install from File..." + bl_label = "Install Add-on from File..." overwrite = BoolProperty( name="Overwrite", @@ -1958,20 +1987,6 @@ class WM_OT_addon_install(Operator): options={'HIDDEN'}, ) - @staticmethod - def _module_remove(path_addons, module): - import os - module = os.path.splitext(module)[0] - for f in os.listdir(path_addons): - f_base = os.path.splitext(f)[0] - if f_base == module: - f_full = os.path.join(path_addons, f) - - if os.path.isdir(f_full): - os.rmdir(f_full) - else: - os.remove(f_full) - def execute(self, context): import addon_utils import traceback @@ -2024,7 +2039,7 @@ class WM_OT_addon_install(Operator): if self.overwrite: for f in file_to_extract.namelist(): - WM_OT_addon_install._module_remove(path_addons, f) + module_filesystem_remove(path_addons, f) else: for f in file_to_extract.namelist(): path_dest = os.path.join(path_addons, os.path.basename(f)) @@ -2042,7 +2057,7 @@ class WM_OT_addon_install(Operator): path_dest = os.path.join(path_addons, os.path.basename(pyfile)) if self.overwrite: - WM_OT_addon_install._module_remove(path_addons, os.path.basename(pyfile)) + module_filesystem_remove(path_addons, os.path.basename(pyfile)) elif os.path.exists(path_dest): self.report({'WARNING'}, "File already installed to %r\n" % path_dest) return {'CANCELLED'} @@ -2077,7 +2092,10 @@ class WM_OT_addon_install(Operator): bpy.utils.refresh_script_paths() # print message - msg = tip_("Modules Installed from %r into %r (%s)") % (pyfile, path_addons, ", ".join(sorted(addons_new))) + msg = ( + tip_("Modules Installed (%s) from %r into %r") % + (", ".join(sorted(addons_new)), pyfile, path_addons) + ) print(msg) self.report({'INFO'}, msg) @@ -2090,7 +2108,7 @@ class WM_OT_addon_install(Operator): class WM_OT_addon_remove(Operator): - "Delete the add-on from the file system" + """Delete the add-on from the file system""" bl_idname = "wm.addon_remove" bl_label = "Remove Add-on" @@ -2149,7 +2167,7 @@ class WM_OT_addon_remove(Operator): class WM_OT_addon_expand(Operator): - "Display information and preferences for this add-on" + """Display information and preferences for this add-on""" bl_idname = "wm.addon_expand" bl_label = "" bl_options = {'INTERNAL'} @@ -2171,8 +2189,9 @@ class WM_OT_addon_expand(Operator): return {'FINISHED'} + class WM_OT_addon_userpref_show(Operator): - "Show add-on user preferences" + """Show add-on user preferences""" bl_idname = "wm.addon_userpref_show" bl_label = "" bl_options = {'INTERNAL'} @@ -2199,3 +2218,160 @@ class WM_OT_addon_userpref_show(Operator): bpy.ops.screen.userpref_show('INVOKE_DEFAULT') return {'FINISHED'} + + +# Note: shares some logic with WM_OT_addon_install +# but not enough to de-duplicate. Fixes here may apply to both. +class WM_OT_app_template_install(Operator): + """Install an application-template""" + bl_idname = "wm.app_template_install" + bl_label = "Install Template from File..." + + overwrite = BoolProperty( + name="Overwrite", + description="Remove existing template with the same ID", + default=True, + ) + + filepath = StringProperty( + subtype='FILE_PATH', + ) + filter_folder = BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_glob = StringProperty( + default="*.zip", + options={'HIDDEN'}, + ) + + def execute(self, context): + import traceback + import zipfile + import shutil + import os + + filepath = self.filepath + + path_app_templates = bpy.utils.user_resource( + 'SCRIPTS', os.path.join("startup", "bl_app_templates_user"), + create=True, + ) + + if not path_app_templates: + self.report({'ERROR'}, "Failed to get add-ons path") + return {'CANCELLED'} + + if not os.path.isdir(path_app_templates): + try: + os.makedirs(path_app_templates, exist_ok=True) + except: + traceback.print_exc() + + app_templates_old = set(os.listdir(path_app_templates)) + + # check to see if the file is in compressed format (.zip) + if zipfile.is_zipfile(filepath): + try: + file_to_extract = zipfile.ZipFile(filepath, 'r') + except: + traceback.print_exc() + return {'CANCELLED'} + + if self.overwrite: + for f in file_to_extract.namelist(): + module_filesystem_remove(path_app_templates, f) + else: + for f in file_to_extract.namelist(): + path_dest = os.path.join(path_app_templates, os.path.basename(f)) + if os.path.exists(path_dest): + self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + return {'CANCELLED'} + + try: # extract the file to "bl_app_templates_user" + file_to_extract.extractall(path_app_templates) + except: + traceback.print_exc() + return {'CANCELLED'} + + else: + # Only support installing zipfiles + self.report({'WARNING'}, "Expected a zip-file %r\n" % filepath) + return {'CANCELLED'} + + app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old + + # in case a new module path was created to install this addon. + bpy.utils.refresh_script_paths() + + # print message + msg = ( + tip_("Template Installed (%s) from %r into %r") % + (", ".join(sorted(app_templates_new)), filepath, path_app_templates) + ) + print(msg) + self.report({'INFO'}, msg) + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +classes = ( + BRUSH_OT_active_index_set, + WM_OT_addon_disable, + WM_OT_addon_enable, + WM_OT_addon_expand, + WM_OT_addon_install, + WM_OT_addon_refresh, + WM_OT_addon_remove, + WM_OT_addon_userpref_show, + WM_OT_app_template_install, + WM_OT_appconfig_activate, + WM_OT_appconfig_default, + WM_OT_blenderplayer_start, + WM_OT_context_collection_boolean_set, + WM_OT_context_cycle_array, + WM_OT_context_cycle_enum, + WM_OT_context_cycle_int, + WM_OT_context_menu_enum, + WM_OT_context_modal_mouse, + WM_OT_context_pie_enum, + WM_OT_context_scale_float, + WM_OT_context_scale_int, + WM_OT_context_set_boolean, + WM_OT_context_set_enum, + WM_OT_context_set_float, + WM_OT_context_set_id, + WM_OT_context_set_int, + WM_OT_context_set_string, + WM_OT_context_set_value, + WM_OT_context_toggle, + WM_OT_context_toggle_enum, + WM_OT_copy_prev_settings, + WM_OT_doc_view, + WM_OT_doc_view_manual, + WM_OT_keyconfig_activate, + WM_OT_keyconfig_export, + WM_OT_keyconfig_import, + WM_OT_keyconfig_remove, + WM_OT_keyconfig_test, + WM_OT_keyitem_add, + WM_OT_keyitem_remove, + WM_OT_keyitem_restore, + WM_OT_keymap_restore, + WM_OT_operator_cheat_sheet, + WM_OT_operator_pie_enum, + WM_OT_path_open, + WM_OT_properties_add, + WM_OT_properties_context_change, + WM_OT_properties_edit, + WM_OT_properties_remove, + WM_OT_sysinfo, + WM_OT_theme_install, + WM_OT_url_open, +) |