Fix T52833: OBJ triangulate doesn't match viewport
[blender-addons.git] / mesh_auto_mirror.py
blob032bff37950501168e73a277ddf63c990e2bf719
1 # ########################################################### #
2 # An simple add-on to auto cut in two and mirror an object #
3 # Actualy partialy uncommented (see further version) #
4 # Author: Lapineige #
5 # License: GPL v3 #
6 # ########################################################### #
8 bl_info = {
9 "name": "Auto Mirror",
10 "description": "Super fast cutting and mirroring for Mesh objects",
11 "author": "Lapineige",
12 "version": (2, 4, 2),
13 "blender": (2, 7, 1),
14 "location": "View 3D > Toolbar > Tools tab > AutoMirror (panel)",
15 "warning": "",
16 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
17 "Py/Scripts/Modeling/AutoMirror",
18 "category": "Mesh"}
21 import bpy
22 from bpy.props import (
23 BoolProperty,
24 EnumProperty,
25 FloatProperty,
26 PointerProperty,
28 from bpy.types import (
29 Operator,
30 Panel,
31 PropertyGroup,
33 from mathutils import Vector
36 # Operators
37 class AlignVertices(Operator):
38 bl_idname = "object.align_vertices"
39 bl_label = "Align Vertices on an Axis"
40 bl_description = ("Align Vertices on an Axis\n"
41 "Needs an Active Mesh Object")
43 @classmethod
44 def poll(cls, context):
45 obj = context.active_object
46 return obj and obj.type == "MESH"
48 def execute(self, context):
49 auto_m = context.scene.auto_mirror
50 bpy.ops.object.mode_set(mode='OBJECT')
52 x1, y1, z1 = bpy.context.scene.cursor_location
53 bpy.ops.view3d.snap_cursor_to_selected()
55 x2, y2, z2 = bpy.context.scene.cursor_location
57 bpy.context.scene.cursor_location[0], \
58 bpy.context.scene.cursor_location[1], \
59 bpy.context.scene.cursor_location[2] = 0, 0, 0
61 # Vertices coordinate to 0 (local coordinate, so on the origin)
62 for vert in bpy.context.object.data.vertices:
63 if vert.select:
64 if auto_m.axis == 'x':
65 axis = 0
66 elif auto_m.axis == 'y':
67 axis = 1
68 elif auto_m.axis == 'z':
69 axis = 2
70 vert.co[axis] = 0
72 bpy.context.scene.cursor_location = x2, y2, z2
73 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
75 bpy.context.scene.cursor_location = x1, y1, z1
76 bpy.ops.object.mode_set(mode='EDIT')
78 return {'FINISHED'}
81 class AutoMirror(Operator):
82 bl_idname = "object.automirror"
83 bl_label = "AutoMirror"
84 bl_description = ("Automatically cut an object along an axis\n"
85 "Needs an Active Mesh Object")
86 bl_options = {'REGISTER'}
88 @classmethod
89 def poll(cls, context):
90 obj = context.active_object
91 return obj and obj.type == "MESH"
93 def get_local_axis_vector(self, context, X, Y, Z, orientation):
94 loc = context.object.location
95 bpy.ops.object.mode_set(mode="OBJECT") # Needed to avoid to translate vertices
97 v1 = Vector((loc[0], loc[1], loc[2]))
98 bpy.ops.transform.translate(
99 value=(X * orientation, Y * orientation, Z * orientation),
100 constraint_axis=((X == 1), (Y == 1), (Z == 1)),
101 constraint_orientation='LOCAL'
103 v2 = Vector((loc[0], loc[1], loc[2]))
104 bpy.ops.transform.translate(
105 value=(-X * orientation, -Y * orientation, -Z * orientation),
106 constraint_axis=((X == 1), (Y == 1), (Z == 1)),
107 constraint_orientation='LOCAL'
110 bpy.ops.object.mode_set(mode="EDIT")
111 return v2 - v1
113 def execute(self, context):
114 auto_m = context.scene.auto_mirror
116 X, Y, Z = 0, 0, 0
117 if auto_m.axis == 'x':
118 X = 1
119 elif auto_m.axis == 'y':
120 Y = 1
121 elif auto_m.axis == 'z':
122 Z = 1
124 current_mode = bpy.context.object.mode # Save the current mode
126 if bpy.context.object.mode != "EDIT":
127 bpy.ops.object.mode_set(mode="EDIT") # Go to edit mode
129 bpy.ops.mesh.select_all(action='SELECT') # Select all the vertices
130 if auto_m.orientation == 'positive':
131 orientation = 1
132 else:
133 orientation = -1
134 cut_normal = self.get_local_axis_vector(context, X, Y, Z, orientation)
136 # Cut the mesh
137 bpy.ops.mesh.bisect(
138 plane_co=(
139 bpy.context.object.location[0],
140 bpy.context.object.location[1],
141 bpy.context.object.location[2]
143 plane_no=cut_normal,
144 use_fill=False,
145 clear_inner=auto_m.cut,
146 clear_outer=0,
147 threshold=auto_m.threshold
150 # Use to align the vertices on the origin, needed by the "threshold"
151 bpy.ops.object.align_vertices()
153 if not auto_m.toggle_edit:
154 bpy.ops.object.mode_set(mode=current_mode) # Reload previous mode
156 if auto_m.cut:
157 bpy.ops.object.modifier_add(type='MIRROR') # Add a mirror modifier
158 bpy.context.object.modifiers[-1].use_x = X # Choose the axis to use, based on the cut's axis
159 bpy.context.object.modifiers[-1].use_y = Y
160 bpy.context.object.modifiers[-1].use_z = Z
161 bpy.context.object.modifiers[-1].use_clip = auto_m.use_clip
162 bpy.context.object.modifiers[-1].show_on_cage = auto_m.show_on_cage
164 if auto_m.apply_mirror:
165 bpy.ops.object.mode_set(mode='OBJECT')
166 bpy.ops.object.modifier_apply(
167 apply_as='DATA',
168 modifier=bpy.context.object.modifiers[-1].name
170 if auto_m.toggle_edit:
171 bpy.ops.object.mode_set(mode='EDIT')
172 else:
173 bpy.ops.object.mode_set(mode=current_mode)
175 return {'FINISHED'}
178 # Panel
179 class BisectMirror(Panel):
180 bl_label = "Auto Mirror"
181 bl_space_type = 'VIEW_3D'
182 bl_region_type = 'TOOLS'
183 bl_category = "Tools"
184 bl_options = {"DEFAULT_CLOSED"}
186 def draw(self, context):
187 layout = self.layout
188 auto_m = context.scene.auto_mirror
189 obj = context.active_object
191 if obj and obj.type == 'MESH':
192 layout.operator("object.automirror", icon="MOD_MIRROR")
193 layout.label("Options:")
194 layout.prop(auto_m, "axis", text="Mirror Axis", expand=True)
195 layout.prop(auto_m, "orientation", text="Orientation")
196 layout.prop(auto_m, "threshold", text="Threshold")
197 layout.prop(auto_m, "toggle_edit", text="Toggle Edit")
198 layout.prop(auto_m, "cut", text="Cut and Mirror", toggle=True, icon="MOD_REMESH")
200 if auto_m.cut:
201 col = layout.column(align=True)
202 row = col.row(align=True)
203 row.prop(auto_m, "use_clip", text="Use Clip", toggle=True)
204 row.prop(auto_m, "show_on_cage", text="Editable", toggle=True)
205 col.prop(auto_m, "apply_mirror", text="Apply Mirror", toggle=True)
206 else:
207 layout.label(icon="INFO", text="No Mesh selected")
210 class AutoMirrorProperties(PropertyGroup):
211 axis = EnumProperty(
212 name="Axis",
213 items=[
214 ("x", "X", "", 1),
215 ("y", "Y", "", 2),
216 ("z", "Z", "", 3)
218 description="Axis used by the mirror modifier"
220 orientation = EnumProperty(
221 name="Orientation",
222 items=[
223 ("positive", "Positive", "", 1),
224 ("negative", "Negative", "", 2)
226 description="Choose the side along the axis of the editable part (+/- coordinates)"
228 threshold = FloatProperty(
229 default=0.001,
230 min=0.001,
231 description="Vertices closer than this distance are merged on the loopcut"
233 toggle_edit = BoolProperty(
234 name="Toggle Edit Mode",
235 default=True,
236 description="If not in Edit mode, change mode to it"
238 cut = BoolProperty(
239 name="Cut",
240 default=True,
241 description="If enabled, cut the mesh in two parts and mirror it\n"
242 "If not, just make a loopcut"
244 clipping = BoolProperty(
245 default=True
247 use_clip = BoolProperty(
248 default=True,
249 description="Use clipping for the mirror modifier"
251 show_on_cage = BoolProperty(
252 default=True,
253 description="Enable editing the cage (it's the classical modifier's option)"
255 apply_mirror = BoolProperty(
256 description="Apply the mirror modifier (useful to symmetrise the mesh)"
260 def register():
261 bpy.utils.register_class(BisectMirror)
262 bpy.utils.register_class(AutoMirror)
263 bpy.utils.register_class(AlignVertices)
264 bpy.utils.register_class(AutoMirrorProperties)
265 bpy.types.Scene.auto_mirror = PointerProperty(
266 type=AutoMirrorProperties
270 def unregister():
271 bpy.utils.unregister_class(BisectMirror)
272 bpy.utils.unregister_class(AutoMirror)
273 bpy.utils.unregister_class(AlignVertices)
274 bpy.utils.unregister_class(AutoMirrorProperties)
275 del bpy.types.Scene.auto_mirror
278 if __name__ == "__main__":
279 register()