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
13 "name": "Auto Mirror",
14 "description": "Super fast cutting and mirroring for mesh",
15 "author": "Lapineige",
17 "blender": (2, 80, 0),
18 "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
20 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html",
26 from mathutils
import Vector
33 from bpy_extras
import view3d_utils
34 from bpy
.types
import (
41 from bpy
.props
import (
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"
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
:
82 if automirror
.axis
== 'x':
84 elif automirror
.axis
== 'y':
86 elif automirror
.axis
== 'z':
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')
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' ?
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
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")
122 layout
.prop(automirror
, "clipping", text
= "Clipping")
123 layout
.prop(automirror
, "mirror", text
= "Apply mirror")
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")
144 def execute(self
, context
):
145 context
.active_object
.select_set(True)
147 automirror
= context
.scene
.automirror
151 if automirror
.axis
== 'x':
153 elif automirror
.axis
== 'y':
155 elif automirror
.axis
== 'z':
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':
170 cut_normal
= self
.get_local_axis_vector(context
, X
, Y
, Z
, orientation
)
175 bpy
.context
.object.location
[0],
176 bpy
.context
.object.location
[1],
177 bpy
.context
.object.location
[2]
179 plane_no
= cut_normal
,
181 clear_inner
= automirror
.cut
,
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
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')
205 bpy
.ops
.object.mode_set(mode
= current_mode
)
212 class VIEW3D_PT_BisectMirror(Panel
):
213 bl_space_type
= 'VIEW_3D'
214 bl_region_type
= 'UI'
215 bl_label
= "Auto Mirror"
217 bl_options
= {'DEFAULT_CLOSED'}
219 def draw(self
, context
):
220 automirror
= context
.scene
.automirror
223 col
= layout
.column(align
=True)
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")
240 layout
.label(icon
="ERROR", text
= "No mesh selected")
243 class AutoMirrorProps(PropertyGroup
):
245 items
= [("x", "X", "", 1),
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(
263 description
="If not in edit mode, change mode to edit",
268 description
="If enabled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
271 clipping
: BoolProperty(
275 Use_Matcap
: BoolProperty(
277 description
="Use clipping for the mirror modifier",
280 show_on_cage
: BoolProperty(
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
294 VIEW3D_PT_BisectMirror
,
298 def update_panel(self
, context
):
299 message
= ": Updating Panel locations has failed"
302 if "bl_rna" in panel
.__dict
__:
303 bpy
.utils
.unregister_class(panel
)
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
))
314 class AutoMirrorAddonPreferences(AddonPreferences
):
315 # this must match the addon name, use '__package__'
316 # when defining this in a submodule of a python package.
319 category
: StringProperty(
320 name
= "Tab Category",
321 description
= "Choose a name for the category of the panel",
323 update
= update_panel
326 def draw(self
, context
):
331 col
.label(text
= "Tab Category:")
332 col
.prop(self
, "category", text
= "")
334 # define classes for registration
336 VIEW3D_PT_BisectMirror
,
339 AutoMirrorAddonPreferences
,
344 # registering and menu integration
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
354 for cls
in reversed(classes
):
355 bpy
.utils
.unregister_class(cls
)
357 del bpy
.types
.Scene
.automirror
359 if __name__
== "__main__":