diff --git a/mesh_auto_mirror.py b/mesh_auto_mirror.py index 68193c1a44a1e135af032e39276fab68d5c92a29..022981a3a9683e725744cecd1b8c4b8f74db522d 100644 --- a/mesh_auto_mirror.py +++ b/mesh_auto_mirror.py @@ -1,17 +1,18 @@ -# ########################################################### # -# An simple add-on to auto cut in two and mirror an object # -# Actually partially uncommented (see further version) # -# Author: Lapineige # -# License: GPL v3 # -# ########################################################### # +###################################################################################################### +# An simple add-on to auto cut in two and mirror an object # +# Actualy partialy uncommented (see further version) # +# Author: Lapineige, Bookyakuno # +# License: GPL v3 # +###################################################################################################### +# 2.8 update by Bookyakuno, meta-androcto bl_info = { "name": "Auto Mirror", - "description": "Super fast cutting and mirroring for Mesh objects", + "description": "Super fast cutting and mirroring for mesh", "author": "Lapineige", - "version": (2, 4, 2), - "blender": (2, 71, 0), - "location": "View 3D > Toolbar > Tools tab > AutoMirror (panel)", + "version": (2, 5, 2), + "blender": (2, 80, 0), + "location": "View 3D > Sidebar > Tools Tab > AutoMirror (panel)", "warning": "", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" "Py/Scripts/Modeling/AutoMirror", @@ -19,26 +20,38 @@ bl_info = { import bpy +from mathutils import Vector + +import bmesh +import bpy +import collections +import mathutils +import math +from bpy_extras import view3d_utils +from bpy.types import ( + Operator, + Menu, + Panel, + AddonPreferences, + ) from bpy.props import ( BoolProperty, EnumProperty, FloatProperty, + IntProperty, PointerProperty, + StringProperty, ) -from bpy.types import ( - Operator, - Panel, - PropertyGroup, - ) -from mathutils import Vector -# Operators -class AlignVertices(Operator): +# Operator + +class AlignVertices(bpy.types.Operator): + + """ Automatically cut an object along an axis """ + bl_idname = "object.align_vertices" - bl_label = "Align Vertices on an Axis" - bl_description = ("Align Vertices on an Axis\n" - "Needs an Active Mesh Object") + bl_label = "Align Vertices on 1 Axis" @classmethod def poll(cls, context): @@ -46,88 +59,95 @@ class AlignVertices(Operator): return obj and obj.type == "MESH" def execute(self, context): - auto_m = context.scene.auto_mirror - bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode = 'OBJECT') - x1, y1, z1 = bpy.context.scene.cursor.location + x1,y1,z1 = bpy.context.scene.cursor.location bpy.ops.view3d.snap_cursor_to_selected() - x2, y2, z2 = bpy.context.scene.cursor.location + x2,y2,z2 = bpy.context.scene.cursor.location bpy.context.scene.cursor.location[0], \ bpy.context.scene.cursor.location[1], \ - bpy.context.scene.cursor.location[2] = 0, 0, 0 + bpy.context.scene.cursor.location[2] = 0, 0, 0 - # Vertices coordinate to 0 (local coordinate, so on the origin) + #Vertices coordinate to 0 (local coordinate, so on the origin) for vert in bpy.context.object.data.vertices: if vert.select: - if auto_m.axis == 'x': + if bpy.context.scene.AutoMirror_axis == 'x': axis = 0 - elif auto_m.axis == 'y': + elif bpy.context.scene.AutoMirror_axis == 'y': axis = 1 - elif auto_m.axis == 'z': + elif bpy.context.scene.AutoMirror_axis == 'z': axis = 2 vert.co[axis] = 0 + # + bpy.context.scene.cursor.location = x2,y2,z2 - bpy.context.scene.cursor.location = x2, y2, z2 bpy.ops.object.origin_set(type='ORIGIN_CURSOR') - bpy.context.scene.cursor.location = x1, y1, z1 - bpy.ops.object.mode_set(mode='EDIT') + bpy.context.scene.cursor.location = x1,y1,z1 + bpy.ops.object.mode_set(mode = 'EDIT') return {'FINISHED'} -class AutoMirror(Operator): +class AutoMirror(bpy.types.Operator): + """ Automatically cut an object along an axis """ bl_idname = "object.automirror" bl_label = "AutoMirror" - bl_description = ("Automatically cut an object along an axis\n" - "Needs an Active Mesh Object") - bl_options = {'REGISTER'} + bl_options = {'REGISTER'} # 'UNDO' ? @classmethod def poll(cls, context): obj = context.active_object return obj and obj.type == "MESH" + def draw(self, context): + layout = self.layout + if bpy.context.object and bpy.context.object.type == 'MESH': + layout.prop(context.scene, "AutoMirror_axis", text="Mirror axis") + layout.prop(context.scene, "AutoMirror_orientation", text="Orientation") + layout.prop(context.scene, "AutoMirror_threshold", text="Threshold") + layout.prop(context.scene, "AutoMirror_toggle_edit", text="Toggle edit") + layout.prop(context.scene, "AutoMirror_cut", text="Cut and mirror") + if bpy.context.scene.AutoMirror_cut: + layout.prop(context.scene, "AutoMirror_clipping", text="Clipping") + layout.prop(context.scene, "AutoMirror_apply_mirror", text="Apply mirror") + + else: + layout.label(icon="ERROR", text="No mesh selected") + def get_local_axis_vector(self, context, X, Y, Z, orientation): loc = context.object.location - bpy.ops.object.mode_set(mode="OBJECT") # Needed to avoid to translate vertices - - v1 = Vector((loc[0], loc[1], loc[2])) - bpy.ops.transform.translate( - value=(X * orientation, Y * orientation, Z * orientation), - constraint_axis=((X == 1), (Y == 1), (Z == 1)), - orient_type='LOCAL' - ) - v2 = Vector((loc[0], loc[1], loc[2])) - bpy.ops.transform.translate( - value=(-X * orientation, -Y * orientation, -Z * orientation), - constraint_axis=((X == 1), (Y == 1), (Z == 1)), - orient_type='LOCAL' - ) + bpy.ops.object.mode_set(mode="OBJECT") # Needed to avoid to translate vertices + + v1 = Vector((loc[0],loc[1],loc[2])) + bpy.ops.transform.translate(value=(X*orientation, Y*orientation, Z*orientation), + constraint_axis=((X==1), (Y==1), (Z==1)), + orient_type='LOCAL') + v2 = Vector((loc[0],loc[1],loc[2])) + bpy.ops.transform.translate(value=(-X*orientation, -Y*orientation, -Z*orientation), + constraint_axis=((X==1), (Y==1), (Z==1)), + orient_type='LOCAL') bpy.ops.object.mode_set(mode="EDIT") - return v2 - v1 + return v2-v1 def execute(self, context): - auto_m = context.scene.auto_mirror - - X, Y, Z = 0, 0, 0 - if auto_m.axis == 'x': + X,Y,Z = 0,0,0 + if bpy.context.scene.AutoMirror_axis == 'x': X = 1 - elif auto_m.axis == 'y': + elif bpy.context.scene.AutoMirror_axis == 'y': Y = 1 - elif auto_m.axis == 'z': + elif bpy.context.scene.AutoMirror_axis == 'z': Z = 1 - current_mode = bpy.context.object.mode # Save the current mode + current_mode = bpy.context.object.mode # Save the current mode if bpy.context.object.mode != "EDIT": - bpy.ops.object.mode_set(mode="EDIT") # Go to edit mode - - bpy.ops.mesh.select_all(action='SELECT') # Select all the vertices - if auto_m.orientation == 'positive': + bpy.ops.object.mode_set(mode="EDIT") # Go to edit mode + bpy.ops.mesh.select_all(action='SELECT') # Select all the vertices + if bpy.context.scene.AutoMirror_orientation == 'positive': orientation = 1 else: orientation = -1 @@ -136,38 +156,32 @@ class AutoMirror(Operator): # Cut the mesh bpy.ops.mesh.bisect( plane_co=( - bpy.context.object.location[0], - bpy.context.object.location[1], - bpy.context.object.location[2] - ), + bpy.context.object.location[0], + bpy.context.object.location[1], + bpy.context.object.location[2] + ), plane_no=cut_normal, - use_fill=False, - clear_inner=auto_m.cut, - clear_outer=0, - threshold=auto_m.threshold - ) - - # Use to align the vertices on the origin, needed by the "threshold" - bpy.ops.object.align_vertices() - - if not auto_m.toggle_edit: - bpy.ops.object.mode_set(mode=current_mode) # Reload previous mode - - if auto_m.cut: - bpy.ops.object.modifier_add(type='MIRROR') # Add a mirror modifier - bpy.context.object.modifiers[-1].use_x = X # Choose the axis to use, based on the cut's axis - bpy.context.object.modifiers[-1].use_y = Y - bpy.context.object.modifiers[-1].use_z = Z - bpy.context.object.modifiers[-1].use_clip = auto_m.use_clip - bpy.context.object.modifiers[-1].show_on_cage = auto_m.show_on_cage - - if auto_m.apply_mirror: + use_fill= False, + clear_inner= bpy.context.scene.AutoMirror_cut, + clear_outer= 0, + threshold= bpy.context.scene.AutoMirror_threshold) + + bpy.ops.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold" + + if not bpy.context.scene.AutoMirror_toggle_edit: + bpy.ops.object.mode_set(mode=current_mode) # Reload previous mode + + if bpy.context.scene.AutoMirror_cut: + bpy.ops.object.modifier_add(type='MIRROR') # Add a mirror modifier + bpy.context.object.modifiers[-1].use_axis[0] = X # Choose the axis to use, based on the cut's axis + bpy.context.object.modifiers[-1].use_axis[1] = Y + bpy.context.object.modifiers[-1].use_axis[2] = Z + bpy.context.object.modifiers[-1].use_clip = context.scene.Use_Matcap + bpy.context.object.modifiers[-1].show_on_cage = context.scene.AutoMirror_show_on_cage + if bpy.context.scene.AutoMirror_apply_mirror: bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.modifier_apply( - apply_as='DATA', - modifier=bpy.context.object.modifiers[-1].name - ) - if auto_m.toggle_edit: + bpy.ops.object.modifier_apply(apply_as= 'DATA', modifier= bpy.context.object.modifiers[-1].name) + if bpy.context.scene.AutoMirror_toggle_edit: bpy.ops.object.mode_set(mode='EDIT') else: bpy.ops.object.mode_set(mode=current_mode) @@ -176,104 +190,135 @@ class AutoMirror(Operator): # Panel -class BisectMirror(Panel): - bl_label = "Auto Mirror" + +class VIEW3D_PT_BisectMirror(Panel): bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' - bl_category = "Tools" - bl_options = {"DEFAULT_CLOSED"} + bl_region_type = 'UI' + bl_label = "Auto Mirror" + bl_category = 'Tools' + bl_options = {'DEFAULT_CLOSED'} + def draw(self, context): layout = self.layout - auto_m = context.scene.auto_mirror - obj = context.active_object + col = layout.column(align=True) + + layout = self.layout + if bpy.context.object and bpy.context.object.type == 'MESH': + + layout.operator("object.automirror") + layout.prop(context.scene, "AutoMirror_axis", text="Mirror Axis", expand=True) + layout.prop(context.scene, "AutoMirror_orientation", text="Orientation") + layout.prop(context.scene, "AutoMirror_threshold", text="Threshold") + layout.prop(context.scene, "AutoMirror_toggle_edit", text="Toggle Edit") + layout.prop(context.scene, "AutoMirror_cut", text="Cut and Mirror") + if bpy.context.scene.AutoMirror_cut: + layout.prop(context.scene, "Use_Matcap", text="Use Clip") + layout.prop(context.scene, "AutoMirror_show_on_cage", text="Editable") + layout.prop(context.scene, "AutoMirror_apply_mirror", text="Apply Mirror") - if obj and obj.type == 'MESH': - layout.operator("object.automirror", icon="MOD_MIRROR") - layout.label(text="Options:") - layout.prop(auto_m, "axis", text="Mirror Axis", expand=True) - layout.prop(auto_m, "orientation", text="Orientation") - layout.prop(auto_m, "threshold", text="Threshold") - layout.prop(auto_m, "toggle_edit", text="Toggle Edit") - layout.prop(auto_m, "cut", text="Cut and Mirror", toggle=True, icon="MOD_REMESH") - - if auto_m.cut: - col = layout.column(align=True) - row = col.row(align=True) - row.prop(auto_m, "use_clip", text="Use Clip", toggle=True) - row.prop(auto_m, "show_on_cage", text="Editable", toggle=True) - col.prop(auto_m, "apply_mirror", text="Apply Mirror", toggle=True) else: - layout.label(icon="INFO", text="No Mesh selected") - - -class AutoMirrorProperties(PropertyGroup): - axis: EnumProperty( - name="Axis", - items=[ - ("x", "X", "", 1), - ("y", "Y", "", 2), - ("z", "Z", "", 3) - ], - description="Axis used by the mirror modifier" - ) - orientation: EnumProperty( - name="Orientation", - items=[ - ("positive", "Positive", "", 1), - ("negative", "Negative", "", 2) - ], - description="Choose the side along the axis of the editable part (+/- coordinates)" - ) - threshold: FloatProperty( - default=0.001, - min=0.001, - description="Vertices closer than this distance are merged on the loopcut" - ) - toggle_edit: BoolProperty( - name="Toggle Edit Mode", - default=True, - description="If not in Edit mode, change mode to it" - ) - cut: BoolProperty( - name="Cut", - default=True, - description="If enabled, cut the mesh in two parts and mirror it\n" - "If not, just make a loopcut" - ) - clipping: BoolProperty( - default=True - ) - use_clip: BoolProperty( - default=True, - description="Use clipping for the mirror modifier" - ) - show_on_cage: BoolProperty( - default=True, - description="Enable editing the cage (it's the classical modifier's option)" - ) - apply_mirror: BoolProperty( - description="Apply the mirror modifier (useful to symmetrise the mesh)" + layout.label(icon="ERROR", text="No mesh selected") + +# Properties + +bpy.types.Scene.AutoMirror_axis = bpy.props.EnumProperty( + items = [("x", "X", "", 1),("y", "Y", "", 2),("z", "Z", "", 3)], + description="Axis used by the mirror modifier") + +bpy.types.Scene.AutoMirror_orientation = bpy.props.EnumProperty( + items = [("positive", "Positive", "", 1),("negative", "Negative", "", 2)], + description="Choose the side along the axis of the editable part (+/- coordinates)") + +bpy.types.Scene.AutoMirror_threshold = bpy.props.FloatProperty( + default= 0.001, min= 0.001, + description="Vertices closer than this distance are merged on the loopcut") + +bpy.types.Scene.AutoMirror_toggle_edit = bpy.props.BoolProperty( + default= False, + description="If not in edit mode, change mode to edit") + +bpy.types.Scene.AutoMirror_cut = bpy.props.BoolProperty( + default= True, + description="If enabeled, cut the mesh in two parts and mirror it. If not, just make a loopcut") + +bpy.types.Scene.AutoMirror_clipping = bpy.props.BoolProperty( + default=True) +bpy.types.Scene.Use_Matcap = bpy.props.BoolProperty(default=True, +description="Use clipping for the mirror modifier") + +bpy.types.Scene.AutoMirror_show_on_cage = bpy.props.BoolProperty( + default=False, + description="Enable to edit the cage (it's the classical modifier's option)") + +bpy.types.Scene.AutoMirror_apply_mirror = bpy.props.BoolProperty( + + description="Apply the mirror modifier (useful to symmetrise the mesh)") + + +# Add-ons Preferences Update Panel + +# Define Panel classes for updating +panels = ( + VIEW3D_PT_BisectMirror, + ) + + +def update_panel(self, context): + message = ": Updating Panel locations has failed" + try: + for panel in panels: + if "bl_rna" in panel.__dict__: + bpy.utils.unregister_class(panel) + + for panel in panels: + panel.bl_category = context.preferences.addons[__name__].preferences.category + bpy.utils.register_class(panel) + + except Exception as e: + print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e)) + pass + + +class AutoMirrorAddonPreferences(AddonPreferences): + # this must match the addon name, use '__package__' + # when defining this in a submodule of a python package. + bl_idname = __name__ + + category: StringProperty( + name="Tab Category", + description="Choose a name for the category of the panel", + default="Tools", + update=update_panel ) + def draw(self, context): + layout = self.layout + row = layout.row() + col = row.column() + col.label(text="Tab Category:") + col.prop(self, "category", text="") + +# define classes for registration +classes = ( + VIEW3D_PT_BisectMirror, + AutoMirror, + AlignVertices, + AutoMirrorAddonPreferences + ) + + +# registering and menu integration def register(): - bpy.utils.register_class(BisectMirror) - bpy.utils.register_class(AutoMirror) - bpy.utils.register_class(AlignVertices) - bpy.utils.register_class(AutoMirrorProperties) - bpy.types.Scene.auto_mirror = PointerProperty( - type=AutoMirrorProperties - ) - + for cls in classes: + bpy.utils.register_class(cls) + update_panel(None, bpy.context) +# unregistering and removing menus def unregister(): - bpy.utils.unregister_class(BisectMirror) - bpy.utils.unregister_class(AutoMirror) - bpy.utils.unregister_class(AlignVertices) - bpy.utils.unregister_class(AutoMirrorProperties) - del bpy.types.Scene.auto_mirror - + for cls in reversed(classes): + bpy.utils.unregister_class(cls) if __name__ == "__main__": register()