diff --git a/src/blenderbim/Makefile b/src/blenderbim/Makefile index 214c998009..ff6834ee17 100644 --- a/src/blenderbim/Makefile +++ b/src/blenderbim/Makefile @@ -655,6 +655,13 @@ endif cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/ rm -rf dist/working + # Required for hipped roof generation + mkdir dist/working + cd dist/working && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip + cd dist/working && unzip master.zip + cp -r dist/working/bpypolyskel-master/bpypolyskel dist/blenderbim/libs/site/packages/ + rm -rf dist/working + cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./* rm -rf dist/blenderbim diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index b2239eb128..dcff87044a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -25,6 +25,7 @@ from . import ( array, product, wall, + roof, slab, space, stair, @@ -48,7 +49,6 @@ classes = ( product.AddEmptyType, product.AlignProduct, product.ChangeTypePage, - product.GenerateHippedRoof, product.LoadTypeThumbnails, workspace.Hotkey, wall.AlignWall, @@ -77,6 +77,7 @@ classes = ( profile.ExtendProfile, profile.RecalculateProfile, profile.Rotate90, + roof.GenerateHippedRoof, slab.DisableEditingExtrusionProfile, slab.DisableEditingSketchExtrusionProfile, slab.EditExtrusionProfile, diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index dea07e116f..6d93cb8ee5 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -516,93 +516,3 @@ def ensure_material_unassigned(usecase_path, ifc_file, settings): obj.active_material_index = i - total_removed bpy.ops.object.material_slot_remove({"object": obj}) total_removed += 1 - - -class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.generate_hipped_roof" - bl_label = "Generate Hipped Roof" - bl_options = {"REGISTER"} - mode: bpy.props.StringProperty(default="ANGLE") - height: bpy.props.FloatProperty(default=1) - angle: bpy.props.FloatProperty(default=10) - - def _execute(self, context): - import bmesh - import shapely - from math import tan, radians - from bpypolyskel import bpypolyskel - - obj = bpy.context.active_object - if not obj: - return - - boundary_lines = [] - - for edge in obj.data.edges: - boundary_lines.append( - shapely.LineString([obj.data.vertices[edge.vertices[0]].co, obj.data.vertices[edge.vertices[1]].co]) - ) - - unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) - closed_polygons = shapely.polygonize(unioned_boundaries.geoms) - - roof_polygon = None - biggest_area = 0 - for polygon in closed_polygons.geoms: - area = polygon.area - if area > biggest_area: - roof_polygon = polygon - biggest_area = area - - roof_polygon = shapely.force_3d(roof_polygon) - - if not shapely.is_ccw(roof_polygon): - roof_polygon = roof_polygon.reverse() - - # Define vertices for the base footprint of the building at height 0.0 - # counterclockwise order - verts = [Vector(v) for v in roof_polygon.exterior.coords[0:-1]] - total_exterior_verts = len(verts) - next_index = total_exterior_verts - - inner_loops = None - for interior in roof_polygon.interiors: - if inner_loops is None: - inner_loops = [] - loop = interior.coords[0:-1] - total_verts = len(loop) - verts.extend([Vector(v) for v in loop]) - inner_loops.append((next_index, total_verts)) - next_index += total_verts - - unit_vectors = None # we have no unit vectors, let them computed by polygonize() - start_exterior_index = 0 - - faces = [] - - if self.mode == "HEIGHT": - height = self.height - angle = 0.0 - else: - angle = tan(radians(round(self.angle, 4))) - height = 0.0 - - faces = bpypolyskel.polygonize( - verts, start_exterior_index, total_exterior_verts, inner_loops, height, angle, faces, unit_vectors - ) - - edges = [] - mesh = bpy.data.meshes.new(name="Building_with_Roof") - mesh.from_pydata(verts, edges, faces) - - bm = bmesh.new() - bm.from_mesh(mesh) - - extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces) - extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)] - bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts) - - bm.to_mesh(mesh) - bm.free() - - obj.data = mesh diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py new file mode 100644 index 0000000000..d003c3f9c5 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -0,0 +1,111 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2023 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on 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 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on 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 BlenderBIM Add-on. If not, see . + + +import bpy +import bmesh +import shapely +import blenderbim.tool as tool +from math import tan, radians +from mathutils import Vector, Matrix +from bpypolyskel import bpypolyskel + + +class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.generate_hipped_roof" + bl_label = "Generate Hipped Roof" + bl_options = {"REGISTER"} + mode: bpy.props.StringProperty(default="ANGLE") + height: bpy.props.FloatProperty(default=1) + angle: bpy.props.FloatProperty(default=10) + + def _execute(self, context): + obj = bpy.context.active_object + if not obj: + return + + boundary_lines = [] + + for edge in obj.data.edges: + boundary_lines.append( + shapely.LineString([obj.data.vertices[edge.vertices[0]].co, obj.data.vertices[edge.vertices[1]].co]) + ) + + unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) + closed_polygons = shapely.polygonize(unioned_boundaries.geoms) + + roof_polygon = None + biggest_area = 0 + for polygon in closed_polygons.geoms: + area = polygon.area + if area > biggest_area: + roof_polygon = polygon + biggest_area = area + + roof_polygon = shapely.force_3d(roof_polygon) + + if not shapely.is_ccw(roof_polygon): + roof_polygon = roof_polygon.reverse() + + # Define vertices for the base footprint of the building at height 0.0 + # counterclockwise order + verts = [Vector(v) for v in roof_polygon.exterior.coords[0:-1]] + total_exterior_verts = len(verts) + next_index = total_exterior_verts + + inner_loops = None + for interior in roof_polygon.interiors: + if inner_loops is None: + inner_loops = [] + loop = interior.coords[0:-1] + total_verts = len(loop) + verts.extend([Vector(v) for v in loop]) + inner_loops.append((next_index, total_verts)) + next_index += total_verts + + unit_vectors = None # we have no unit vectors, let them computed by polygonize() + start_exterior_index = 0 + + faces = [] + + if self.mode == "HEIGHT": + height = self.height + angle = 0.0 + else: + angle = tan(radians(round(self.angle, 4))) + height = 0.0 + + faces = bpypolyskel.polygonize( + verts, start_exterior_index, total_exterior_verts, inner_loops, height, angle, faces, unit_vectors + ) + + edges = [] + mesh = bpy.data.meshes.new(name="Building_with_Roof") + mesh.from_pydata(verts, edges, faces) + + bm = bmesh.new() + bm.from_mesh(mesh) + + extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces) + extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)] + bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts) + + bm.to_mesh(mesh) + bm.free() + + obj.data = mesh