Welcome to mirror list, hosted at ThFree Co, Russian Federation.

mesh_edges_floor_plan.py « mesh_tools - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: bff943d23ea4275d04236a97f979f4f6ec2ee201 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# 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()