Roof Modifier - separating vertices on defining gable roof angle

Added option (which is activated by default) - when you set gable roof angle for some edge it will duplicate the top vertex instead of just moving which allows you to do shape like this - https://imgur.com/a/OEFZ4pw

Previously you would get something like this in that case - https://community.osarch.org/uploads/editor/ap/hlt70rbw5r1l.png
This commit is contained in:
Andrej730
2023-04-20 17:16:18 +05:00
parent 16b6861765
commit 26ab901a9e
3 changed files with 49 additions and 13 deletions
@@ -31,3 +31,4 @@ class BIMCadProperties(PropertyGroup):
gable_roof_edge_angle: bpy.props.FloatProperty( gable_roof_edge_angle: bpy.props.FloatProperty(
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE" name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
) )
gable_roof_separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
@@ -229,6 +229,7 @@ class CadHotkey(bpy.types.Operator):
and bpy.context.active_object.BIMRoofProperties.is_editing_path and bpy.context.active_object.BIMRoofProperties.is_editing_path
): ):
self.layout.row().prop(props, "gable_roof_edge_angle") self.layout.row().prop(props, "gable_roof_edge_angle")
self.layout.row().prop(props, "gable_roof_separate_verts")
elif self.hotkey == "S_V": elif self.hotkey == "S_V":
if not self.is_profile(): if not self.is_profile():
@@ -285,7 +286,9 @@ class CadHotkey(bpy.types.Operator):
and RoofData.data["parameters"] and RoofData.data["parameters"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path and bpy.context.active_object.BIMRoofProperties.is_editing_path
): ):
bpy.ops.bim.set_gable_roof_edge_angle(angle=self.props.gable_roof_edge_angle) bpy.ops.bim.set_gable_roof_edge_angle(
angle=self.props.gable_roof_edge_angle, separate_verts=self.props.gable_roof_separate_verts
)
def hotkey_S_T(self): def hotkey_S_T(self):
bpy.ops.bim.cad_mitre() bpy.ops.bim.cad_mitre()
@@ -17,7 +17,6 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from bpy.types import Operator
import bmesh import bmesh
import ifcopenshell import ifcopenshell
@@ -29,12 +28,11 @@ from blenderbim.bim.module.model.data import RoofData, refresh
from blenderbim.bim.module.model.decorator import ProfileDecorator from blenderbim.bim.module.model.decorator import ProfileDecorator
import json import json
from math import tan, radians, degrees, atan from math import tan
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from bpypolyskel import bpypolyskel from bpypolyskel import bpypolyskel
import shapely import shapely
from pprint import pprint from pprint import pprint
from itertools import chain
from math import pi from math import pi
# reference: # reference:
@@ -116,10 +114,13 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
original_geometry_data = dict() original_geometry_data = dict()
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles") angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
separate_verts_layer = bm.edges.layers.int.get("BBIM_gable_roof_separate_verts")
if angle_layer: if angle_layer:
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), e[angle_layer]) for e in bm.edges] original_geometry_data["edges"] = [
(set(bm_get_indices(e.verts)), e[angle_layer], e[separate_verts_layer]) for e in bm.edges
]
else: else:
original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), None) for e in bm.edges] original_geometry_data["edges"] = [(set(bm_get_indices(e.verts)), None, None) for e in bm.edges]
original_geometry_data["verts"] = {v.index: v.co.copy() for v in bm.verts} original_geometry_data["verts"] = {v.index: v.co.copy() for v in bm.verts}
footprint_z = bm.verts[:][0].co.z footprint_z = bm.verts[:][0].co.z
@@ -212,6 +213,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
footprint_edges = [] footprint_edges = []
footprint_verts = set() footprint_verts = set()
verts_to_change = {} verts_to_change = {}
verts_to_rip = []
# find footprint edges # find footprint edges
for edge in bm.edges: for edge in bm.edges:
@@ -227,7 +229,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
# iterate over edges from original geometry # iterate over edges from original geometry
# if their angle was redefined by user - apply the changes to the related vertices # if their angle was redefined by user - apply the changes to the related vertices
# to match the requested angle # to match the requested angle
for old_edge_verts, defined_angle in original_geometry_data["edges"]: for old_edge_verts, defined_angle, separate_verts in original_geometry_data["edges"]:
if not defined_angle: if not defined_angle:
continue continue
@@ -240,14 +242,35 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
verts_to_move = find_other_polygon_verts(identical_edge) verts_to_move = find_other_polygon_verts(identical_edge)
for v in verts_to_move: for v in verts_to_move:
vert_co = verts_to_change.get(v, v.co) if not separate_verts:
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle) vert_co = verts_to_change.get(v, v.co)
verts_to_change[v] = new_vert_co new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
verts_to_change[v] = new_vert_co
else:
vert_co = v.co
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
verts_to_rip.append([v, new_vert_co, identical_edge])
def separate_vert(bm, vert, edge, new_co):
face = next(f for f in vert.link_faces if edge in f.edges)
new_v = bmesh.utils.face_vert_separate(face, vert)
new_v.co = new_co
new_edge = bm.edges.new((vert, new_v))
for cur_edge in face.edges:
if cur_edge == edge:
continue
bmesh.ops.contextual_create(bm, geom=[new_edge, cur_edge])
# apply all changes once at the end # apply all changes once at the end
for v in verts_to_change: for v in verts_to_change:
v.co = verts_to_change[v] v.co = verts_to_change[v]
for v, new_co, edge in verts_to_rip:
separate_vert(bm, v, edge, new_co)
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"] extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"] extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts) bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
@@ -289,9 +312,6 @@ def update_roof_modifier_ifc_data(context):
# occurences attributes # occurences attributes
# occurences = tool.Ifc.get_all_element_occurences(element) # occurences = tool.Ifc.get_all_element_occurences(element)
# TODO: add Qto_RoofBaseQuantities, need to calculate GrossArea, NetArea, ProjectedArea
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/Qto_RoofBaseQuantities.htm
tool.Ifc.edit(obj) tool.Ifc.edit(obj)
@@ -311,12 +331,14 @@ def update_roof_modifier_bmesh(context):
RoofData.load() RoofData.load()
path_data = RoofData.data["parameters"]["data_dict"]["path_data"] path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
angle_layer_data = path_data.get("gable_roof_angles", None) angle_layer_data = path_data.get("gable_roof_angles", None)
separate_verts_data = path_data.get("gable_roof_separate_verts", None)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
# need to make sure we support edit mode # need to make sure we support edit mode
# since users will probably be in edit mode when they'll be changing roof path # since users will probably be in edit mode when they'll be changing roof path
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True) bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
angle_layer = bm.edges.layers.float.new("BBIM_gable_roof_angles") angle_layer = bm.edges.layers.float.new("BBIM_gable_roof_angles")
separate_verts_layer = bm.edges.layers.int.new("BBIM_gable_roof_separate_verts")
# generating roof path # generating roof path
new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]] new_verts = [bm.verts.new(Vector(v) * si_conversion) for v in path_data["verts"]]
@@ -325,6 +347,7 @@ def update_roof_modifier_bmesh(context):
e = path_data["edges"][i] e = path_data["edges"][i]
edge = bm.edges.new((new_verts[e[0]], new_verts[e[1]])) edge = bm.edges.new((new_verts[e[0]], new_verts[e[1]]))
edge[angle_layer] = angle_layer_data[i] if angle_layer_data else 0 edge[angle_layer] = angle_layer_data[i] if angle_layer_data else 0
edge[separate_verts_layer] = separate_verts_data[i] if separate_verts_data else 0
new_edges.append(edge) new_edges.append(edge)
if props.is_editing_path: if props.is_editing_path:
@@ -349,12 +372,15 @@ def get_path_data(obj):
bm_mesh_clean_up(bm) bm_mesh_clean_up(bm)
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles") angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
separate_verts_layer = bm.edges.layers.int.get("BBIM_gable_roof_separate_verts")
path_data = dict() path_data = dict()
path_data["edges"] = [bm_get_indices(e.verts) for e in bm.edges] path_data["edges"] = [bm_get_indices(e.verts) for e in bm.edges]
path_data["verts"] = [v.co / si_conversion for v in bm.verts] path_data["verts"] = [v.co / si_conversion for v in bm.verts]
if angle_layer: if angle_layer:
path_data["gable_roof_angles"] = [e[angle_layer] for e in bm.edges] path_data["gable_roof_angles"] = [e[angle_layer] for e in bm.edges]
if separate_verts_layer:
path_data["gable_roof_separate_verts"] = [e[separate_verts_layer] for e in bm.edges]
if not path_data["edges"] or not path_data["verts"]: if not path_data["edges"] or not path_data["verts"]:
return None return None
@@ -634,6 +660,7 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
bl_label = "Set Gable Roof Edge Angle" bl_label = "Set Gable Roof Edge Angle"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
angle: bpy.props.FloatProperty(name="Angle", default=90) angle: bpy.props.FloatProperty(name="Angle", default=90)
separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -656,12 +683,17 @@ class SetGableRoofEdgeAngle(bpy.types.Operator):
if "BBIM_gable_roof_angles" not in me.attributes: if "BBIM_gable_roof_angles" not in me.attributes:
me.attributes.new("BBIM_gable_roof_angles", type="FLOAT", domain="EDGE") me.attributes.new("BBIM_gable_roof_angles", type="FLOAT", domain="EDGE")
if "BBIM_gable_roof_separate_verts" not in me.attributes:
me.attributes.new("BBIM_gable_roof_separate_verts", type="INT", domain="EDGE")
angles_layer = bm.edges.layers.float["BBIM_gable_roof_angles"] angles_layer = bm.edges.layers.float["BBIM_gable_roof_angles"]
separate_verts_layer = bm.edges.layers.int["BBIM_gable_roof_separate_verts"]
for e in bm.edges: for e in bm.edges:
if not e.select: if not e.select:
continue continue
e[angles_layer] = self.angle e[angles_layer] = self.angle
e[separate_verts_layer] = self.separate_verts
tool.Blender.apply_bmesh(me, bm) tool.Blender.apply_bmesh(me, bm)
return {"FINISHED"} return {"FINISHED"}