Update for 2.8
[blender-addons.git] / mesh_extra_tools / mesh_edges_floor_plan.py
blobbc4b16105f3d744bce67a03a3cb1dd0f847368b2
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; version 2
6 # of the License.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # based upon the functionality of Mesh to wall by luxuy_BlenderCN
20 # thanks to meta-androcto
22 bl_info = {
23 "name": "Edge Floor Plan",
24 "author": "lijenstina",
25 "version": (0, 2),
26 "blender": (2, 78, 0),
27 "location": "View3D > EditMode > Mesh",
28 "description": "Make a Floor Plan from Edges",
29 "wiki_url": "",
30 "category": "Mesh"}
32 import bpy
33 import bmesh
34 from bpy.types import Operator
35 from bpy.props import (
36 BoolProperty,
37 EnumProperty,
38 FloatProperty,
39 FloatVectorProperty,
40 IntProperty,
44 # Handle error notifications
45 def error_handlers(self, error, reports="ERROR"):
46 if self and reports:
47 self.report({'WARNING'}, reports + " (See Console for more info)")
49 print("\n[mesh.edges_floor_plan]\nError: {}\n".format(error))
52 class MESH_OT_edges_floor_plan(Operator):
53 bl_idname = "mesh.edges_floor_plan"
54 bl_label = "Edges Floor Plan"
55 bl_description = "Top View, Extrude Flat Along Edges"
56 bl_options = {'REGISTER', 'UNDO'}
58 wid = FloatProperty(
59 name="Wall width:",
60 description="Set the width of the generated walls\n",
61 default=0.1,
62 min=0.001, max=30000
64 depth = FloatProperty(
65 name="Inner height:",
66 description="Set the height of the inner wall edges",
67 default=0.0,
68 min=0, max=10
70 connect_ends = BoolProperty(
71 name="Connect Ends",
72 description="Connect the ends of the boundary Edge loops",
73 default=False
75 repeat_cleanup = IntProperty(
76 name="Recursive Prepare",
77 description="Number of times that the preparation phase runs\n"
78 "at the start of the script\n"
79 "If parts of the mesh are not modified, increase this value",
80 min=1, max=20,
81 default=1
83 fill_items = [
84 ('EDGE_NET', "Edge Net",
85 "Edge Net Method for mesh preparation - Initial Fill\n"
86 "The filled in faces will be Inset individually\n"
87 "Supports simple 3D objects"),
88 ('SINGLE_FACE', "Single Face",
89 "Single Face Method for mesh preparation - Initial Fill\n"
90 "The produced face will be Triangulated before Inset Region\n"
91 "Good for edges forming a circle, avoid 3D objects"),
92 ('SOLIDIFY', "Solidify",
93 "Extrude and Solidify Method\n"
94 "Useful for complex meshes, however works best on flat surfaces\n"
95 "as the extrude direction has to be defined")
97 fill_type = EnumProperty(
98 name="Fill Type",
99 items=fill_items,
100 description="Choose the method for creating geometry",
101 default='SOLIDIFY'
103 keep_faces = BoolProperty(
104 name="Keep Faces",
105 description="Keep or not the fill faces\n"
106 "Can depend on Remove Ngons state",
107 default=False
109 tri_faces = BoolProperty(
110 name="Triangulate Faces",
111 description="Triangulate the created fill faces\n"
112 "Sometimes can lead to unsatisfactory results",
113 default=False
115 initial_extrude = FloatVectorProperty(
116 name="Initial Extrude",
117 description="",
118 default=(0.0, 0.0, 0.1),
119 min=-20.0, max=20.0,
120 subtype='XYZ',
121 precision=3,
122 size=3
124 remove_ngons = BoolProperty(
125 name="Remove Ngons",
126 description="Keep or not the Ngon Faces\n"
127 "Note about limitations:\n"
128 "Sometimes the kept Faces could be Ngons\n"
129 "Removing the Ngons can lead to no geometry created",
130 default=True
132 offset = FloatProperty(
133 name="Wall Offset:",
134 description="Set the offset for the Solidify modifier",
135 default=0.0,
136 min=-1.0, max=1.0
138 only_rim = BoolProperty(
139 name="Rim Only",
140 description="Solidify Fill Rim only option",
141 default=False
144 @classmethod
145 def poll(cls, context):
146 ob = context.active_object
147 return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
149 def check_edge(self, context):
150 bpy.ops.object.mode_set(mode='OBJECT')
151 bpy.ops.object.mode_set(mode='EDIT')
152 obj = bpy.context.object
153 me_check = obj.data
154 if len(me_check.edges) < 1:
155 return False
157 return True
159 @staticmethod
160 def ensure(bm):
161 if bm:
162 bm.verts.ensure_lookup_table()
163 bm.edges.ensure_lookup_table()
164 bm.faces.ensure_lookup_table()
166 def solidify_mod(self, context, ob, wid, offset, only_rim):
167 try:
168 mods = ob.modifiers.new(
169 name="_Mesh_Solidify_Wall", type='SOLIDIFY'
171 mods.thickness = wid
172 mods.use_quality_normals = True
173 mods.offset = offset
174 mods.use_even_offset = True
175 mods.use_rim = True
176 mods.use_rim_only = only_rim
177 mods.show_on_cage = True
179 bpy.ops.object.modifier_apply(
180 modifier="_Mesh_Solidify_Wall"
182 except Exception as e:
183 error_handlers(self, e,
184 reports="Adding a Solidify Modifier failed")
185 pass
187 def draw(self, context):
188 layout = self.layout
190 box = layout.box()
191 box.label(text="Choose Method:", icon="SCRIPTWIN")
192 box.prop(self, "fill_type")
194 col = box.column(align=True)
196 if self.fill_type == 'EDGE_NET':
197 col.prop(self, "repeat_cleanup")
198 col.prop(self, "remove_ngons", toggle=True)
200 elif self.fill_type == 'SOLIDIFY':
201 col.prop(self, "offset", slider=True)
202 col.prop(self, "initial_extrude")
204 else:
205 col.prop(self, "remove_ngons", toggle=True)
206 col.prop(self, "tri_faces", toggle=True)
208 box = layout.box()
209 box.label(text="Settings:", icon="MOD_BUILD")
211 col = box.column(align=True)
212 col.prop(self, "wid")
214 if self.fill_type != 'SOLIDIFY':
215 col.prop(self, "depth")
216 col.prop(self, "connect_ends", toggle=True)
217 col.prop(self, "keep_faces", toggle=True)
218 else:
219 col.prop(self, "only_rim", toggle=True)
221 def execute(self, context):
222 if not self.check_edge(context):
223 self.report({'WARNING'},
224 "Operation Cancelled. Needs a Mesh with at least one edge")
225 return {'CANCELLED'}
227 wid = self.wid * 0.1
228 depth = self.depth * 0.1
229 offset = self.offset * 0.1
230 store_selection_mode = context.tool_settings.mesh_select_mode
231 # Note: the remove_doubles called after bmesh creation would make
232 # blender crash with certain meshes - keep it in mind for the future
233 bpy.ops.mesh.remove_doubles(threshold=0.003)
234 bpy.ops.object.mode_set(mode='OBJECT')
235 bpy.ops.object.mode_set(mode='EDIT')
236 ob = bpy.context.object
238 me = ob.data
239 bm = bmesh.from_edit_mesh(me)
241 bmesh.ops.delete(bm, geom=bm.faces, context=3)
242 self.ensure(bm)
243 context.tool_settings.mesh_select_mode = (False, True, False)
244 original_edges = [edge.index for edge in bm.edges]
245 original_verts = [vert.index for vert in bm.verts]
246 self.ensure(bm)
247 bpy.ops.mesh.select_all(action='DESELECT')
249 if self.fill_type == 'EDGE_NET':
250 for i in range(self.repeat_cleanup):
251 bmesh.ops.edgenet_prepare(bm, edges=bm.edges)
252 self.ensure(bm)
253 bmesh.ops.edgenet_fill(bm, edges=bm.edges, mat_nr=0, use_smooth=True, sides=0)
254 self.ensure(bm)
255 if self.remove_ngons:
256 ngons = [face for face in bm.faces if len(face.edges) > 4]
257 self.ensure(bm)
258 bmesh.ops.delete(bm, geom=ngons, context=5) # 5 - delete faces
259 del ngons
260 self.ensure(bm)
262 elif self.fill_type == 'SOLIDIFY':
263 for vert in bm.verts:
264 vert.normal_update()
265 self.ensure(bm)
266 bmesh.ops.extrude_edge_only(
267 bm, edges=bm.edges, use_select_history=False
269 self.ensure(bm)
270 verts_extrude = [vert for vert in bm.verts if vert.index in original_verts]
271 self.ensure(bm)
272 bmesh.ops.translate(
274 verts=verts_extrude,
275 vec=(self.initial_extrude)
277 self.ensure(bm)
278 del verts_extrude
279 self.ensure(bm)
281 for edge in bm.edges:
282 if edge.is_boundary:
283 edge.select = True
285 bm = bmesh.update_edit_mesh(ob.data, 1, 1)
287 bpy.ops.object.mode_set(mode='OBJECT')
288 self.solidify_mod(context, ob, wid, offset, self.only_rim)
290 bpy.ops.object.mode_set(mode='EDIT')
292 context.tool_settings.mesh_select_mode = store_selection_mode
294 return {'FINISHED'}
296 else:
297 bm.faces.new(bm.verts)
298 self.ensure(bm)
300 if self.tri_faces:
301 bmesh.ops.triangle_fill(
302 bm, use_beauty=True, use_dissolve=False, edges=bm.edges
304 self.ensure(bm)
306 if self.remove_ngons and self.fill_type != 'EDGE_NET':
307 ngons = [face for face in bm.faces if len(face.edges) > 4]
308 self.ensure(bm)
309 bmesh.ops.delete(bm, geom=ngons, context=5) # 5 - delete faces
310 del ngons
311 self.ensure(bm)
313 del_boundary = [edge for edge in bm.edges if edge.index not in original_edges]
314 self.ensure(bm)
316 del original_edges
317 self.ensure(bm)
319 if self.fill_type == 'EDGE_NET':
320 extrude_inner = bmesh.ops.inset_individual(
321 bm, faces=bm.faces, thickness=wid, depth=depth,
322 use_even_offset=True, use_interpolate=False,
323 use_relative_offset=False
325 else:
326 extrude_inner = bmesh.ops.inset_region(
327 bm, faces=bm.faces, faces_exclude=[], use_boundary=True,
328 use_even_offset=True, use_interpolate=False,
329 use_relative_offset=False, use_edge_rail=False,
330 thickness=wid, depth=depth, use_outset=False
332 self.ensure(bm)
334 del_faces = [faces for faces in bm.faces if faces not in extrude_inner["faces"]]
335 self.ensure(bm)
336 del extrude_inner
337 self.ensure(bm)
339 if not self.keep_faces:
340 bmesh.ops.delete(bm, geom=del_faces, context=5) # 5 delete faces
341 del del_faces
342 self.ensure(bm)
344 face_del = set()
345 for face in bm.faces:
346 for edge in del_boundary:
347 if isinstance(edge, bmesh.types.BMEdge):
348 if edge in face.edges:
349 face_del.add(face)
350 self.ensure(bm)
351 face_del = list(face_del)
352 self.ensure(bm)
354 del del_boundary
355 self.ensure(bm)
357 if not self.connect_ends:
358 bmesh.ops.delete(bm, geom=face_del, context=5)
359 self.ensure(bm)
361 del face_del
362 self.ensure(bm)
364 for edge in bm.edges:
365 if edge.is_boundary:
366 edge.select = True
368 bm = bmesh.update_edit_mesh(ob.data, 1, 1)
370 context.tool_settings.mesh_select_mode = store_selection_mode
372 return {'FINISHED'}
375 def register():
376 bpy.utils.register_class(MESH_OT_edges_floor_plan)
379 def unregister():
380 bpy.utils.unregister_class(MESH_OT_edges_floor_plan)
383 if __name__ == "__main__":
384 register()