1 # SPDX-License-Identifier: GPL-3.0-or-later
3 ######################################################################################################
4 # A simple add-on to auto cut in two and mirror an object #
5 # Actually partially uncommented (see further version) #
6 # Author: Lapineige, Bookyakuno #
7 ######################################################################################################
8 # 2.8 update by Bookyakuno, meta-androcto
11 "name": "Auto Mirror",
12 "description": "Super fast cutting and mirroring for mesh",
13 "author": "Lapineige",
15 "blender": (2, 80, 0),
16 "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
18 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html",
24 from mathutils
import Vector
31 from bpy_extras
import view3d_utils
32 from bpy
.types
import (
39 from bpy
.props
import (
51 class AlignVertices(Operator
):
53 """ Automatically cut an object along an axis """
55 bl_idname
= "object.align_vertices"
56 bl_label
= "Align Vertices on 1 Axis"
59 def poll(cls
, context
):
60 obj
= context
.active_object
61 return obj
and obj
.type == "MESH"
63 def execute(self
, context
):
64 automirror
= context
.scene
.automirror
66 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
68 x1
,y1
,z1
= bpy
.context
.scene
.cursor
.location
69 bpy
.ops
.view3d
.snap_cursor_to_selected()
71 x2
,y2
,z2
= bpy
.context
.scene
.cursor
.location
73 bpy
.context
.scene
.cursor
.location
[0], \
74 bpy
.context
.scene
.cursor
.location
[1], \
75 bpy
.context
.scene
.cursor
.location
[2] = 0, 0, 0
77 #Vertices coordinate to 0 (local coordinate, so on the origin)
78 for vert
in bpy
.context
.object.data
.vertices
:
80 if automirror
.axis
== 'x':
82 elif automirror
.axis
== 'y':
84 elif automirror
.axis
== 'z':
88 bpy
.context
.scene
.cursor
.location
= x2
,y2
,z2
90 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
92 bpy
.context
.scene
.cursor
.location
= x1
,y1
,z1
94 bpy
.ops
.object.mode_set(mode
= 'EDIT')
98 class AutoMirror(bpy
.types
.Operator
):
99 """ Automatically cut an object along an axis """
100 bl_idname
= "object.automirror"
101 bl_label
= "AutoMirror"
102 bl_options
= {'REGISTER'} # 'UNDO' ?
105 def poll(cls
, context
):
106 obj
= context
.active_object
107 return obj
and obj
.type == "MESH"
109 def draw(self
, context
):
110 automirror
= context
.scene
.automirror
113 if bpy
.context
.object and bpy
.context
.object.type == 'MESH':
114 layout
.prop(automirror
, "axis", text
= "Mirror axis")
115 layout
.prop(automirror
, "orientation", text
= "Orientation")
116 layout
.prop(automirror
, "threshold", text
= "Threshold")
117 layout
.prop(automirror
, "toggle_edit", text
= "Toggle edit")
118 layout
.prop(automirror
, "cut", text
= "Cut and mirror")
120 layout
.prop(automirror
, "clipping", text
= "Clipping")
121 layout
.prop(automirror
, "mirror", text
= "Apply mirror")
124 layout
.label(icon
= "ERROR", text
= "No mesh selected")
126 def get_local_axis_vector(self
, context
, X
, Y
, Z
, orientation
):
127 loc
= context
.object.location
128 bpy
.ops
.object.mode_set(mode
= "OBJECT") # Needed to avoid to translate vertices
130 v1
= Vector((loc
[0],loc
[1],loc
[2]))
131 bpy
.ops
.transform
.translate(value
= (X
*orientation
, Y
*orientation
, Z
*orientation
),
132 constraint_axis
= ((X
==1), (Y
==1), (Z
==1)),
133 orient_type
= 'LOCAL')
134 v2
= Vector((loc
[0],loc
[1],loc
[2]))
135 bpy
.ops
.transform
.translate(value
= (-X
*orientation
, -Y
*orientation
, -Z
*orientation
),
136 constraint_axis
= ((X
==1), (Y
==1), (Z
==1)),
137 orient_type
= 'LOCAL')
139 bpy
.ops
.object.mode_set(mode
="EDIT")
142 def execute(self
, context
):
143 context
.active_object
.select_set(True)
145 automirror
= context
.scene
.automirror
149 if automirror
.axis
== 'x':
151 elif automirror
.axis
== 'y':
153 elif automirror
.axis
== 'z':
156 current_mode
= bpy
.context
.object.mode
# Save the current mode
158 if bpy
.context
.object.mode
!= "EDIT":
159 bpy
.ops
.object.mode_set(mode
= "EDIT") # Go to edit mode
161 bpy
.ops
.mesh
.select_all(action
= 'SELECT') # Select all the vertices
163 if automirror
.orientation
== 'positive':
168 cut_normal
= self
.get_local_axis_vector(context
, X
, Y
, Z
, orientation
)
173 bpy
.context
.object.location
[0],
174 bpy
.context
.object.location
[1],
175 bpy
.context
.object.location
[2]
177 plane_no
= cut_normal
,
179 clear_inner
= automirror
.cut
,
181 threshold
= automirror
.threshold
184 bpy
.ops
.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"
186 if not automirror
.toggle_edit
:
187 bpy
.ops
.object.mode_set(mode
= current_mode
) # Reload previous mode
190 bpy
.ops
.object.modifier_add(type = 'MIRROR') # Add a mirror modifier
191 bpy
.context
.object.modifiers
[-1].use_axis
[0] = X
# Choose the axis to use, based on the cut's axis
192 bpy
.context
.object.modifiers
[-1].use_axis
[1] = Y
193 bpy
.context
.object.modifiers
[-1].use_axis
[2] = Z
194 bpy
.context
.object.modifiers
[-1].use_clip
= automirror
.Use_Matcap
195 bpy
.context
.object.modifiers
[-1].show_on_cage
= automirror
.show_on_cage
196 if automirror
.apply_mirror
:
197 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
198 bpy
.ops
.object.modifier_apply(modifier
= bpy
.context
.object.modifiers
[-1].name
)
199 if automirror
.toggle_edit
:
200 bpy
.ops
.object.mode_set(mode
= 'EDIT')
202 bpy
.ops
.object.mode_set(mode
= current_mode
)
209 class VIEW3D_PT_BisectMirror(Panel
):
210 bl_space_type
= 'VIEW_3D'
211 bl_region_type
= 'UI'
212 bl_label
= "Auto Mirror"
214 bl_options
= {'DEFAULT_CLOSED'}
216 def draw(self
, context
):
217 automirror
= context
.scene
.automirror
220 col
= layout
.column(align
=True)
224 if bpy
.context
.object and bpy
.context
.object.type == 'MESH':
225 layout
.operator("object.automirror")
226 layout
.prop(automirror
, "axis", text
= "Mirror Axis", expand
=True)
227 layout
.prop(automirror
, "orientation", text
= "Orientation")
228 layout
.prop(automirror
, "threshold", text
= "Threshold")
229 layout
.prop(automirror
, "toggle_edit", text
= "Toggle Edit")
230 layout
.prop(automirror
, "cut", text
= "Cut and Mirror")
231 if bpy
.context
.scene
.automirror
.cut
:
232 layout
.prop(automirror
, "Use_Matcap", text
= "Use Clip")
233 layout
.prop(automirror
, "show_on_cage", text
= "Editable")
234 layout
.prop(automirror
, "apply_mirror", text
= "Apply Mirror")
237 layout
.label(icon
="ERROR", text
= "No mesh selected")
240 class AutoMirrorProps(PropertyGroup
):
242 items
= [("x", "X", "", 1),
245 description
="Axis used by the mirror modifier",
248 orientation
: EnumProperty(
249 items
= [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
250 description
="Choose the side along the axis of the editable part (+/- coordinates)",
253 threshold
: FloatProperty(
254 default
= 0.001, min= 0.001,
255 description
="Vertices closer than this distance are merged on the loopcut",
258 toggle_edit
: BoolProperty(
260 description
="If not in edit mode, change mode to edit",
265 description
="If enabled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
268 clipping
: BoolProperty(
272 Use_Matcap
: BoolProperty(
274 description
="Use clipping for the mirror modifier",
277 show_on_cage
: BoolProperty(
279 description
="Enable to edit the cage (it's the classical modifier's option)",
282 apply_mirror
: BoolProperty(
283 description
="Apply the mirror modifier (useful to symmetrise the mesh)",
287 # Add-ons Preferences Update Panel
289 # Define Panel classes for updating
291 VIEW3D_PT_BisectMirror
,
295 def update_panel(self
, context
):
296 message
= ": Updating Panel locations has failed"
299 if "bl_rna" in panel
.__dict
__:
300 bpy
.utils
.unregister_class(panel
)
303 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
304 bpy
.utils
.register_class(panel
)
306 except Exception as e
:
307 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
311 class AutoMirrorAddonPreferences(AddonPreferences
):
312 # this must match the addon name, use '__package__'
313 # when defining this in a submodule of a python package.
316 category
: StringProperty(
317 name
= "Tab Category",
318 description
= "Choose a name for the category of the panel",
320 update
= update_panel
323 def draw(self
, context
):
328 col
.label(text
= "Tab Category:")
329 col
.prop(self
, "category", text
= "")
331 # define classes for registration
333 VIEW3D_PT_BisectMirror
,
336 AutoMirrorAddonPreferences
,
341 # registering and menu integration
344 bpy
.utils
.register_class(cls
)
346 bpy
.types
.Scene
.automirror
= PointerProperty(type = AutoMirrorProps
)
347 update_panel(None, bpy
.context
)
349 # unregistering and removing menus
351 for cls
in reversed(classes
):
352 bpy
.utils
.unregister_class(cls
)
354 del bpy
.types
.Scene
.automirror
356 if __name__
== "__main__":