diff --git a/mesh_auto_mirror.py b/mesh_auto_mirror.py index 84128072cfd89c0a60dc8da09be0c294c0067d24..032bff37950501168e73a277ddf63c990e2bf719 100644 --- a/mesh_auto_mirror.py +++ b/mesh_auto_mirror.py @@ -1,208 +1,182 @@ -###################################################################################################### -# An simple add-on to auto cut in two and mirror an object # -# Actualy partialy uncommented (see further version) # -# Author: Lapineige # -# License: GPL v3 # -###################################################################################################### - - -############# Add-on description (used by Blender) +# ########################################################### # +# An simple add-on to auto cut in two and mirror an object # +# Actualy partialy uncommented (see further version) # +# Author: Lapineige # +# License: GPL v3 # +# ########################################################### # bl_info = { "name": "Auto Mirror", - "description": "Super fast cutting and mirroring for mesh", + "description": "Super fast cutting and mirroring for Mesh objects", "author": "Lapineige", - "version": (2, 4, 1), + "version": (2, 4, 2), "blender": (2, 7, 1), "location": "View 3D > Toolbar > Tools tab > AutoMirror (panel)", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/AutoMirror", - "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" + "Py/Scripts/Modeling/AutoMirror", "category": "Mesh"} import bpy +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + PointerProperty, + ) +from bpy.types import ( + Operator, + Panel, + PropertyGroup, + ) from mathutils import Vector -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= True, - 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.AutoMirror_use_clip = bpy.props.BoolProperty( - default=True, - description="Use clipping for the mirror modifier" - ) -bpy.types.Scene.AutoMirror_show_on_cage = bpy.props.BoolProperty( - default=True, - 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)" - ) - -############### Operator -class AlignVertices(bpy.types.Operator): - """ """ +# Operators +class AlignVertices(Operator): bl_idname = "object.align_vertices" - bl_label = "Align Vertices on 1 Axis" + bl_label = "Align Vertices on an Axis" + bl_description = ("Align Vertices on an Axis\n" + "Needs an Active Mesh Object") @classmethod def poll(cls, context): - return True + obj = context.active_object + return obj and obj.type == "MESH" def execute(self, context): - bpy.ops.object.mode_set(mode = 'OBJECT') + auto_m = context.scene.auto_mirror + 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[0], \ + bpy.context.scene.cursor_location[1], \ + 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 bpy.context.scene.AutoMirror_axis == 'x': + if auto_m.axis == 'x': axis = 0 - elif bpy.context.scene.AutoMirror_axis == 'y': + elif auto_m.axis == 'y': axis = 1 - elif bpy.context.scene.AutoMirror_axis == 'z': + elif auto_m.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.context.scene.cursor_location = x1, y1, z1 + bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.object.mode_set(mode = 'EDIT') return {'FINISHED'} -class AutoMirror(bpy.types.Operator): - """ Automatically cut an object along an axis """ + +class AutoMirror(Operator): bl_idname = "object.automirror" bl_label = "AutoMirror" - bl_options = {'REGISTER'} # 'UNDO' ? + bl_description = ("Automatically cut an object along an axis\n" + "Needs an Active Mesh Object") + bl_options = {'REGISTER'} @classmethod def poll(cls, context): - return True - - 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") + obj = context.active_object + return obj and obj.type == "MESH" 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 + bpy.ops.object.mode_set(mode="OBJECT") # Needed to avoid to translate vertices - v1 = Vector((loc[0],loc[1],loc[2])) + 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)), - constraint_orientation='LOCAL') - v2 = Vector((loc[0],loc[1],loc[2])) + value=(X * orientation, Y * orientation, Z * orientation), + constraint_axis=((X == 1), (Y == 1), (Z == 1)), + constraint_orientation='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)), - constraint_orientation='LOCAL') + value=(-X * orientation, -Y * orientation, -Z * orientation), + constraint_axis=((X == 1), (Y == 1), (Z == 1)), + constraint_orientation='LOCAL' + ) bpy.ops.object.mode_set(mode="EDIT") - return v2-v1 + return v2 - v1 def execute(self, context): - X,Y,Z = 0,0,0 - if bpy.context.scene.AutoMirror_axis == 'x': + auto_m = context.scene.auto_mirror + + X, Y, Z = 0, 0, 0 + if auto_m.axis == 'x': X = 1 - elif bpy.context.scene.AutoMirror_axis == 'y': + elif auto_m.axis == 'y': Y = 1 - elif bpy.context.scene.AutoMirror_axis == 'z': + elif auto_m.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 bpy.context.scene.AutoMirror_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 auto_m.orientation == 'positive': orientation = 1 else: orientation = -1 cut_normal = self.get_local_axis_vector(context, X, Y, Z, orientation) - bpy.ops.mesh.bisect(plane_co=(bpy.context.object.location[0], - bpy.context.object.location[1], - bpy.context.object.location[2]), - plane_no=cut_normal, - use_fill= False, - clear_inner= bpy.context.scene.AutoMirror_cut, - clear_outer= 0, - threshold= bpy.context.scene.AutoMirror_threshold) # Cut the mesh - - 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_x = X # Choose the axis to use, based on the cut's axis + # Cut the mesh + bpy.ops.mesh.bisect( + plane_co=( + 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 = context.scene.AutoMirror_use_clip - bpy.context.object.modifiers[-1].show_on_cage = context.scene.AutoMirror_show_on_cage - if bpy.context.scene.AutoMirror_apply_mirror: + 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: bpy.ops.object.mode_set(mode='OBJECT') - 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.modifier_apply( + apply_as='DATA', + modifier=bpy.context.object.modifiers[-1].name + ) + if auto_m.toggle_edit: bpy.ops.object.mode_set(mode='EDIT') else: bpy.ops.object.mode_set(mode=current_mode) return {'FINISHED'} -#################### Panel -class BisectMirror(bpy.types.Panel): - """ The AutoMirror panel on the toolbar tab 'Tools' """ +# Panel +class BisectMirror(Panel): bl_label = "Auto Mirror" bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' @@ -211,33 +185,94 @@ class BisectMirror(bpy.types.Panel): def draw(self, context): 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: - row = layout.row() - row.prop(context.scene, "AutoMirror_use_clip", text="Use Clip") - row.prop(context.scene, "AutoMirror_show_on_cage", text="Editable") - layout.prop(context.scene, "AutoMirror_apply_mirror", text="Apply Mirror") - + auto_m = context.scene.auto_mirror + obj = context.active_object + + if obj and obj.type == 'MESH': + layout.operator("object.automirror", icon="MOD_MIRROR") + layout.label("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="ERROR", text="No mesh selected") + 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)" + ) 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 + ) 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 if __name__ == "__main__":