mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00

The add-on file had the license as GPL2, but for it to be compatible with Blender it has to be GPL or later.
386 lines
13 KiB
Python
386 lines
13 KiB
Python
# ##### 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; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# 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",
|
|
"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()
|