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 #
6 ######################################################################################################
7 # 2.8 update by Bookyakuno, meta-androcto
10 "name": "Auto Mirror",
11 "description": "Super fast cutting and mirroring for mesh",
12 "author": "Lapineige",
14 "blender": (2, 80, 0),
15 "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
17 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html",
23 from mathutils
import Vector
30 from bpy_extras
import view3d_utils
31 from bpy
.types
import (
38 from bpy
.props
import (
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"
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
:
79 if automirror
.axis
== 'x':
81 elif automirror
.axis
== 'y':
83 elif automirror
.axis
== 'z':
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')
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' ?
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
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")
119 layout
.prop(automirror
, "clipping", text
= "Clipping")
120 layout
.prop(automirror
, "mirror", text
= "Apply mirror")
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")
141 def execute(self
, context
):
142 context
.active_object
.select_set(True)
144 automirror
= context
.scene
.automirror
148 if automirror
.axis
== 'x':
150 elif automirror
.axis
== 'y':
152 elif automirror
.axis
== 'z':
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':
167 cut_normal
= self
.get_local_axis_vector(context
, X
, Y
, Z
, orientation
)
172 bpy
.context
.object.location
[0],
173 bpy
.context
.object.location
[1],
174 bpy
.context
.object.location
[2]
176 plane_no
= cut_normal
,
178 clear_inner
= automirror
.cut
,
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
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')
201 bpy
.ops
.object.mode_set(mode
= current_mode
)
208 class VIEW3D_PT_BisectMirror(Panel
):
209 bl_space_type
= 'VIEW_3D'
210 bl_region_type
= 'UI'
211 bl_label
= "Auto Mirror"
213 bl_options
= {'DEFAULT_CLOSED'}
215 def draw(self
, context
):
216 automirror
= context
.scene
.automirror
219 col
= layout
.column(align
=True)
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")
236 layout
.label(icon
="ERROR", text
= "No mesh selected")
239 class AutoMirrorProps(PropertyGroup
):
241 items
= [("x", "X", "", 1),
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(
259 description
="If not in edit mode, change mode to edit",
264 description
="If enabeled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
267 clipping
: BoolProperty(
271 Use_Matcap
: BoolProperty(
273 description
="Use clipping for the mirror modifier",
276 show_on_cage
: BoolProperty(
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
290 VIEW3D_PT_BisectMirror
,
294 def update_panel(self
, context
):
295 message
= ": Updating Panel locations has failed"
298 if "bl_rna" in panel
.__dict
__:
299 bpy
.utils
.unregister_class(panel
)
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
))
310 class AutoMirrorAddonPreferences(AddonPreferences
):
311 # this must match the addon name, use '__package__'
312 # when defining this in a submodule of a python package.
315 category
: StringProperty(
316 name
= "Tab Category",
317 description
= "Choose a name for the category of the panel",
319 update
= update_panel
322 def draw(self
, context
):
327 col
.label(text
= "Tab Category:")
328 col
.prop(self
, "category", text
= "")
330 # define classes for registration
332 VIEW3D_PT_BisectMirror
,
335 AutoMirrorAddonPreferences
,
340 # registering and menu integration
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
350 for cls
in reversed(classes
):
351 bpy
.utils
.unregister_class(cls
)
353 del bpy
.types
.Scene
.automirror
355 if __name__
== "__main__":