Cleanup: trailing space
[blender-addons.git] / mesh_tools / mesh_edges_floor_plan.py
blobbff943d23ea4275d04236a97f979f4f6ec2ee201
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # based upon the functionality of Mesh to wall by luxuy_BlenderCN
4 # thanks to meta-androcto
6 bl_info = {
7 "name": "Edge Floor Plan",
8 "author": "lijenstina",
9 "version": (0, 2),
10 "blender": (2, 78, 0),
11 "location": "View3D > EditMode > Mesh",
12 "description": "Make a Floor Plan from Edges",
13 "doc_url": "",
14 "category": "Mesh",
17 import bpy
18 import bmesh
19 from bpy.types import Operator
20 from bpy.props import (
21 BoolProperty,
22 EnumProperty,
23 FloatProperty,
24 FloatVectorProperty,
25 IntProperty,
29 # Handle error notifications
30 def error_handlers(self, error, reports="ERROR"):
31 if self and reports:
32 self.report({'WARNING'}, reports + " (See Console for more info)")
34 print("\n[mesh.edges_floor_plan]\nError: {}\n".format(error))
37 class MESH_OT_edges_floor_plan(Operator):
38 bl_idname = "mesh.edges_floor_plan"
39 bl_label = "Edges Floor Plan"
40 bl_description = "Top View, Extrude Flat Along Edges"
41 bl_options = {'REGISTER', 'UNDO'}
43 wid: FloatProperty(
44 name="Wall width:",
45 description="Set the width of the generated walls\n",
46 default=0.1,
47 min=0.001, max=30000
49 depth: FloatProperty(
50 name="Inner height:",
51 description="Set the height of the inner wall edges",
52 default=0.0,
53 min=0, max=10
55 connect_ends: BoolProperty(
56 name="Connect Ends",
57 description="Connect the ends of the boundary Edge loops",
58 default=False
60 repeat_cleanup: IntProperty(
61 name="Recursive Prepare",
62 description="Number of times that the preparation phase runs\n"
63 "at the start of the script\n"
64 "If parts of the mesh are not modified, increase this value",
65 min=1, max=20,
66 default=1
68 fill_items = [
69 ('EDGE_NET', "Edge Net",
70 "Edge Net Method for mesh preparation - Initial Fill\n"
71 "The filled in faces will be Inset individually\n"
72 "Supports simple 3D objects"),
73 ('SINGLE_FACE', "Single Face",
74 "Single Face Method for mesh preparation - Initial Fill\n"
75 "The produced face will be Triangulated before Inset Region\n"
76 "Good for edges forming a circle, avoid 3D objects"),
77 ('SOLIDIFY', "Solidify",
78 "Extrude and Solidify Method\n"
79 "Useful for complex meshes, however works best on flat surfaces\n"
80 "as the extrude direction has to be defined")
82 fill_type: EnumProperty(
83 name="Fill Type",
84 items=fill_items,
85 description="Choose the method for creating geometry",
86 default='SOLIDIFY'
88 keep_faces: BoolProperty(
89 name="Keep Faces",
90 description="Keep or not the fill faces\n"
91 "Can depend on Remove Ngons state",
92 default=False
94 tri_faces: BoolProperty(
95 name="Triangulate Faces",
96 description="Triangulate the created fill faces\n"
97 "Sometimes can lead to unsatisfactory results",
98 default=False
100 initial_extrude: FloatVectorProperty(
101 name="Initial Extrude",
102 description="",
103 default=(0.0, 0.0, 0.1),
104 min=-20.0, max=20.0,
105 subtype='XYZ',
106 precision=3,
107 size=3
109 remove_ngons: BoolProperty(
110 name="Remove Ngons",
111 description="Keep or not the Ngon Faces\n"
112 "Note about limitations:\n"
113 "Sometimes the kept Faces could be Ngons\n"
114 "Removing the Ngons can lead to no geometry created",
115 default=True
117 offset: FloatProperty(
118 name="Wall Offset:",
119 description="Set the offset for the Solidify modifier",
120 default=0.0,
121 min=-1.0, max=1.0
123 only_rim: BoolProperty(
124 name="Rim Only",
125 description="Solidify Fill Rim only option",
126 default=False
129 @classmethod
130 def poll(cls, context):
131 ob = context.active_object
132 return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
134 def check_edge(self, context):
135 bpy.ops.object.mode_set(mode='OBJECT')
136 bpy.ops.object.mode_set(mode='EDIT')
137 obj = bpy.context.object
138 me_check = obj.data
139 if len(me_check.edges) < 1:
140 return False
142 return True
144 @staticmethod
145 def ensure(bm):
146 if bm:
147 bm.verts.ensure_lookup_table()
148 bm.edges.ensure_lookup_table()
149 bm.faces.ensure_lookup_table()
151 def solidify_mod(self, context, ob, wid, offset, only_rim):
152 try:
153 mods = ob.modifiers.new(
154 name="_Mesh_Solidify_Wall", type='SOLIDIFY'
156 mods.thickness = wid
157 mods.use_quality_normals = True
158 mods.offset = offset
159 mods.use_even_offset = True
160 mods.use_rim = True
161 mods.use_rim_only = only_rim
162 mods.show_on_cage = True
164 bpy.ops.object.modifier_apply(
165 modifier="_Mesh_Solidify_Wall"
167 except Exception as e:
168 error_handlers(self, e,
169 reports="Adding a Solidify Modifier failed")
170 pass
172 def draw(self, context):
173 layout = self.layout
175 box = layout.box()
176 box.label(text="Choose Method:", icon="NONE")
177 box.prop(self, "fill_type")
179 col = box.column(align=True)
181 if self.fill_type == 'EDGE_NET':
182 col.prop(self, "repeat_cleanup")
183 col.prop(self, "remove_ngons", toggle=True)
185 elif self.fill_type == 'SOLIDIFY':
186 col.prop(self, "offset", slider=True)
187 col.prop(self, "initial_extrude")
189 else:
190 col.prop(self, "remove_ngons", toggle=True)
191 col.prop(self, "tri_faces", toggle=True)
193 box = layout.box()
194 box.label(text="Settings:", icon="NONE")
196 col = box.column(align=True)
197 col.prop(self, "wid")
199 if self.fill_type != 'SOLIDIFY':
200 col.prop(self, "depth")
201 col.prop(self, "connect_ends", toggle=True)
202 col.prop(self, "keep_faces", toggle=True)
203 else:
204 col.prop(self, "only_rim", toggle=True)
206 def execute(self, context):
207 if not self.check_edge(context):
208 self.report({'WARNING'},
209 "Operation Cancelled. Needs a Mesh with at least one edge")
210 return {'CANCELLED'}
212 wid = self.wid * 0.1
213 depth = self.depth * 0.1
214 offset = self.offset * 0.1
215 store_selection_mode = context.tool_settings.mesh_select_mode
216 # Note: the remove_doubles called after bmesh creation would make
217 # blender crash with certain meshes - keep it in mind for the future
218 bpy.ops.mesh.remove_doubles(threshold=0.003)
219 bpy.ops.object.mode_set(mode='OBJECT')
220 bpy.ops.object.mode_set(mode='EDIT')
221 ob = bpy.context.object
223 me = ob.data
224 bm = bmesh.from_edit_mesh(me)
226 bmesh.ops.delete(bm, geom=bm.faces, context='FACES_ONLY')
227 self.ensure(bm)
228 context.tool_settings.mesh_select_mode = (False, True, False)
229 original_edges = [edge.index for edge in bm.edges]
230 original_verts = [vert.index for vert in bm.verts]
231 self.ensure(bm)
232 bpy.ops.mesh.select_all(action='DESELECT')
234 if self.fill_type == 'EDGE_NET':
235 for i in range(self.repeat_cleanup):
236 bmesh.ops.edgenet_prepare(bm, edges=bm.edges)
237 self.ensure(bm)
238 bmesh.ops.edgenet_fill(bm, edges=bm.edges, mat_nr=0, use_smooth=True, sides=0)
239 self.ensure(bm)
240 if self.remove_ngons:
241 ngons = [face for face in bm.faces if len(face.edges) > 4]
242 self.ensure(bm)
243 bmesh.ops.delete(bm, geom=ngons, context='FACES') # 5 - delete faces
244 del ngons
245 self.ensure(bm)
247 elif self.fill_type == 'SOLIDIFY':
248 for vert in bm.verts:
249 vert.normal_update()
250 self.ensure(bm)
251 bmesh.ops.extrude_edge_only(
252 bm, edges=bm.edges, use_select_history=False
254 self.ensure(bm)
255 verts_extrude = [vert for vert in bm.verts if vert.index in original_verts]
256 self.ensure(bm)
257 bmesh.ops.translate(
259 verts=verts_extrude,
260 vec=(self.initial_extrude)
262 self.ensure(bm)
263 del verts_extrude
264 self.ensure(bm)
266 for edge in bm.edges:
267 if edge.is_boundary:
268 edge.select = True
270 bm = bmesh.update_edit_mesh(ob.data, loop_triangles=True, destructive=True)
272 bpy.ops.object.mode_set(mode='OBJECT')
273 self.solidify_mod(context, ob, wid, offset, self.only_rim)
275 bpy.ops.object.mode_set(mode='EDIT')
277 context.tool_settings.mesh_select_mode = store_selection_mode
279 return {'FINISHED'}
281 else:
282 bm.faces.new(bm.verts)
283 self.ensure(bm)
285 if self.tri_faces:
286 bmesh.ops.triangle_fill(
287 bm, use_beauty=True, use_dissolve=False, edges=bm.edges
289 self.ensure(bm)
291 if self.remove_ngons and self.fill_type != 'EDGE_NET':
292 ngons = [face for face in bm.faces if len(face.edges) > 4]
293 self.ensure(bm)
294 bmesh.ops.delete(bm, geom=ngons, context='FACES') # 5 - delete faces
295 del ngons
296 self.ensure(bm)
298 del_boundary = [edge for edge in bm.edges if edge.index not in original_edges]
299 self.ensure(bm)
301 del original_edges
302 self.ensure(bm)
304 if self.fill_type == 'EDGE_NET':
305 extrude_inner = bmesh.ops.inset_individual(
306 bm, faces=bm.faces, thickness=wid, depth=depth,
307 use_even_offset=True, use_interpolate=False,
308 use_relative_offset=False
310 else:
311 extrude_inner = bmesh.ops.inset_region(
312 bm, faces=bm.faces, faces_exclude=[], use_boundary=True,
313 use_even_offset=True, use_interpolate=False,
314 use_relative_offset=False, use_edge_rail=False,
315 thickness=wid, depth=depth, use_outset=False
317 self.ensure(bm)
319 del_faces = [faces for faces in bm.faces if faces not in extrude_inner["faces"]]
320 self.ensure(bm)
321 del extrude_inner
322 self.ensure(bm)
324 if not self.keep_faces:
325 bmesh.ops.delete(bm, geom=del_faces, context='FACES') # 5 delete faces
326 del del_faces
327 self.ensure(bm)
329 face_del = set()
330 for face in bm.faces:
331 for edge in del_boundary:
332 if isinstance(edge, bmesh.types.BMEdge):
333 if edge in face.edges:
334 face_del.add(face)
335 self.ensure(bm)
336 face_del = list(face_del)
337 self.ensure(bm)
339 del del_boundary
340 self.ensure(bm)
342 if not self.connect_ends:
343 bmesh.ops.delete(bm, geom=face_del, context='FACES')
344 self.ensure(bm)
346 del face_del
347 self.ensure(bm)
349 for edge in bm.edges:
350 if edge.is_boundary:
351 edge.select = True
353 bm = bmesh.update_edit_mesh(ob.data, loop_triangles=True, destructive=True)
355 context.tool_settings.mesh_select_mode = store_selection_mode
357 return {'FINISHED'}
360 def register():
361 bpy.utils.register_class(MESH_OT_edges_floor_plan)
364 def unregister():
365 bpy.utils.unregister_class(MESH_OT_edges_floor_plan)
368 if __name__ == "__main__":
369 register()