rafter edge angle and roof thickness #3092

Added option to adjust rafter edge angle and roof thickness
Short demonstration - https://imgur.com/a/TsYSs6i
This commit is contained in:
Andrej730
2023-05-30 18:00:37 +05:00
parent 9dd82036ba
commit f6e72ec06f
4 changed files with 59 additions and 38 deletions
@@ -564,6 +564,7 @@ class BIMRailingProperties(PropertyGroup):
class BIMRoofProperties(PropertyGroup):
non_si_units_props = ("is_editing", "path_data", "roof_type", "roof_added_previously", "generation_method", "angle", "rafter_edge_angle")
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
roof_generation_methods = (
("HEIGHT", "HEIGHT", ""),
@@ -582,14 +583,26 @@ class BIMRoofProperties(PropertyGroup):
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
)
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE")
roof_thickness: bpy.props.FloatProperty(name="Roof Thickness", default=0.1, subtype="DISTANCE")
rafter_edge_angle: bpy.props.FloatProperty(name="Rafter Edge Angle", min=0, default=pi / 2, subtype="ANGLE")
def get_general_kwargs(self):
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"roof_type": self.roof_type,
"generation_method": self.generation_method,
"roof_thickness": self.roof_thickness,
"rafter_edge_angle": self.rafter_edge_angle,
}
if self.generation_method == "HEIGHT":
kwargs["height"] = self.height
else:
kwargs["angle"] = self.angle
return kwargs
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
@@ -28,7 +28,7 @@ from blenderbim.bim.module.model.data import RoofData, refresh
from blenderbim.bim.module.model.decorator import ProfileDecorator
import json
from math import tan, pi
from math import tan, pi, radians
from mathutils import Vector, Matrix
from bpypolyskel import bpypolyskel
import shapely
@@ -39,9 +39,6 @@ from pprint import pprint
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
NON_SI_ROOF_PROPS = ("is_editing", "roof_type", "roof_added_previously", "generation_method", "angle")
def float_is_zero(f):
return 0.0001 >= f >= -0.0001
@@ -110,7 +107,7 @@ def is_valid_roof_footprint(bm):
return ({"FINISHED"}, "")
def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutate_current_bmesh=True):
def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, roof_thickness = 0.1, angle=pi / 18, rafter_edge_angle = pi / 2, mutate_current_bmesh=True):
"""return bmesh with gable roof geometry
`mutate_current_bmesh` is a flag to indicate whether the input bmesh
@@ -232,10 +229,13 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
verts_to_change = {}
verts_to_rip = []
bottom_chords_to_remove = []
def is_footprint_edge(edge):
return all(float_is_zero(v.co.z - footprint_z) for v in edge.verts)
# find footprint edges
for edge in bm.edges:
if all(float_is_zero(v.co.z - footprint_z) for v in edge.verts):
if is_footprint_edge(edge):
footprint_edges.append(edge)
footprint_verts.update(edge.verts)
@@ -299,7 +299,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
for identical_edge, edge_verts_remaped, defined_angle in process_later:
verts_to_move = find_other_polygon_verts(identical_edge)
for v in verts_to_move[:]:
verts_to_move.extend(related_verts[v])
verts_to_move.extend(related_verts.get(v, []))
for v in verts_to_move:
vert_co = verts_to_change.get(v, v.co)
@@ -312,10 +312,33 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
bmesh.ops.delete(bm, geom=bottom_chords_to_remove, context="EDGES")
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
# add roof thickness
extrusion_geom = bm_sort_out_geom(bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"])
extruded_edges = extrusion_geom["edges"]
extruded_verts = extrusion_geom["verts"]
rafter_edge_angle = pi/2 - rafter_edge_angle
default_offset_dir = Vector([0, 0, 1]) * roof_thickness
footprint_verts = set()
footprint_edges = []
for edge in extruded_edges:
if is_footprint_edge(edge):
footprint_edges.append(edge)
# TODO: rafter_edge_angle might differ
# if footprint edges are not connected by 90 degrees
shifted_verts = set()
for edge in footprint_edges:
footprint_verts.update(edge.verts)
v0, v1 = edge.verts
edge_dir = (v0.co - v1.co).normalized()
offset_dir = Matrix.Rotation(rafter_edge_angle, 4, edge_dir) @ default_offset_dir
for v in edge.verts:
v.co += offset_dir * (Vector([1, 1, 0]) if v in shifted_verts else 1)
shifted_verts.update(footprint_verts)
verts = [v for v in extruded_verts if v not in footprint_verts]
bmesh.ops.translate(bm, vec=default_offset_dir, verts=verts)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
return bm
@@ -395,8 +418,9 @@ def update_roof_modifier_bmesh(context):
tool.Blender.apply_bmesh(obj.data, bm)
return
height = props.height * si_conversion
generate_hiped_roof_bmesh(bm, props.generation_method, height, props.angle, mutate_current_bmesh=True)
generate_hiped_roof_bmesh(
bm, props.generation_method, props.height, props.roof_thickness,
props.angle, props.rafter_edge_angle, mutate_current_bmesh=True)
tool.Blender.apply_bmesh(obj.data, bm)
@@ -482,10 +506,6 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Object has to be IfcRoof/IfcRoofType type to add a roof.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.roof_added_previously:
convert_property_group_from_si(props, skip_props=NON_SI_ROOF_PROPS)
# rejecting original roof shape to be safe
# taking into account only it's bounding box dimensions
if obj.dimensions.x == 0 or obj.dimensions.y == 0:
@@ -500,7 +520,7 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
max_y = bbox["max_y"]
min_z = bbox["min_z"]
roof_data = props.get_general_kwargs()
roof_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = {
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
"verts": [
@@ -529,17 +549,8 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
props = obj.BIMRoofProperties
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
data["path_data"] = json.dumps(data["path_data"])
# required since we could load pset from .ifc and BIMRoofProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = NON_SI_ROOF_PROPS + tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = 1
return {"FINISHED"}
@@ -555,8 +566,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
props = obj.BIMRoofProperties
# restore previous settings since editing was canceled
for prop_name in data:
setattr(props, prop_name, data[prop_name])
props.set_props_kwargs_from_ifc_data(data)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
@@ -587,7 +597,7 @@ class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
RoofData.load()
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
roof_data = props.get_general_kwargs()
roof_data = props.get_general_kwargs(convert_to_project_units=True)
roof_data["path_data"] = path_data
props.is_editing = -1
@@ -629,10 +639,9 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
main_bm.edges.layers.int.new("BBIM_preview")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
height = props.height * si_conversion
second_bm = generate_hiped_roof_bmesh(
bm, props.generation_method, height, props.angle, mutate_current_bmesh=False
bm,props.generation_method, props.height, props.roof_thickness,
props.angle, props.rafter_edge_angle, mutate_current_bmesh=False
)
bmesh.ops.translate(second_bm, verts=second_bm.verts, vec=Vector((0, 0, 1)))
@@ -686,7 +695,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
self.report(op_status, error_message)
return {"CANCELLED"}
roof_data = props.get_general_kwargs()
roof_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = get_path_data(obj)
roof_data["path_data"] = path_data
ProfileDecorator.uninstall()
@@ -614,7 +614,7 @@ class BIM_PT_roof(bpy.types.Panel):
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
row = box.row(align=True)
row.label(text=f"{props.bl_rna.properties[prop].name}")
if prop == "angle":
if prop in ("angle", "rafter_edge_angle"):
prop_value = round(degrees(prop_value), 2)
row.label(text=str(prop_value))
else:
-1
View File
@@ -64,7 +64,6 @@ class Model(blenderbim.core.tool.Model):
if prop_name in non_si_props:
continue
prop_value = data[prop_name]
print(prop_name, prop_value)
if isinstance(prop_value, collections.abc.Iterable):
data[prop_name] = [v * si_conversion for v in prop_value]
else: