Rigify: store advanced options in armature instead of window manager.
[blender-addons.git] / mesh_auto_mirror.py
blob6ec0c059f434cda6054cd07a9af3bd27b2d836f3
1 ######################################################################################################
2 # A simple add-on to auto cut in two and mirror an object #
3 # Actually partially uncommented (see further version) #
4 # Author: Lapineige, Bookyakuno #
5 # License: GPL v3 #
6 ######################################################################################################
7 # 2.8 update by Bookyakuno, meta-androcto
9 bl_info = {
10 "name": "Auto Mirror",
11 "description": "Super fast cutting and mirroring for mesh",
12 "author": "Lapineige",
13 "version": (2, 5, 2),
14 "blender": (2, 80, 0),
15 "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
16 "warning": "",
17 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
18 "Py/Scripts/Modeling/AutoMirror",
19 "category": "Mesh"}
22 import bpy
23 from mathutils import Vector
25 import bmesh
26 import bpy
27 import collections
28 import mathutils
29 import math
30 from bpy_extras import view3d_utils
31 from bpy.types import (
32 Operator,
33 Menu,
34 Panel,
35 AddonPreferences,
37 from bpy.props import (
38 BoolProperty,
39 EnumProperty,
40 FloatProperty,
41 IntProperty,
42 PointerProperty,
43 StringProperty,
47 # Operator
49 class AlignVertices(bpy.types.Operator):
51 """ Automatically cut an object along an axis """
53 bl_idname = "object.align_vertices"
54 bl_label = "Align Vertices on 1 Axis"
56 @classmethod
57 def poll(cls, context):
58 obj = context.active_object
59 return obj and obj.type == "MESH"
61 def execute(self, context):
62 bpy.ops.object.mode_set(mode = 'OBJECT')
64 x1,y1,z1 = bpy.context.scene.cursor.location
65 bpy.ops.view3d.snap_cursor_to_selected()
67 x2,y2,z2 = bpy.context.scene.cursor.location
69 bpy.context.scene.cursor.location[0], \
70 bpy.context.scene.cursor.location[1], \
71 bpy.context.scene.cursor.location[2] = 0, 0, 0
73 #Vertices coordinate to 0 (local coordinate, so on the origin)
74 for vert in bpy.context.object.data.vertices:
75 if vert.select:
76 if bpy.context.scene.AutoMirror_axis == 'x':
77 axis = 0
78 elif bpy.context.scene.AutoMirror_axis == 'y':
79 axis = 1
80 elif bpy.context.scene.AutoMirror_axis == 'z':
81 axis = 2
82 vert.co[axis] = 0
84 bpy.context.scene.cursor.location = x2,y2,z2
86 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
88 bpy.context.scene.cursor.location = x1,y1,z1
90 bpy.ops.object.mode_set(mode = 'EDIT')
91 return {'FINISHED'}
94 class AutoMirror(bpy.types.Operator):
95 """ Automatically cut an object along an axis """
96 bl_idname = "object.automirror"
97 bl_label = "AutoMirror"
98 bl_options = {'REGISTER'} # 'UNDO' ?
100 @classmethod
101 def poll(cls, context):
102 obj = context.active_object
103 return obj and obj.type == "MESH"
105 def draw(self, context):
106 layout = self.layout
107 if bpy.context.object and bpy.context.object.type == 'MESH':
108 layout.prop(context.scene, "AutoMirror_axis", text="Mirror axis")
109 layout.prop(context.scene, "AutoMirror_orientation", text="Orientation")
110 layout.prop(context.scene, "AutoMirror_threshold", text="Threshold")
111 layout.prop(context.scene, "AutoMirror_toggle_edit", text="Toggle edit")
112 layout.prop(context.scene, "AutoMirror_cut", text="Cut and mirror")
113 if bpy.context.scene.AutoMirror_cut:
114 layout.prop(context.scene, "AutoMirror_clipping", text="Clipping")
115 layout.prop(context.scene, "AutoMirror_apply_mirror", text="Apply mirror")
117 else:
118 layout.label(icon="ERROR", text="No mesh selected")
120 def get_local_axis_vector(self, context, X, Y, Z, orientation):
121 loc = context.object.location
122 bpy.ops.object.mode_set(mode="OBJECT") # Needed to avoid to translate vertices
124 v1 = Vector((loc[0],loc[1],loc[2]))
125 bpy.ops.transform.translate(value=(X*orientation, Y*orientation, Z*orientation),
126 constraint_axis=((X==1), (Y==1), (Z==1)),
127 orient_type='LOCAL')
128 v2 = Vector((loc[0],loc[1],loc[2]))
129 bpy.ops.transform.translate(value=(-X*orientation, -Y*orientation, -Z*orientation),
130 constraint_axis=((X==1), (Y==1), (Z==1)),
131 orient_type='LOCAL')
133 bpy.ops.object.mode_set(mode="EDIT")
134 return v2-v1
136 def execute(self, context):
137 X,Y,Z = 0,0,0
138 if bpy.context.scene.AutoMirror_axis == 'x':
139 X = 1
140 elif bpy.context.scene.AutoMirror_axis == 'y':
141 Y = 1
142 elif bpy.context.scene.AutoMirror_axis == 'z':
143 Z = 1
145 current_mode = bpy.context.object.mode # Save the current mode
147 if bpy.context.object.mode != "EDIT":
148 bpy.ops.object.mode_set(mode="EDIT") # Go to edit mode
149 bpy.ops.mesh.select_all(action='SELECT') # Select all the vertices
150 if bpy.context.scene.AutoMirror_orientation == 'positive':
151 orientation = 1
152 else:
153 orientation = -1
154 cut_normal = self.get_local_axis_vector(context, X, Y, Z, orientation)
156 # Cut the mesh
157 bpy.ops.mesh.bisect(
158 plane_co=(
159 bpy.context.object.location[0],
160 bpy.context.object.location[1],
161 bpy.context.object.location[2]
163 plane_no=cut_normal,
164 use_fill= False,
165 clear_inner= bpy.context.scene.AutoMirror_cut,
166 clear_outer= 0,
167 threshold= bpy.context.scene.AutoMirror_threshold)
169 bpy.ops.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"
171 if not bpy.context.scene.AutoMirror_toggle_edit:
172 bpy.ops.object.mode_set(mode=current_mode) # Reload previous mode
174 if bpy.context.scene.AutoMirror_cut:
175 bpy.ops.object.modifier_add(type='MIRROR') # Add a mirror modifier
176 bpy.context.object.modifiers[-1].use_axis[0] = X # Choose the axis to use, based on the cut's axis
177 bpy.context.object.modifiers[-1].use_axis[1] = Y
178 bpy.context.object.modifiers[-1].use_axis[2] = Z
179 bpy.context.object.modifiers[-1].use_clip = context.scene.Use_Matcap
180 bpy.context.object.modifiers[-1].show_on_cage = context.scene.AutoMirror_show_on_cage
181 if bpy.context.scene.AutoMirror_apply_mirror:
182 bpy.ops.object.mode_set(mode='OBJECT')
183 bpy.ops.object.modifier_apply(apply_as= 'DATA', modifier= bpy.context.object.modifiers[-1].name)
184 if bpy.context.scene.AutoMirror_toggle_edit:
185 bpy.ops.object.mode_set(mode='EDIT')
186 else:
187 bpy.ops.object.mode_set(mode=current_mode)
189 return {'FINISHED'}
192 # Panel
194 class VIEW3D_PT_BisectMirror(Panel):
195 bl_space_type = 'VIEW_3D'
196 bl_region_type = 'UI'
197 bl_label = "Auto Mirror"
198 bl_category = 'Edit'
199 bl_options = {'DEFAULT_CLOSED'}
202 def draw(self, context):
203 layout = self.layout
204 col = layout.column(align=True)
206 layout = self.layout
207 if bpy.context.object and bpy.context.object.type == 'MESH':
209 layout.operator("object.automirror")
210 layout.prop(context.scene, "AutoMirror_axis", text="Mirror Axis", expand=True)
211 layout.prop(context.scene, "AutoMirror_orientation", text="Orientation")
212 layout.prop(context.scene, "AutoMirror_threshold", text="Threshold")
213 layout.prop(context.scene, "AutoMirror_toggle_edit", text="Toggle Edit")
214 layout.prop(context.scene, "AutoMirror_cut", text="Cut and Mirror")
215 if bpy.context.scene.AutoMirror_cut:
216 layout.prop(context.scene, "Use_Matcap", text="Use Clip")
217 layout.prop(context.scene, "AutoMirror_show_on_cage", text="Editable")
218 layout.prop(context.scene, "AutoMirror_apply_mirror", text="Apply Mirror")
220 else:
221 layout.label(icon="ERROR", text="No mesh selected")
223 # Properties
225 bpy.types.Scene.AutoMirror_axis = bpy.props.EnumProperty(
226 items = [("x", "X", "", 1),("y", "Y", "", 2),("z", "Z", "", 3)],
227 description="Axis used by the mirror modifier")
229 bpy.types.Scene.AutoMirror_orientation = bpy.props.EnumProperty(
230 items = [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
231 description="Choose the side along the axis of the editable part (+/- coordinates)")
233 bpy.types.Scene.AutoMirror_threshold = bpy.props.FloatProperty(
234 default= 0.001, min= 0.001,
235 description="Vertices closer than this distance are merged on the loopcut")
237 bpy.types.Scene.AutoMirror_toggle_edit = bpy.props.BoolProperty(
238 default= False,
239 description="If not in edit mode, change mode to edit")
241 bpy.types.Scene.AutoMirror_cut = bpy.props.BoolProperty(
242 default= True,
243 description="If enabeled, cut the mesh in two parts and mirror it. If not, just make a loopcut")
245 bpy.types.Scene.AutoMirror_clipping = bpy.props.BoolProperty(
246 default=True)
247 bpy.types.Scene.Use_Matcap = bpy.props.BoolProperty(default=True,
248 description="Use clipping for the mirror modifier")
250 bpy.types.Scene.AutoMirror_show_on_cage = bpy.props.BoolProperty(
251 default=False,
252 description="Enable to edit the cage (it's the classical modifier's option)")
254 bpy.types.Scene.AutoMirror_apply_mirror = bpy.props.BoolProperty(
256 description="Apply the mirror modifier (useful to symmetrise the mesh)")
259 # Add-ons Preferences Update Panel
261 # Define Panel classes for updating
262 panels = (
263 VIEW3D_PT_BisectMirror,
267 def update_panel(self, context):
268 message = ": Updating Panel locations has failed"
269 try:
270 for panel in panels:
271 if "bl_rna" in panel.__dict__:
272 bpy.utils.unregister_class(panel)
274 for panel in panels:
275 panel.bl_category = context.preferences.addons[__name__].preferences.category
276 bpy.utils.register_class(panel)
278 except Exception as e:
279 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
280 pass
283 class AutoMirrorAddonPreferences(AddonPreferences):
284 # this must match the addon name, use '__package__'
285 # when defining this in a submodule of a python package.
286 bl_idname = __name__
288 category: StringProperty(
289 name="Tab Category",
290 description="Choose a name for the category of the panel",
291 default="Edit",
292 update=update_panel
295 def draw(self, context):
296 layout = self.layout
298 row = layout.row()
299 col = row.column()
300 col.label(text="Tab Category:")
301 col.prop(self, "category", text="")
303 # define classes for registration
304 classes = (
305 VIEW3D_PT_BisectMirror,
306 AutoMirror,
307 AlignVertices,
308 AutoMirrorAddonPreferences
312 # registering and menu integration
313 def register():
314 for cls in classes:
315 bpy.utils.register_class(cls)
316 update_panel(None, bpy.context)
318 # unregistering and removing menus
319 def unregister():
320 for cls in reversed(classes):
321 bpy.utils.unregister_class(cls)
323 if __name__ == "__main__":
324 register()