# SPDX-License-Identifier: GPL-2.0-or-later # based upon the functionality of Mesh to wall by luxuy_BlenderCN # thanks to meta-androcto bl_info = { "name": "Edge Floor Plan", "author": "lijenstina", "version": (0, 2), "blender": (2, 78, 0), "location": "View3D > EditMode > Mesh", "description": "Make a Floor Plan from Edges", "doc_url": "", "category": "Mesh", } import bpy import bmesh from bpy.types import Operator from bpy.props import ( BoolProperty, EnumProperty, FloatProperty, FloatVectorProperty, IntProperty, ) # Handle error notifications def error_handlers(self, error, reports="ERROR"): if self and reports: self.report({'WARNING'}, reports + " (See Console for more info)") print("\n[mesh.edges_floor_plan]\nError: {}\n".format(error)) class MESH_OT_edges_floor_plan(Operator): bl_idname = "mesh.edges_floor_plan" bl_label = "Edges Floor Plan" bl_description = "Top View, Extrude Flat Along Edges" bl_options = {'REGISTER', 'UNDO'} wid: FloatProperty( name="Wall width:", description="Set the width of the generated walls\n", default=0.1, min=0.001, max=30000 ) depth: FloatProperty( name="Inner height:", description="Set the height of the inner wall edges", default=0.0, min=0, max=10 ) connect_ends: BoolProperty( name="Connect Ends", description="Connect the ends of the boundary Edge loops", default=False ) repeat_cleanup: IntProperty( name="Recursive Prepare", description="Number of times that the preparation phase runs\n" "at the start of the script\n" "If parts of the mesh are not modified, increase this value", min=1, max=20, default=1 ) fill_items = [ ('EDGE_NET', "Edge Net", "Edge Net Method for mesh preparation - Initial Fill\n" "The filled in faces will be Inset individually\n" "Supports simple 3D objects"), ('SINGLE_FACE', "Single Face", "Single Face Method for mesh preparation - Initial Fill\n" "The produced face will be Triangulated before Inset Region\n" "Good for edges forming a circle, avoid 3D objects"), ('SOLIDIFY', "Solidify", "Extrude and Solidify Method\n" "Useful for complex meshes, however works best on flat surfaces\n" "as the extrude direction has to be defined") ] fill_type: EnumProperty( name="Fill Type", items=fill_items, description="Choose the method for creating geometry", default='SOLIDIFY' ) keep_faces: BoolProperty( name="Keep Faces", description="Keep or not the fill faces\n" "Can depend on Remove Ngons state", default=False ) tri_faces: BoolProperty( name="Triangulate Faces", description="Triangulate the created fill faces\n" "Sometimes can lead to unsatisfactory results", default=False ) initial_extrude: FloatVectorProperty( name="Initial Extrude", description="", default=(0.0, 0.0, 0.1), min=-20.0, max=20.0, subtype='XYZ', precision=3, size=3 ) remove_ngons: BoolProperty( name="Remove Ngons", description="Keep or not the Ngon Faces\n" "Note about limitations:\n" "Sometimes the kept Faces could be Ngons\n" "Removing the Ngons can lead to no geometry created", default=True ) offset: FloatProperty( name="Wall Offset:", description="Set the offset for the Solidify modifier", default=0.0, min=-1.0, max=1.0 ) only_rim: BoolProperty( name="Rim Only", description="Solidify Fill Rim only option", default=False ) @classmethod def poll(cls, context): ob = context.active_object return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') def check_edge(self, context): bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='EDIT') obj = bpy.context.object me_check = obj.data if len(me_check.edges) < 1: return False return True @staticmethod def ensure(bm): if bm: bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() def solidify_mod(self, context, ob, wid, offset, only_rim): try: mods = ob.modifiers.new( name="_Mesh_Solidify_Wall", type='SOLIDIFY' ) mods.thickness = wid mods.use_quality_normals = True mods.offset = offset mods.use_even_offset = True mods.use_rim = True mods.use_rim_only = only_rim mods.show_on_cage = True bpy.ops.object.modifier_apply( modifier="_Mesh_Solidify_Wall" ) except Exception as e: error_handlers(self, e, reports="Adding a Solidify Modifier failed") pass def draw(self, context): layout = self.layout box = layout.box() box.label(text="Choose Method:", icon="NONE") box.prop(self, "fill_type") col = box.column(align=True) if self.fill_type == 'EDGE_NET': col.prop(self, "repeat_cleanup") col.prop(self, "remove_ngons", toggle=True) elif self.fill_type == 'SOLIDIFY': col.prop(self, "offset", slider=True) col.prop(self, "initial_extrude") else: col.prop(self, "remove_ngons", toggle=True) col.prop(self, "tri_faces", toggle=True) box = layout.box() box.label(text="Settings:", icon="NONE") col = box.column(align=True) col.prop(self, "wid") if self.fill_type != 'SOLIDIFY': col.prop(self, "depth") col.prop(self, "connect_ends", toggle=True) col.prop(self, "keep_faces", toggle=True) else: col.prop(self, "only_rim", toggle=True) def execute(self, context): if not self.check_edge(context): self.report({'WARNING'}, "Operation Cancelled. Needs a Mesh with at least one edge") return {'CANCELLED'} wid = self.wid * 0.1 depth = self.depth * 0.1 offset = self.offset * 0.1 store_selection_mode = context.tool_settings.mesh_select_mode # Note: the remove_doubles called after bmesh creation would make # blender crash with certain meshes - keep it in mind for the future bpy.ops.mesh.remove_doubles(threshold=0.003) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='EDIT') ob = bpy.context.object me = ob.data bm = bmesh.from_edit_mesh(me) bmesh.ops.delete(bm, geom=bm.faces, context='FACES_ONLY') self.ensure(bm) context.tool_settings.mesh_select_mode = (False, True, False) original_edges = [edge.index for edge in bm.edges] original_verts = [vert.index for vert in bm.verts] self.ensure(bm) bpy.ops.mesh.select_all(action='DESELECT') if self.fill_type == 'EDGE_NET': for i in range(self.repeat_cleanup): bmesh.ops.edgenet_prepare(bm, edges=bm.edges) self.ensure(bm) bmesh.ops.edgenet_fill(bm, edges=bm.edges, mat_nr=0, use_smooth=True, sides=0) self.ensure(bm) if self.remove_ngons: ngons = [face for face in bm.faces if len(face.edges) > 4] self.ensure(bm) bmesh.ops.delete(bm, geom=ngons, context='FACES') # 5 - delete faces del ngons self.ensure(bm) elif self.fill_type == 'SOLIDIFY': for vert in bm.verts: vert.normal_update() self.ensure(bm) bmesh.ops.extrude_edge_only( bm, edges=bm.edges, use_select_history=False ) self.ensure(bm) verts_extrude = [vert for vert in bm.verts if vert.index in original_verts] self.ensure(bm) bmesh.ops.translate( bm, verts=verts_extrude, vec=(self.initial_extrude) ) self.ensure(bm) del verts_extrude self.ensure(bm) for edge in bm.edges: if edge.is_boundary: edge.select = True bm = bmesh.update_edit_mesh(ob.data, loop_triangles=True, destructive=True) bpy.ops.object.mode_set(mode='OBJECT') self.solidify_mod(context, ob, wid, offset, self.only_rim) bpy.ops.object.mode_set(mode='EDIT') context.tool_settings.mesh_select_mode = store_selection_mode return {'FINISHED'} else: bm.faces.new(bm.verts) self.ensure(bm) if self.tri_faces: bmesh.ops.triangle_fill( bm, use_beauty=True, use_dissolve=False, edges=bm.edges ) self.ensure(bm) if self.remove_ngons and self.fill_type != 'EDGE_NET': ngons = [face for face in bm.faces if len(face.edges) > 4] self.ensure(bm) bmesh.ops.delete(bm, geom=ngons, context='FACES') # 5 - delete faces del ngons self.ensure(bm) del_boundary = [edge for edge in bm.edges if edge.index not in original_edges] self.ensure(bm) del original_edges self.ensure(bm) if self.fill_type == 'EDGE_NET': extrude_inner = bmesh.ops.inset_individual( bm, faces=bm.faces, thickness=wid, depth=depth, use_even_offset=True, use_interpolate=False, use_relative_offset=False ) else: extrude_inner = bmesh.ops.inset_region( bm, faces=bm.faces, faces_exclude=[], use_boundary=True, use_even_offset=True, use_interpolate=False, use_relative_offset=False, use_edge_rail=False, thickness=wid, depth=depth, use_outset=False ) self.ensure(bm) del_faces = [faces for faces in bm.faces if faces not in extrude_inner["faces"]] self.ensure(bm) del extrude_inner self.ensure(bm) if not self.keep_faces: bmesh.ops.delete(bm, geom=del_faces, context='FACES') # 5 delete faces del del_faces self.ensure(bm) face_del = set() for face in bm.faces: for edge in del_boundary: if isinstance(edge, bmesh.types.BMEdge): if edge in face.edges: face_del.add(face) self.ensure(bm) face_del = list(face_del) self.ensure(bm) del del_boundary self.ensure(bm) if not self.connect_ends: bmesh.ops.delete(bm, geom=face_del, context='FACES') self.ensure(bm) del face_del self.ensure(bm) for edge in bm.edges: if edge.is_boundary: edge.select = True bm = bmesh.update_edit_mesh(ob.data, loop_triangles=True, destructive=True) context.tool_settings.mesh_select_mode = store_selection_mode return {'FINISHED'} def register(): bpy.utils.register_class(MESH_OT_edges_floor_plan) def unregister(): bpy.utils.unregister_class(MESH_OT_edges_floor_plan) if __name__ == "__main__": register()