GPencil Tools: Fix box-deform with multi-user data
[blender-addons.git] / mesh_auto_mirror.py
blob47611668af6b14ecc9e98c67c293e3ac3f165092
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, 3),
14 "blender": (2, 80, 0),
15 "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
16 "warning": "",
17 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html",
18 "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,
36 PropertyGroup,
38 from bpy.props import (
39 BoolProperty,
40 EnumProperty,
41 FloatProperty,
42 IntProperty,
43 PointerProperty,
44 StringProperty,
48 # Operator
50 class AlignVertices(Operator):
52 """ Automatically cut an object along an axis """
54 bl_idname = "object.align_vertices"
55 bl_label = "Align Vertices on 1 Axis"
57 @classmethod
58 def poll(cls, context):
59 obj = context.active_object
60 return obj and obj.type == "MESH"
62 def execute(self, context):
63 automirror = context.scene.automirror
65 bpy.ops.object.mode_set(mode = 'OBJECT')
67 x1,y1,z1 = bpy.context.scene.cursor.location
68 bpy.ops.view3d.snap_cursor_to_selected()
70 x2,y2,z2 = bpy.context.scene.cursor.location
72 bpy.context.scene.cursor.location[0], \
73 bpy.context.scene.cursor.location[1], \
74 bpy.context.scene.cursor.location[2] = 0, 0, 0
76 #Vertices coordinate to 0 (local coordinate, so on the origin)
77 for vert in bpy.context.object.data.vertices:
78 if vert.select:
79 if automirror.axis == 'x':
80 axis = 0
81 elif automirror.axis == 'y':
82 axis = 1
83 elif automirror.axis == 'z':
84 axis = 2
85 vert.co[axis] = 0
87 bpy.context.scene.cursor.location = x2,y2,z2
89 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
91 bpy.context.scene.cursor.location = x1,y1,z1
93 bpy.ops.object.mode_set(mode = 'EDIT')
94 return {'FINISHED'}
97 class AutoMirror(bpy.types.Operator):
98 """ Automatically cut an object along an axis """
99 bl_idname = "object.automirror"
100 bl_label = "AutoMirror"
101 bl_options = {'REGISTER'} # 'UNDO' ?
103 @classmethod
104 def poll(cls, context):
105 obj = context.active_object
106 return obj and obj.type == "MESH"
108 def draw(self, context):
109 automirror = context.scene.automirror
111 layout = self.layout
112 if bpy.context.object and bpy.context.object.type == 'MESH':
113 layout.prop(automirror, "axis", text = "Mirror axis")
114 layout.prop(automirror, "orientation", text = "Orientation")
115 layout.prop(automirror, "threshold", text = "Threshold")
116 layout.prop(automirror, "toggle_edit", text = "Toggle edit")
117 layout.prop(automirror, "cut", text = "Cut and mirror")
118 if automirror.cut:
119 layout.prop(automirror, "clipping", text = "Clipping")
120 layout.prop(automirror, "mirror", text = "Apply mirror")
122 else:
123 layout.label(icon = "ERROR", text = "No mesh selected")
125 def get_local_axis_vector(self, context, X, Y, Z, orientation):
126 loc = context.object.location
127 bpy.ops.object.mode_set(mode = "OBJECT") # Needed to avoid to translate vertices
129 v1 = Vector((loc[0],loc[1],loc[2]))
130 bpy.ops.transform.translate(value = (X*orientation, Y*orientation, Z*orientation),
131 constraint_axis = ((X==1), (Y==1), (Z==1)),
132 orient_type = 'LOCAL')
133 v2 = Vector((loc[0],loc[1],loc[2]))
134 bpy.ops.transform.translate(value = (-X*orientation, -Y*orientation, -Z*orientation),
135 constraint_axis = ((X==1), (Y==1), (Z==1)),
136 orient_type = 'LOCAL')
138 bpy.ops.object.mode_set(mode="EDIT")
139 return v2-v1
141 def execute(self, context):
142 context.active_object.select_set(True)
144 automirror = context.scene.automirror
146 X,Y,Z = 0,0,0
148 if automirror.axis == 'x':
149 X = 1
150 elif automirror.axis == 'y':
151 Y = 1
152 elif automirror.axis == 'z':
153 Z = 1
155 current_mode = bpy.context.object.mode # Save the current mode
157 if bpy.context.object.mode != "EDIT":
158 bpy.ops.object.mode_set(mode = "EDIT") # Go to edit mode
160 bpy.ops.mesh.select_all(action = 'SELECT') # Select all the vertices
162 if automirror.orientation == 'positive':
163 orientation = 1
164 else:
165 orientation = -1
167 cut_normal = self.get_local_axis_vector(context, X, Y, Z, orientation)
169 # Cut the mesh
170 bpy.ops.mesh.bisect(
171 plane_co = (
172 bpy.context.object.location[0],
173 bpy.context.object.location[1],
174 bpy.context.object.location[2]
176 plane_no = cut_normal,
177 use_fill = False,
178 clear_inner = automirror.cut,
179 clear_outer = 0,
180 threshold = automirror.threshold
183 bpy.ops.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"
185 if not automirror.toggle_edit:
186 bpy.ops.object.mode_set(mode = current_mode) # Reload previous mode
188 if automirror.cut:
189 bpy.ops.object.modifier_add(type = 'MIRROR') # Add a mirror modifier
190 bpy.context.object.modifiers[-1].use_axis[0] = X # Choose the axis to use, based on the cut's axis
191 bpy.context.object.modifiers[-1].use_axis[1] = Y
192 bpy.context.object.modifiers[-1].use_axis[2] = Z
193 bpy.context.object.modifiers[-1].use_clip = automirror.Use_Matcap
194 bpy.context.object.modifiers[-1].show_on_cage = automirror.show_on_cage
195 if automirror.apply_mirror:
196 bpy.ops.object.mode_set(mode = 'OBJECT')
197 bpy.ops.object.modifier_apply(modifier = bpy.context.object.modifiers[-1].name)
198 if automirror.toggle_edit:
199 bpy.ops.object.mode_set(mode = 'EDIT')
200 else:
201 bpy.ops.object.mode_set(mode = current_mode)
203 return {'FINISHED'}
206 # Panel
208 class VIEW3D_PT_BisectMirror(Panel):
209 bl_space_type = 'VIEW_3D'
210 bl_region_type = 'UI'
211 bl_label = "Auto Mirror"
212 bl_category = 'Edit'
213 bl_options = {'DEFAULT_CLOSED'}
215 def draw(self, context):
216 automirror = context.scene.automirror
218 layout = self.layout
219 col = layout.column(align=True)
221 layout = self.layout
223 if bpy.context.object and bpy.context.object.type == 'MESH':
224 layout.operator("object.automirror")
225 layout.prop(automirror, "axis", text = "Mirror Axis", expand=True)
226 layout.prop(automirror, "orientation", text = "Orientation")
227 layout.prop(automirror, "threshold", text = "Threshold")
228 layout.prop(automirror, "toggle_edit", text = "Toggle Edit")
229 layout.prop(automirror, "cut", text = "Cut and Mirror")
230 if bpy.context.scene.automirror.cut:
231 layout.prop(automirror, "Use_Matcap", text = "Use Clip")
232 layout.prop(automirror, "show_on_cage", text = "Editable")
233 layout.prop(automirror, "apply_mirror", text = "Apply Mirror")
235 else:
236 layout.label(icon="ERROR", text = "No mesh selected")
238 # Properties
239 class AutoMirrorProps(PropertyGroup):
240 axis : EnumProperty(
241 items = [("x", "X", "", 1),
242 ("y", "Y", "", 2),
243 ("z", "Z", "", 3)],
244 description="Axis used by the mirror modifier",
247 orientation : EnumProperty(
248 items = [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
249 description="Choose the side along the axis of the editable part (+/- coordinates)",
252 threshold : FloatProperty(
253 default= 0.001, min= 0.001,
254 description="Vertices closer than this distance are merged on the loopcut",
257 toggle_edit : BoolProperty(
258 default= False,
259 description="If not in edit mode, change mode to edit",
262 cut : BoolProperty(
263 default= True,
264 description="If enabeled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
267 clipping : BoolProperty(
268 default=True,
271 Use_Matcap : BoolProperty(
272 default=True,
273 description="Use clipping for the mirror modifier",
276 show_on_cage : BoolProperty(
277 default=False,
278 description="Enable to edit the cage (it's the classical modifier's option)",
281 apply_mirror : BoolProperty(
282 description="Apply the mirror modifier (useful to symmetrise the mesh)",
286 # Add-ons Preferences Update Panel
288 # Define Panel classes for updating
289 panels = (
290 VIEW3D_PT_BisectMirror,
294 def update_panel(self, context):
295 message = ": Updating Panel locations has failed"
296 try:
297 for panel in panels:
298 if "bl_rna" in panel.__dict__:
299 bpy.utils.unregister_class(panel)
301 for panel in panels:
302 panel.bl_category = context.preferences.addons[__name__].preferences.category
303 bpy.utils.register_class(panel)
305 except Exception as e:
306 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
307 pass
310 class AutoMirrorAddonPreferences(AddonPreferences):
311 # this must match the addon name, use '__package__'
312 # when defining this in a submodule of a python package.
313 bl_idname = __name__
315 category: StringProperty(
316 name = "Tab Category",
317 description = "Choose a name for the category of the panel",
318 default = "Edit",
319 update = update_panel
322 def draw(self, context):
323 layout = self.layout
325 row = layout.row()
326 col = row.column()
327 col.label(text = "Tab Category:")
328 col.prop(self, "category", text = "")
330 # define classes for registration
331 classes = (
332 VIEW3D_PT_BisectMirror,
333 AutoMirror,
334 AlignVertices,
335 AutoMirrorAddonPreferences,
336 AutoMirrorProps,
340 # registering and menu integration
341 def register():
342 for cls in classes:
343 bpy.utils.register_class(cls)
345 bpy.types.Scene.automirror = PointerProperty(type = AutoMirrorProps)
346 update_panel(None, bpy.context)
348 # unregistering and removing menus
349 def unregister():
350 for cls in reversed(classes):
351 bpy.utils.unregister_class(cls)
353 del bpy.types.Scene.automirror
355 if __name__ == "__main__":
356 register()