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 # Add a mirror modifier
191 mirror_modifier
= bpy
.context
.object.modifiers
.new("", 'MIRROR')
192 mirror_modifier
.use_axis
[0] = X
# Choose the axis to use, based on the cut's axis
193 mirror_modifier
.use_axis
[1] = Y
194 mirror_modifier
.use_axis
[2] = Z
195 mirror_modifier
.use_clip
= automirror
.Use_Matcap
196 mirror_modifier
.show_on_cage
= automirror
.show_on_cage
197 if automirror
.apply_mirror
:
198 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
199 bpy
.ops
.object.modifier_apply(modifier
= bpy
.context
.object.modifiers
[-1].name
)
200 if automirror
.toggle_edit
:
201 bpy
.ops
.object.mode_set(mode
= 'EDIT')
203 bpy
.ops
.object.mode_set(mode
= current_mode
)
210 class VIEW3D_PT_BisectMirror(Panel
):
211 bl_space_type
= 'VIEW_3D'
212 bl_region_type
= 'UI'
213 bl_label
= "Auto Mirror"
215 bl_options
= {'DEFAULT_CLOSED'}
217 def draw(self
, context
):
218 automirror
= context
.scene
.automirror
221 col
= layout
.column(align
=True)
225 if bpy
.context
.object and bpy
.context
.object.type == 'MESH':
226 layout
.operator("object.automirror")
227 layout
.prop(automirror
, "axis", text
= "Mirror Axis", expand
=True)
228 layout
.prop(automirror
, "orientation", text
= "Orientation")
229 layout
.prop(automirror
, "threshold", text
= "Threshold")
230 layout
.prop(automirror
, "toggle_edit", text
= "Toggle Edit")
231 layout
.prop(automirror
, "cut", text
= "Cut and Mirror")
232 if bpy
.context
.scene
.automirror
.cut
:
233 layout
.prop(automirror
, "Use_Matcap", text
= "Use Clip")
234 layout
.prop(automirror
, "show_on_cage", text
= "Editable")
235 layout
.prop(automirror
, "apply_mirror", text
= "Apply Mirror")
238 layout
.label(icon
="ERROR", text
= "No mesh selected")
241 class AutoMirrorProps(PropertyGroup
):
243 items
= [("x", "X", "", 1),
246 description
="Axis used by the mirror modifier",
249 orientation
: EnumProperty(
250 items
= [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
251 description
="Choose the side along the axis of the editable part (+/- coordinates)",
254 threshold
: FloatProperty(
255 default
= 0.001, min= 0.001,
256 description
="Vertices closer than this distance are merged on the loopcut",
259 toggle_edit
: BoolProperty(
261 description
="If not in edit mode, change mode to edit",
266 description
="If enabled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
269 clipping
: BoolProperty(
273 Use_Matcap
: BoolProperty(
275 description
="Use clipping for the mirror modifier",
278 show_on_cage
: BoolProperty(
280 description
="Enable to edit the cage (it's the classical modifier's option)",
283 apply_mirror
: BoolProperty(
284 description
="Apply the mirror modifier (useful to symmetrise the mesh)",
288 # Add-ons Preferences Update Panel
290 # Define Panel classes for updating
292 VIEW3D_PT_BisectMirror
,
296 def update_panel(self
, context
):
297 message
= ": Updating Panel locations has failed"
300 if "bl_rna" in panel
.__dict
__:
301 bpy
.utils
.unregister_class(panel
)
304 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
305 bpy
.utils
.register_class(panel
)
307 except Exception as e
:
308 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
312 class AutoMirrorAddonPreferences(AddonPreferences
):
313 # this must match the addon name, use '__package__'
314 # when defining this in a submodule of a python package.
317 category
: StringProperty(
318 name
= "Tab Category",
319 description
= "Choose a name for the category of the panel",
321 update
= update_panel
324 def draw(self
, context
):
329 col
.label(text
= "Tab Category:")
330 col
.prop(self
, "category", text
= "")
332 # define classes for registration
334 VIEW3D_PT_BisectMirror
,
337 AutoMirrorAddonPreferences
,
342 # registering and menu integration
345 bpy
.utils
.register_class(cls
)
347 bpy
.types
.Scene
.automirror
= PointerProperty(type = AutoMirrorProps
)
348 update_panel(None, bpy
.context
)
350 # unregistering and removing menus
352 for cls
in reversed(classes
):
353 bpy
.utils
.unregister_class(cls
)
355 del bpy
.types
.Scene
.automirror
357 if __name__
== "__main__":