Fix FBX char type being interpreted as bool
[blender-addons.git] / mesh_auto_mirror.py
blobbfc4e6f59d41b6dbee6d75e7c4067ffa7e8605d9
1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-3.0-or-later
5 ######################################################################################################
6 # A simple add-on to auto cut in two and mirror an object #
7 # Actually partially uncommented (see further version) #
8 # Author: Lapineige, Bookyakuno #
9 ######################################################################################################
10 # 2.8 update by Bookyakuno, meta-androcto
12 bl_info = {
13 "name": "Auto Mirror",
14 "description": "Super fast cutting and mirroring for mesh",
15 "author": "Lapineige",
16 "version": (2, 5, 4),
17 "blender": (2, 80, 0),
18 "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
19 "warning": "",
20 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html",
21 "category": "Mesh",
25 import bpy
26 from mathutils import Vector
28 import bmesh
29 import bpy
30 import collections
31 import mathutils
32 import math
33 from bpy_extras import view3d_utils
34 from bpy.types import (
35 Operator,
36 Menu,
37 Panel,
38 AddonPreferences,
39 PropertyGroup,
41 from bpy.props import (
42 BoolProperty,
43 EnumProperty,
44 FloatProperty,
45 IntProperty,
46 PointerProperty,
47 StringProperty,
51 # Operator
53 class AlignVertices(Operator):
55 """ Automatically cut an object along an axis """
57 bl_idname = "object.align_vertices"
58 bl_label = "Align Vertices on 1 Axis"
60 @classmethod
61 def poll(cls, context):
62 obj = context.active_object
63 return obj and obj.type == "MESH"
65 def execute(self, context):
66 automirror = context.scene.automirror
68 bpy.ops.object.mode_set(mode = 'OBJECT')
70 x1,y1,z1 = bpy.context.scene.cursor.location
71 bpy.ops.view3d.snap_cursor_to_selected()
73 x2,y2,z2 = bpy.context.scene.cursor.location
75 bpy.context.scene.cursor.location[0], \
76 bpy.context.scene.cursor.location[1], \
77 bpy.context.scene.cursor.location[2] = 0, 0, 0
79 #Vertices coordinate to 0 (local coordinate, so on the origin)
80 for vert in bpy.context.object.data.vertices:
81 if vert.select:
82 if automirror.axis == 'x':
83 axis = 0
84 elif automirror.axis == 'y':
85 axis = 1
86 elif automirror.axis == 'z':
87 axis = 2
88 vert.co[axis] = 0
90 bpy.context.scene.cursor.location = x2,y2,z2
92 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
94 bpy.context.scene.cursor.location = x1,y1,z1
96 bpy.ops.object.mode_set(mode = 'EDIT')
97 return {'FINISHED'}
100 class AutoMirror(bpy.types.Operator):
101 """ Automatically cut an object along an axis """
102 bl_idname = "object.automirror"
103 bl_label = "AutoMirror"
104 bl_options = {'REGISTER'} # 'UNDO' ?
106 @classmethod
107 def poll(cls, context):
108 obj = context.active_object
109 return obj and obj.type == "MESH"
111 def draw(self, context):
112 automirror = context.scene.automirror
114 layout = self.layout
115 if bpy.context.object and bpy.context.object.type == 'MESH':
116 layout.prop(automirror, "axis", text = "Mirror axis")
117 layout.prop(automirror, "orientation", text = "Orientation")
118 layout.prop(automirror, "threshold", text = "Threshold")
119 layout.prop(automirror, "toggle_edit", text = "Toggle edit")
120 layout.prop(automirror, "cut", text = "Cut and mirror")
121 if automirror.cut:
122 layout.prop(automirror, "clipping", text = "Clipping")
123 layout.prop(automirror, "mirror", text = "Apply mirror")
125 else:
126 layout.label(icon = "ERROR", text = "No mesh selected")
128 def get_local_axis_vector(self, context, X, Y, Z, orientation):
129 loc = context.object.location
130 bpy.ops.object.mode_set(mode = "OBJECT") # Needed to avoid to translate vertices
132 v1 = Vector((loc[0],loc[1],loc[2]))
133 bpy.ops.transform.translate(value = (X*orientation, Y*orientation, Z*orientation),
134 constraint_axis = ((X==1), (Y==1), (Z==1)),
135 orient_type = 'LOCAL')
136 v2 = Vector((loc[0],loc[1],loc[2]))
137 bpy.ops.transform.translate(value = (-X*orientation, -Y*orientation, -Z*orientation),
138 constraint_axis = ((X==1), (Y==1), (Z==1)),
139 orient_type = 'LOCAL')
141 bpy.ops.object.mode_set(mode="EDIT")
142 return v2-v1
144 def execute(self, context):
145 context.active_object.select_set(True)
147 automirror = context.scene.automirror
149 X,Y,Z = 0,0,0
151 if automirror.axis == 'x':
152 X = 1
153 elif automirror.axis == 'y':
154 Y = 1
155 elif automirror.axis == 'z':
156 Z = 1
158 current_mode = bpy.context.object.mode # Save the current mode
160 if bpy.context.object.mode != "EDIT":
161 bpy.ops.object.mode_set(mode = "EDIT") # Go to edit mode
163 bpy.ops.mesh.select_all(action = 'SELECT') # Select all the vertices
165 if automirror.orientation == 'positive':
166 orientation = 1
167 else:
168 orientation = -1
170 cut_normal = self.get_local_axis_vector(context, X, Y, Z, orientation)
172 # Cut the mesh
173 bpy.ops.mesh.bisect(
174 plane_co = (
175 bpy.context.object.location[0],
176 bpy.context.object.location[1],
177 bpy.context.object.location[2]
179 plane_no = cut_normal,
180 use_fill = False,
181 clear_inner = automirror.cut,
182 clear_outer = 0,
183 threshold = automirror.threshold
186 bpy.ops.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"
188 if not automirror.toggle_edit:
189 bpy.ops.object.mode_set(mode = current_mode) # Reload previous mode
191 if automirror.cut:
192 # Add a mirror modifier
193 mirror_modifier = bpy.context.object.modifiers.new("", 'MIRROR')
194 mirror_modifier.use_axis[0] = X # Choose the axis to use, based on the cut's axis
195 mirror_modifier.use_axis[1] = Y
196 mirror_modifier.use_axis[2] = Z
197 mirror_modifier.use_clip = automirror.Use_Matcap
198 mirror_modifier.show_on_cage = automirror.show_on_cage
199 if automirror.apply_mirror:
200 bpy.ops.object.mode_set(mode = 'OBJECT')
201 bpy.ops.object.modifier_apply(modifier = bpy.context.object.modifiers[-1].name)
202 if automirror.toggle_edit:
203 bpy.ops.object.mode_set(mode = 'EDIT')
204 else:
205 bpy.ops.object.mode_set(mode = current_mode)
207 return {'FINISHED'}
210 # Panel
212 class VIEW3D_PT_BisectMirror(Panel):
213 bl_space_type = 'VIEW_3D'
214 bl_region_type = 'UI'
215 bl_label = "Auto Mirror"
216 bl_category = 'Edit'
217 bl_options = {'DEFAULT_CLOSED'}
219 def draw(self, context):
220 automirror = context.scene.automirror
222 layout = self.layout
223 col = layout.column(align=True)
225 layout = self.layout
227 if bpy.context.object and bpy.context.object.type == 'MESH':
228 layout.operator("object.automirror")
229 layout.prop(automirror, "axis", text = "Mirror Axis", expand=True)
230 layout.prop(automirror, "orientation", text = "Orientation")
231 layout.prop(automirror, "threshold", text = "Threshold")
232 layout.prop(automirror, "toggle_edit", text = "Toggle Edit")
233 layout.prop(automirror, "cut", text = "Cut and Mirror")
234 if bpy.context.scene.automirror.cut:
235 layout.prop(automirror, "Use_Matcap", text = "Use Clip")
236 layout.prop(automirror, "show_on_cage", text = "Editable")
237 layout.prop(automirror, "apply_mirror", text = "Apply Mirror")
239 else:
240 layout.label(icon="ERROR", text = "No mesh selected")
242 # Properties
243 class AutoMirrorProps(PropertyGroup):
244 axis : EnumProperty(
245 items = [("x", "X", "", 1),
246 ("y", "Y", "", 2),
247 ("z", "Z", "", 3)],
248 description="Axis used by the mirror modifier",
251 orientation : EnumProperty(
252 items = [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
253 description="Choose the side along the axis of the editable part (+/- coordinates)",
256 threshold : FloatProperty(
257 default= 0.001, min= 0.001,
258 description="Vertices closer than this distance are merged on the loopcut",
261 toggle_edit : BoolProperty(
262 default= False,
263 description="If not in edit mode, change mode to edit",
266 cut : BoolProperty(
267 default= True,
268 description="If enabled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
271 clipping : BoolProperty(
272 default=True,
275 Use_Matcap : BoolProperty(
276 default=True,
277 description="Use clipping for the mirror modifier",
280 show_on_cage : BoolProperty(
281 default=False,
282 description="Enable to edit the cage (it's the classical modifier's option)",
285 apply_mirror : BoolProperty(
286 description="Apply the mirror modifier (useful to symmetrise the mesh)",
290 # Add-ons Preferences Update Panel
292 # Define Panel classes for updating
293 panels = (
294 VIEW3D_PT_BisectMirror,
298 def update_panel(self, context):
299 message = ": Updating Panel locations has failed"
300 try:
301 for panel in panels:
302 if "bl_rna" in panel.__dict__:
303 bpy.utils.unregister_class(panel)
305 for panel in panels:
306 panel.bl_category = context.preferences.addons[__name__].preferences.category
307 bpy.utils.register_class(panel)
309 except Exception as e:
310 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
311 pass
314 class AutoMirrorAddonPreferences(AddonPreferences):
315 # this must match the addon name, use '__package__'
316 # when defining this in a submodule of a python package.
317 bl_idname = __name__
319 category: StringProperty(
320 name = "Tab Category",
321 description = "Choose a name for the category of the panel",
322 default = "Edit",
323 update = update_panel
326 def draw(self, context):
327 layout = self.layout
329 row = layout.row()
330 col = row.column()
331 col.label(text = "Tab Category:")
332 col.prop(self, "category", text = "")
334 # define classes for registration
335 classes = (
336 VIEW3D_PT_BisectMirror,
337 AutoMirror,
338 AlignVertices,
339 AutoMirrorAddonPreferences,
340 AutoMirrorProps,
344 # registering and menu integration
345 def register():
346 for cls in classes:
347 bpy.utils.register_class(cls)
349 bpy.types.Scene.automirror = PointerProperty(type = AutoMirrorProps)
350 update_panel(None, bpy.context)
352 # unregistering and removing menus
353 def unregister():
354 for cls in reversed(classes):
355 bpy.utils.unregister_class(cls)
357 del bpy.types.Scene.automirror
359 if __name__ == "__main__":
360 register()