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 automirror
= context
.scene
.automirror
146 if automirror
.axis
== 'x':
148 elif automirror
.axis
== 'y':
150 elif automirror
.axis
== 'z':
153 current_mode
= bpy
.context
.object.mode
# Save the current mode
155 if bpy
.context
.object.mode
!= "EDIT":
156 bpy
.ops
.object.mode_set(mode
= "EDIT") # Go to edit mode
158 bpy
.ops
.mesh
.select_all(action
= 'SELECT') # Select all the vertices
160 if automirror
.orientation
== 'positive':
165 cut_normal
= self
.get_local_axis_vector(context
, X
, Y
, Z
, orientation
)
170 bpy
.context
.object.location
[0],
171 bpy
.context
.object.location
[1],
172 bpy
.context
.object.location
[2]
174 plane_no
= cut_normal
,
176 clear_inner
= automirror
.cut
,
178 threshold
= automirror
.threshold
181 bpy
.ops
.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"
183 if not automirror
.toggle_edit
:
184 bpy
.ops
.object.mode_set(mode
= current_mode
) # Reload previous mode
187 bpy
.ops
.object.modifier_add(type = 'MIRROR') # Add a mirror modifier
188 bpy
.context
.object.modifiers
[-1].use_axis
[0] = X
# Choose the axis to use, based on the cut's axis
189 bpy
.context
.object.modifiers
[-1].use_axis
[1] = Y
190 bpy
.context
.object.modifiers
[-1].use_axis
[2] = Z
191 bpy
.context
.object.modifiers
[-1].use_clip
= automirror
.Use_Matcap
192 bpy
.context
.object.modifiers
[-1].show_on_cage
= automirror
.show_on_cage
193 if automirror
.apply_mirror
:
194 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
195 bpy
.ops
.object.modifier_apply(apply_as
= 'DATA',
196 modifier
= bpy
.context
.object.modifiers
[-1].name
)
197 if automirror
.toggle_edit
:
198 bpy
.ops
.object.mode_set(mode
= 'EDIT')
200 bpy
.ops
.object.mode_set(mode
= current_mode
)
207 class VIEW3D_PT_BisectMirror(Panel
):
208 bl_space_type
= 'VIEW_3D'
209 bl_region_type
= 'UI'
210 bl_label
= "Auto Mirror"
212 bl_options
= {'DEFAULT_CLOSED'}
214 def draw(self
, context
):
215 automirror
= context
.scene
.automirror
218 col
= layout
.column(align
=True)
222 if bpy
.context
.object and bpy
.context
.object.type == 'MESH':
223 layout
.operator("object.automirror")
224 layout
.prop(automirror
, "axis", text
= "Mirror Axis", expand
=True)
225 layout
.prop(automirror
, "orientation", text
= "Orientation")
226 layout
.prop(automirror
, "threshold", text
= "Threshold")
227 layout
.prop(automirror
, "toggle_edit", text
= "Toggle Edit")
228 layout
.prop(automirror
, "cut", text
= "Cut and Mirror")
229 if bpy
.context
.scene
.automirror
.cut
:
230 layout
.prop(automirror
, "Use_Matcap", text
= "Use Clip")
231 layout
.prop(automirror
, "show_on_cage", text
= "Editable")
232 layout
.prop(automirror
, "apply_mirror", text
= "Apply Mirror")
235 layout
.label(icon
="ERROR", text
= "No mesh selected")
238 class AutoMirrorProps(PropertyGroup
):
240 items
= [("x", "X", "", 1),
243 description
="Axis used by the mirror modifier",
246 orientation
: EnumProperty(
247 items
= [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
248 description
="Choose the side along the axis of the editable part (+/- coordinates)",
251 threshold
: FloatProperty(
252 default
= 0.001, min= 0.001,
253 description
="Vertices closer than this distance are merged on the loopcut",
256 toggle_edit
: BoolProperty(
258 description
="If not in edit mode, change mode to edit",
263 description
="If enabeled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
266 clipping
: BoolProperty(
270 Use_Matcap
: BoolProperty(
272 description
="Use clipping for the mirror modifier",
275 show_on_cage
: BoolProperty(
277 description
="Enable to edit the cage (it's the classical modifier's option)",
280 apply_mirror
: BoolProperty(
281 description
="Apply the mirror modifier (useful to symmetrise the mesh)",
285 # Add-ons Preferences Update Panel
287 # Define Panel classes for updating
289 VIEW3D_PT_BisectMirror
,
293 def update_panel(self
, context
):
294 message
= ": Updating Panel locations has failed"
297 if "bl_rna" in panel
.__dict
__:
298 bpy
.utils
.unregister_class(panel
)
301 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
302 bpy
.utils
.register_class(panel
)
304 except Exception as e
:
305 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
309 class AutoMirrorAddonPreferences(AddonPreferences
):
310 # this must match the addon name, use '__package__'
311 # when defining this in a submodule of a python package.
314 category
: StringProperty(
315 name
= "Tab Category",
316 description
= "Choose a name for the category of the panel",
318 update
= update_panel
321 def draw(self
, context
):
326 col
.label(text
= "Tab Category:")
327 col
.prop(self
, "category", text
= "")
329 # define classes for registration
331 VIEW3D_PT_BisectMirror
,
334 AutoMirrorAddonPreferences
,
339 # registering and menu integration
342 bpy
.utils
.register_class(cls
)
344 bpy
.types
.Scene
.automirror
= PointerProperty(type = AutoMirrorProps
)
345 update_panel(None, bpy
.context
)
347 # unregistering and removing menus
349 for cls
in reversed(classes
):
350 bpy
.utils
.unregister_class(cls
)
352 del bpy
.types
.Scene
.automirror
354 if __name__
== "__main__":