mesh_tools: add mesh_edges_floor_plan update to 2.8
authormeta-androcto <meta.androcto1@gmail.com>
Sun, 2 Jun 2019 05:42:30 +0000 (15:42 +1000)
committermeta-androcto <meta.androcto1@gmail.com>
Sun, 2 Jun 2019 05:42:30 +0000 (15:42 +1000)
mesh_tools/__init__.py
mesh_tools/mesh_edges_floor_plan.py [new file with mode: 0644]

index d1c5468..28bef77 100644 (file)
@@ -45,6 +45,7 @@ if "bpy" in locals():
     importlib.reload(mesh_extrude_and_reshape)
     importlib.reload(mesh_edge_roundifier)
     importlib.reload(mesh_edgetools)
+    importlib.reload(mesh_edges_floor_plan)
 
 
 else:
@@ -56,6 +57,7 @@ else:
     from . import mesh_extrude_and_reshape
     from . import mesh_edge_roundifier
     from . import mesh_edgetools
+    from . import mesh_edges_floor_plan
 
 
 import bmesh
@@ -942,6 +944,8 @@ class VIEW3D_PT_edit_mesh_tools(Panel):
             row = col_top.row(align=True)
             row.operator('mesh.edge_roundifier', text="Edge Roundify")
             row = col_top.row(align=True)
+            row.operator('mesh.edges_floor_plan', text="Edges Floor Plan")
+            row = col_top.row(align=True)
             row.operator("mesh.extrude_edges_move", text="Extrude Edges")
             row = col_top.row(align=True)
             row.operator("mesh.bevel", text="Bevel Edges").vertex_only = False
@@ -1115,6 +1119,8 @@ def register():
     mesh_extrude_and_reshape.register()
     mesh_edge_roundifier.register()
     mesh_edgetools.register()
+    mesh_edges_floor_plan.register()
+
 
 # unregistering and removing menus
 def unregister():
@@ -1135,6 +1141,7 @@ def unregister():
     mesh_extrude_and_reshape.unregister()
     mesh_edge_roundifier.unregister()
     mesh_edgetools.unregister()
+    mesh_edges_floor_plan.unregister()
 
 if __name__ == "__main__":
     register()
diff --git a/mesh_tools/mesh_edges_floor_plan.py b/mesh_tools/mesh_edges_floor_plan.py
new file mode 100644 (file)
index 0000000..1bbd774
--- /dev/null
@@ -0,0 +1,384 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; version 2
+#  of the License.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# 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",
+    "wiki_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, 1, 1)
+
+            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, 1, 1)
+
+        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()