Skip to content
Snippets Groups Projects
mesh_edges_floor_plan.py 12.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • # 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()