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 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
18 "Py/Scripts/Modeling/AutoMirror",
23 from mathutils
import Vector
30 from bpy_extras
import view3d_utils
31 from bpy
.types
import (
37 from bpy
.props
import (
49 class AlignVertices(bpy
.types
.Operator
):
51 """ Automatically cut an object along an axis """
53 bl_idname
= "object.align_vertices"
54 bl_label
= "Align Vertices on 1 Axis"
57 def poll(cls
, context
):
58 obj
= context
.active_object
59 return obj
and obj
.type == "MESH"
61 def execute(self
, context
):
62 bpy
.ops
.object.mode_set(mode
= 'OBJECT')
64 x1
,y1
,z1
= bpy
.context
.scene
.cursor
.location
65 bpy
.ops
.view3d
.snap_cursor_to_selected()
67 x2
,y2
,z2
= bpy
.context
.scene
.cursor
.location
69 bpy
.context
.scene
.cursor
.location
[0], \
70 bpy
.context
.scene
.cursor
.location
[1], \
71 bpy
.context
.scene
.cursor
.location
[2] = 0, 0, 0
73 #Vertices coordinate to 0 (local coordinate, so on the origin)
74 for vert
in bpy
.context
.object.data
.vertices
:
76 if bpy
.context
.scene
.AutoMirror_axis
== 'x':
78 elif bpy
.context
.scene
.AutoMirror_axis
== 'y':
80 elif bpy
.context
.scene
.AutoMirror_axis
== 'z':
84 bpy
.context
.scene
.cursor
.location
= x2
,y2
,z2
86 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
88 bpy
.context
.scene
.cursor
.location
= x1
,y1
,z1
90 bpy
.ops
.object.mode_set(mode
= 'EDIT')
94 class AutoMirror(bpy
.types
.Operator
):
95 """ Automatically cut an object along an axis """
96 bl_idname
= "object.automirror"
97 bl_label
= "AutoMirror"
98 bl_options
= {'REGISTER'} # 'UNDO' ?
101 def poll(cls
, context
):
102 obj
= context
.active_object
103 return obj
and obj
.type == "MESH"
105 def draw(self
, context
):
107 if bpy
.context
.object and bpy
.context
.object.type == 'MESH':
108 layout
.prop(context
.scene
, "AutoMirror_axis", text
="Mirror axis")
109 layout
.prop(context
.scene
, "AutoMirror_orientation", text
="Orientation")
110 layout
.prop(context
.scene
, "AutoMirror_threshold", text
="Threshold")
111 layout
.prop(context
.scene
, "AutoMirror_toggle_edit", text
="Toggle edit")
112 layout
.prop(context
.scene
, "AutoMirror_cut", text
="Cut and mirror")
113 if bpy
.context
.scene
.AutoMirror_cut
:
114 layout
.prop(context
.scene
, "AutoMirror_clipping", text
="Clipping")
115 layout
.prop(context
.scene
, "AutoMirror_apply_mirror", text
="Apply mirror")
118 layout
.label(icon
="ERROR", text
="No mesh selected")
120 def get_local_axis_vector(self
, context
, X
, Y
, Z
, orientation
):
121 loc
= context
.object.location
122 bpy
.ops
.object.mode_set(mode
="OBJECT") # Needed to avoid to translate vertices
124 v1
= Vector((loc
[0],loc
[1],loc
[2]))
125 bpy
.ops
.transform
.translate(value
=(X
*orientation
, Y
*orientation
, Z
*orientation
),
126 constraint_axis
=((X
==1), (Y
==1), (Z
==1)),
128 v2
= Vector((loc
[0],loc
[1],loc
[2]))
129 bpy
.ops
.transform
.translate(value
=(-X
*orientation
, -Y
*orientation
, -Z
*orientation
),
130 constraint_axis
=((X
==1), (Y
==1), (Z
==1)),
133 bpy
.ops
.object.mode_set(mode
="EDIT")
136 def execute(self
, context
):
138 if bpy
.context
.scene
.AutoMirror_axis
== 'x':
140 elif bpy
.context
.scene
.AutoMirror_axis
== 'y':
142 elif bpy
.context
.scene
.AutoMirror_axis
== 'z':
145 current_mode
= bpy
.context
.object.mode
# Save the current mode
147 if bpy
.context
.object.mode
!= "EDIT":
148 bpy
.ops
.object.mode_set(mode
="EDIT") # Go to edit mode
149 bpy
.ops
.mesh
.select_all(action
='SELECT') # Select all the vertices
150 if bpy
.context
.scene
.AutoMirror_orientation
== 'positive':
154 cut_normal
= self
.get_local_axis_vector(context
, X
, Y
, Z
, orientation
)
159 bpy
.context
.object.location
[0],
160 bpy
.context
.object.location
[1],
161 bpy
.context
.object.location
[2]
165 clear_inner
= bpy
.context
.scene
.AutoMirror_cut
,
167 threshold
= bpy
.context
.scene
.AutoMirror_threshold
)
169 bpy
.ops
.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"
171 if not bpy
.context
.scene
.AutoMirror_toggle_edit
:
172 bpy
.ops
.object.mode_set(mode
=current_mode
) # Reload previous mode
174 if bpy
.context
.scene
.AutoMirror_cut
:
175 bpy
.ops
.object.modifier_add(type='MIRROR') # Add a mirror modifier
176 bpy
.context
.object.modifiers
[-1].use_axis
[0] = X
# Choose the axis to use, based on the cut's axis
177 bpy
.context
.object.modifiers
[-1].use_axis
[1] = Y
178 bpy
.context
.object.modifiers
[-1].use_axis
[2] = Z
179 bpy
.context
.object.modifiers
[-1].use_clip
= context
.scene
.Use_Matcap
180 bpy
.context
.object.modifiers
[-1].show_on_cage
= context
.scene
.AutoMirror_show_on_cage
181 if bpy
.context
.scene
.AutoMirror_apply_mirror
:
182 bpy
.ops
.object.mode_set(mode
='OBJECT')
183 bpy
.ops
.object.modifier_apply(apply_as
= 'DATA', modifier
= bpy
.context
.object.modifiers
[-1].name
)
184 if bpy
.context
.scene
.AutoMirror_toggle_edit
:
185 bpy
.ops
.object.mode_set(mode
='EDIT')
187 bpy
.ops
.object.mode_set(mode
=current_mode
)
194 class VIEW3D_PT_BisectMirror(Panel
):
195 bl_space_type
= 'VIEW_3D'
196 bl_region_type
= 'UI'
197 bl_label
= "Auto Mirror"
199 bl_options
= {'DEFAULT_CLOSED'}
202 def draw(self
, context
):
204 col
= layout
.column(align
=True)
207 if bpy
.context
.object and bpy
.context
.object.type == 'MESH':
209 layout
.operator("object.automirror")
210 layout
.prop(context
.scene
, "AutoMirror_axis", text
="Mirror Axis", expand
=True)
211 layout
.prop(context
.scene
, "AutoMirror_orientation", text
="Orientation")
212 layout
.prop(context
.scene
, "AutoMirror_threshold", text
="Threshold")
213 layout
.prop(context
.scene
, "AutoMirror_toggle_edit", text
="Toggle Edit")
214 layout
.prop(context
.scene
, "AutoMirror_cut", text
="Cut and Mirror")
215 if bpy
.context
.scene
.AutoMirror_cut
:
216 layout
.prop(context
.scene
, "Use_Matcap", text
="Use Clip")
217 layout
.prop(context
.scene
, "AutoMirror_show_on_cage", text
="Editable")
218 layout
.prop(context
.scene
, "AutoMirror_apply_mirror", text
="Apply Mirror")
221 layout
.label(icon
="ERROR", text
="No mesh selected")
225 bpy
.types
.Scene
.AutoMirror_axis
= bpy
.props
.EnumProperty(
226 items
= [("x", "X", "", 1),("y", "Y", "", 2),("z", "Z", "", 3)],
227 description
="Axis used by the mirror modifier")
229 bpy
.types
.Scene
.AutoMirror_orientation
= bpy
.props
.EnumProperty(
230 items
= [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
231 description
="Choose the side along the axis of the editable part (+/- coordinates)")
233 bpy
.types
.Scene
.AutoMirror_threshold
= bpy
.props
.FloatProperty(
234 default
= 0.001, min= 0.001,
235 description
="Vertices closer than this distance are merged on the loopcut")
237 bpy
.types
.Scene
.AutoMirror_toggle_edit
= bpy
.props
.BoolProperty(
239 description
="If not in edit mode, change mode to edit")
241 bpy
.types
.Scene
.AutoMirror_cut
= bpy
.props
.BoolProperty(
243 description
="If enabeled, cut the mesh in two parts and mirror it. If not, just make a loopcut")
245 bpy
.types
.Scene
.AutoMirror_clipping
= bpy
.props
.BoolProperty(
247 bpy
.types
.Scene
.Use_Matcap
= bpy
.props
.BoolProperty(default
=True,
248 description
="Use clipping for the mirror modifier")
250 bpy
.types
.Scene
.AutoMirror_show_on_cage
= bpy
.props
.BoolProperty(
252 description
="Enable to edit the cage (it's the classical modifier's option)")
254 bpy
.types
.Scene
.AutoMirror_apply_mirror
= bpy
.props
.BoolProperty(
256 description
="Apply the mirror modifier (useful to symmetrise the mesh)")
259 # Add-ons Preferences Update Panel
261 # Define Panel classes for updating
263 VIEW3D_PT_BisectMirror
,
267 def update_panel(self
, context
):
268 message
= ": Updating Panel locations has failed"
271 if "bl_rna" in panel
.__dict
__:
272 bpy
.utils
.unregister_class(panel
)
275 panel
.bl_category
= context
.preferences
.addons
[__name__
].preferences
.category
276 bpy
.utils
.register_class(panel
)
278 except Exception as e
:
279 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
283 class AutoMirrorAddonPreferences(AddonPreferences
):
284 # this must match the addon name, use '__package__'
285 # when defining this in a submodule of a python package.
288 category
: StringProperty(
290 description
="Choose a name for the category of the panel",
295 def draw(self
, context
):
300 col
.label(text
="Tab Category:")
301 col
.prop(self
, "category", text
="")
303 # define classes for registration
305 VIEW3D_PT_BisectMirror
,
308 AutoMirrorAddonPreferences
312 # registering and menu integration
315 bpy
.utils
.register_class(cls
)
316 update_panel(None, bpy
.context
)
318 # unregistering and removing menus
320 for cls
in reversed(classes
):
321 bpy
.utils
.unregister_class(cls
)
323 if __name__
== "__main__":