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