Files
IfcOpenShell/src/bonsai/bonsai/bim/module/model/prop.py
T
Dion Moult 71668fbaf1 Only switch representation once when creating a door
Previously it would switch representation (and sync changes) every
single time a new representation was created (in the
replace_obj_ifc_representation function). I think this is misplaced
responsibility. The function should change the representation, but not
be responsible for switching.
2025-02-15 18:29:13 +11:00

1021 lines
40 KiB
Python

# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai 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.
#
# Bonsai 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.util.element
import bonsai.tool as tool
import math
from bonsai.bim.prop import ObjProperty
from bonsai.bim.module.model.data import AuthoringData
from bpy.types import PropertyGroup, NodeTree
from math import pi, radians
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
from bonsai.bim.module.model.door import update_door_modifier_bmesh
from typing import TYPE_CHECKING, Literal, get_args
def get_ifc_class(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["ifc_classes"]
def get_boundary_class(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["boundary_class"]
def get_relating_type_id(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
else:
AuthoringData.data["type_elements"] = AuthoringData.type_elements()
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
return AuthoringData.data["relating_type_id"]
def get_materials(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["materials"]
def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
if tool.Blender.get_enum_safe(self, "relating_type_id") is None:
self["relating_type_id"] = 0
AuthoringData.data["total_types"] = AuthoringData.total_types()
AuthoringData.data["total_pages"] = AuthoringData.total_pages()
AuthoringData.data["prev_page"] = AuthoringData.prev_page()
AuthoringData.data["next_page"] = AuthoringData.next_page()
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
def update_relating_type_id(self, context):
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["relating_type_name"] = AuthoringData.relating_type_name()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
AuthoringData.data["predefined_type"] = AuthoringData.predefined_type()
self.type_page = [e[0] for e in AuthoringData.data["relating_type_id"]].index(self.relating_type_id) // 9 + 1
def update_type_page(self, context):
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class, offset=9 * (self.type_page - 1), limit=9)
self["type_page"] = min(self["type_page"], AuthoringData.data["total_pages"])
self["type_page"] = max(self["type_page"], 1)
def update_relating_array_from_object(self, context):
bpy.ops.bim.enable_editing_array(item=self.is_editing)
return
def is_object_array_applicable(self, obj):
element = tool.Ifc.get_entity(obj)
if not element:
return False
return ifcopenshell.util.element.get_pset(element, "BBIM_Array")
def update_wall_axis_decorator(self, context):
if self.show_wall_axis:
WallAxisDecorator.install(bpy.context)
else:
WallAxisDecorator.uninstall()
def update_slab_direction_decorator(self, context):
if self.show_slab_direction:
SlabDirectionDecorator.install(bpy.context)
else:
SlabDirectionDecorator.uninstall()
def update_search_name(self, context):
AuthoringData.load()
# Total number of pages may decrease when using the search bar :
if self.type_page > AuthoringData.data["total_pages"]:
self.type_page = max(1, AuthoringData.data["total_pages"])
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
def update_x_angle(self, context):
angle_deg = math.degrees(self.x_angle)
if tool.Cad.is_x(angle_deg, -90, 0.5) or tool.Cad.is_x(angle_deg, 90, 0.5):
self.x_angle = 0
def update_door(self, context):
update_door_modifier_bmesh(context)
class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
relating_type_id: bpy.props.EnumProperty(
items=get_relating_type_id, name="Relating Type", update=update_relating_type_id
)
search_name: bpy.props.StringProperty(
name="Search Name",
default="",
description="Use this property to filter the list of available types",
update=update_search_name,
options={"SKIP_SAVE", "TEXTEDIT_UPDATE"},
)
menu_relating_type_id: bpy.props.IntProperty()
icon_id: bpy.props.IntProperty()
updating: bpy.props.BoolProperty(default=False)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
)
occurrence_name_function: bpy.props.StringProperty(
name="Occurrence Name Function",
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
)
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
extrusion_depth: bpy.props.FloatProperty(name="Extrusion Depth", min=0.001, default=42.0, subtype="DISTANCE")
cardinal_point: bpy.props.EnumProperty(
items=(
# TODO: complain to buildingSMART
("1", "bottom left", ""),
("2", "bottom centre", ""),
("3", "bottom right", ""),
("4", "mid-depth left", ""),
("5", "mid-depth centre", ""),
("6", "mid-depth right", ""),
("7", "top left", ""),
("8", "top centre", ""),
("9", "top right", ""),
("10", "geometric centroid", ""),
("11", "bottom in line with the geometric centroid", ""),
("12", "left in line with the geometric centroid", ""),
("13", "right in line with the geometric centroid", ""),
("14", "top in line with the geometric centroid", ""),
("15", "shear centre", ""),
("16", "bottom in line with the shear centre", ""),
("17", "left in line with the shear centre", ""),
("18", "right in line with the shear centre", ""),
("19", "top in line with the shear centre", ""),
),
name="Cardinal Point",
default="5",
)
length: bpy.props.FloatProperty(name="Length", default=42.0, subtype="DISTANCE")
openings: bpy.props.CollectionProperty(type=ObjProperty)
x: bpy.props.FloatProperty(name="X", default=0.5, subtype="DISTANCE", description="Size by X axis for the opening")
y: bpy.props.FloatProperty(name="Y", default=0.5, subtype="DISTANCE", description="Size by Y axis for the opening")
z: bpy.props.FloatProperty(name="Z", default=0.5, subtype="DISTANCE", description="Size by Z axis for the opening")
rl_mode: bpy.props.EnumProperty(
items=(
("BOTTOM", "Bottom", "Snaps the element's lowest geometry to the container's Z value."),
("CONTAINER", "Container", "Positions the element's placement origin at the container's Z value."),
("CURSOR", "Cursor", "Places the object placement at the 3D cursor's Z value."),
),
name="RL Mode",
default="BOTTOM",
)
# Used for things like walls, doors, flooring, skirting, etc
rl1: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for walls")
# Used for things like windows, other hosted furniture, and MEP
rl2: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for windows")
# Used for plan calculation points such as in room generation
rl3: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for space calculation")
type_page: bpy.props.IntProperty(name="Type Page", default=1, min=1, update=update_type_page)
x_angle: bpy.props.FloatProperty(
name="X Angle", default=0, subtype="ANGLE", min=math.radians(-180), max=math.radians(180), update=update_x_angle
)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class")
direction_sense: bpy.props.EnumProperty(
items=[("POSITIVE", "Positive", ""), ("NEGATIVE", "Negative", "")],
name="Material Usage Direction Sense",
default="POSITIVE",
)
offset_type_vertical: bpy.props.EnumProperty(
items=[("EXTERIOR", "Exterior", ""), ("CENTER", "Center", ""), ("INTERIOR", "Interior", "")],
name="Vertical Layer Offset Type",
default="EXTERIOR",
description="It's a convention that affects the offset to reference line",
)
offset_type_horizontal: bpy.props.EnumProperty(
items=[("TOP", "Top", ""), ("CENTER", "Center", ""), ("BOTTOM", "Bottom", "")],
name="Horizontal Layer Offset Type",
default="TOP",
description="It's a convention that affects the offset to reference line",
)
offset: bpy.props.FloatProperty(name="Offset", default=0.0, description="Material usage offset from reference line")
show_wall_axis: bpy.props.BoolProperty(
name="Show Wall Axis",
default=False,
update=update_wall_axis_decorator,
)
show_slab_direction: bpy.props.BoolProperty(
name="Show Slab Direction",
default=False,
update=update_slab_direction_decorator,
)
if TYPE_CHECKING:
ifc_class: str
relating_type_id: str
search_name: str
menu_relating_type_id: int
icon_id: int
updating: bool
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
occurrence_name_function: str
extrusion_depth: float
cardinal_point: Literal[
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
]
length: float
openings: bpy.types.bpy_prop_collection_idprop[ObjProperty]
x: float
y: float
z: float
rl_mode: Literal["BOTTOM", "CONTAINER", "CURSOR"]
rl1: float
rl2: float
rl3: float
type_page: int
x_angle: float
type_name: str
boundary_class: str
direction_sense: Literal["POSITIVE", "NEGATIVE"]
offset_type_vertical: Literal["EXTERIOR", "CENTER", "INTERIOR"]
offset_type_horizontal: Literal["TOP", "CENTER", "BOTTOM"]
offset: float
show_wall_axis: bool
show_slab_direction: bool
class BIMArrayProperties(PropertyGroup):
is_editing: bpy.props.IntProperty(
default=-1, description="Currently edited array index. -1 if not in array editing mode."
)
count: bpy.props.IntProperty(name="Count", default=0, min=0)
x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE")
y: bpy.props.FloatProperty(name="Y", default=0, subtype="DISTANCE")
z: bpy.props.FloatProperty(name="Z", default=0, subtype="DISTANCE")
use_local_space: bpy.props.BoolProperty(
name="Use Local Space",
description="Use local space for array items offset instead of world space",
default=True,
)
method: bpy.props.EnumProperty(
items=(("OFFSET", "Offset", ""), ("DISTRIBUTE", "Distribute", "")),
name="Method",
default="OFFSET",
)
sync_children: bpy.props.BoolProperty(
name="Sync Children",
description="Regenerate all children based on the parent object",
default=False,
)
relating_array_object: bpy.props.PointerProperty(
type=bpy.types.Object,
name="Copy Array Properties",
update=update_relating_array_from_object,
poll=is_object_array_applicable,
)
def update_total_length_target(self, context):
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
def update_tread_run(self, context):
if self.total_length_lock:
self["number_of_treads"] = int((self.total_length_target / self.tread_run) - 1)
else:
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
def update_number_of_treads(self, context):
if self.total_length_lock:
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
else:
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
class BIMStairProperties(PropertyGroup):
def validate_nosing_value(self, context):
if self.stair_type != "WOOD/STEEL" and self.nosing_length < 0:
self["nosing_length"] = 0
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
stair_types = (
("CONCRETE", "Concrete", ""),
("WOOD/STEEL", "Wood / Steel", ""),
("GENERIC", "Generic", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE")
number_of_treads: bpy.props.IntProperty(
name="Number of Treads", default=6, soft_min=1, update=update_number_of_treads
)
total_length_target: bpy.props.FloatProperty(
name="Total Length Target",
default=3.0,
soft_min=0.01,
subtype="DISTANCE",
update=update_total_length_target,
description="Total Length Target, might not be exactly respected depending on the parameters",
)
total_length_lock: bpy.props.BoolProperty(
default=False,
name="Lock Total Length",
description="Lock Total Length when changing number of treads or tread run",
)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE")
tread_run: bpy.props.FloatProperty(
name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE", update=update_tread_run
)
base_slab_depth: bpy.props.FloatProperty(name="Base Slab Depth", default=0.25, soft_min=0, subtype="DISTANCE")
top_slab_depth: bpy.props.FloatProperty(name="Top Slab Depth", default=0.25, soft_min=0, subtype="DISTANCE")
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True)
stair_type: bpy.props.EnumProperty(
name="Stair Type", items=stair_types, default="CONCRETE", update=validate_nosing_value
)
custom_first_last_tread_run: bpy.props.FloatVectorProperty(
name="Custom First / Last Treads Widths",
description='Specify custom first / last treads widths, different from the general "Tread Run". Leave 0 to disable.',
default=(0, 0),
min=0,
unit="LENGTH",
size=2,
)
nosing_length: bpy.props.FloatProperty(
name="Nosing Length",
description=(
"Overhang of the tread, not counted as a part of the tread run.\n"
"Can be negative for WOOD/STEEL stair (then it becomes a tread gap)"
),
default=0,
unit="LENGTH",
update=validate_nosing_value,
)
nosing_depth: bpy.props.FloatProperty(
name="Nosing Depth", description="Depth of the tread's nosing", min=0, default=0, unit="LENGTH"
)
def get_props_kwargs(self, convert_to_project_units=False, stair_type=None):
if not stair_type:
stair_type = self.stair_type
stair_kwargs = {
"stair_type": stair_type,
"width": self.width,
"height": self.height,
"number_of_treads": self.number_of_treads,
"tread_run": self.tread_run,
"nosing_length": self.nosing_length,
}
if stair_type == "CONCRETE":
concrete_props = {
"nosing_depth": self.nosing_depth,
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
"has_top_nib": self.has_top_nib,
"tread_depth": self.tread_depth,
}
stair_kwargs.update(concrete_props)
elif stair_type == "WOOD/STEEL":
wood_steel_props = {
"tread_depth": self.tread_depth,
}
stair_kwargs.update(wood_steel_props)
elif stair_type == "GENERIC":
generic_props = {
"nosing_depth": self.nosing_depth,
}
stair_kwargs.update(generic_props)
# defined here to appear last in UI
stair_kwargs["custom_first_last_tread_run"] = self.custom_first_last_tread_run
if not convert_to_project_units:
return stair_kwargs
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
return stair_kwargs
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])
class BIMSverchokProperties(PropertyGroup):
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
def window_type_prop_update(self, context):
number_of_panels, panels_data = self.window_types_panels[self.window_type]
self.first_mullion_offset, self.second_mullion_offset = panels_data[0]
self.first_transom_offset, self.second_transom_offset = panels_data[1]
# default prop values are in mm and converted later
class BIMWindowProperties(PropertyGroup):
non_si_units_props = ("is_editing", "window_type")
window_types = (
("SINGLE_PANEL", "SINGLE_PANEL", ""),
("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""),
("DOUBLE_PANEL_VERTICAL", "DOUBLE_PANEL_VERTICAL", ""),
("TRIPLE_PANEL_BOTTOM", "TRIPLE_PANEL_BOTTOM", ""),
("TRIPLE_PANEL_TOP", "TRIPLE_PANEL_TOP", ""),
("TRIPLE_PANEL_LEFT", "TRIPLE_PANEL_LEFT", ""),
("TRIPLE_PANEL_RIGHT", "TRIPLE_PANEL_RIGHT", ""),
("TRIPLE_PANEL_HORIZONTAL", "TRIPLE_PANEL_HORIZONTAL", ""),
("TRIPLE_PANEL_VERTICAL", "TRIPLE_PANEL_VERTICAL", ""),
)
# number of panels and default mullion/transom values
# fmt: off
window_types_panels = {
"SINGLE_PANEL": (1, ((0, 0 ), (0, 0 ))),
"DOUBLE_PANEL_HORIZONTAL": (2, ((0, 0 ), (0.45, 0 ))),
"DOUBLE_PANEL_VERTICAL": (2, ((0.3, 0 ), (0, 0 ))),
"TRIPLE_PANEL_BOTTOM": (3, ((0.3, 0 ), (0.45, 0 ))),
"TRIPLE_PANEL_TOP": (3, ((0.3, 0 ), (0.45, 0 ))),
"TRIPLE_PANEL_LEFT": (3, ((0.3, 0 ), (0.45, 0 ))),
"TRIPLE_PANEL_RIGHT": (3, ((0.3, 0 ), (0.45, 0 ))),
"TRIPLE_PANEL_HORIZONTAL": (3, ((0, 0 ), (0.3, 0.6))),
"TRIPLE_PANEL_VERTICAL": (3, ((0.2, 0.4), (0, 0 ))),
}
# fmt: on
is_editing: bpy.props.BoolProperty(default=False)
window_type: bpy.props.EnumProperty(
name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update
)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9, subtype="DISTANCE")
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6, subtype="DISTANCE")
# lining properties
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050, subtype="DISTANCE")
lining_to_panel_offset_x: bpy.props.FloatProperty(
name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE"
)
lining_to_panel_offset_y: bpy.props.FloatProperty(
name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE"
)
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050, subtype="DISTANCE")
first_mullion_offset: bpy.props.FloatProperty(
name="First Mullion Offset",
description="Distance from the first lining to the first mullion center",
default=0.3,
subtype="DISTANCE",
)
second_mullion_offset: bpy.props.FloatProperty(
name="Second Mullion Offset",
description="Distance from the first lining to the second mullion center",
default=0.45,
subtype="DISTANCE",
)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050, subtype="DISTANCE")
first_transom_offset: bpy.props.FloatProperty(
name="First Transom Offset",
description="Distance from the first lining to the first transom center",
default=0.3,
subtype="DISTANCE",
)
second_transom_offset: bpy.props.FloatProperty(
name="Second Transom Offset",
description="Distance from the first lining to the second transom center",
default=0.6,
subtype="DISTANCE",
)
# panel properties
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3, subtype="TRANSLATION")
frame_thickness: bpy.props.FloatVectorProperty(
name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION"
)
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"window_type": self.window_type,
"overall_height": self.overall_height,
"overall_width": self.overall_width,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ["window_type"])
def get_lining_kwargs(self, window_type=None, convert_to_project_units=False):
if not window_type:
window_type = self.window_type
kwargs = {
"lining_depth": self.lining_depth,
"lining_thickness": self.lining_thickness,
"lining_offset": self.lining_offset,
"lining_to_panel_offset_x": self.lining_to_panel_offset_x,
"lining_to_panel_offset_y": self.lining_to_panel_offset_y,
}
if window_type in (
"DOUBLE_PANEL_VERTICAL",
"TRIPLE_PANEL_BOTTOM",
"TRIPLE_PANEL_TOP",
"TRIPLE_PANEL_LEFT",
"TRIPLE_PANEL_RIGHT",
"TRIPLE_PANEL_VERTICAL",
):
kwargs["mullion_thickness"] = self.mullion_thickness
kwargs["first_mullion_offset"] = self.first_mullion_offset
if window_type in (
"DOUBLE_PANEL_HORIZONTAL",
"TRIPLE_PANEL_BOTTOM",
"TRIPLE_PANEL_TOP",
"TRIPLE_PANEL_LEFT",
"TRIPLE_PANEL_RIGHT",
"TRIPLE_PANEL_HORIZONTAL",
):
kwargs["transom_thickness"] = self.transom_thickness
kwargs["first_transom_offset"] = self.first_transom_offset
if window_type in ("TRIPLE_PANEL_VERTICAL",):
kwargs["second_mullion_offset"] = self.second_mullion_offset
if window_type in ("TRIPLE_PANEL_HORIZONTAL",):
kwargs["second_transom_offset"] = self.second_transom_offset
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def get_panel_kwargs(self, convert_to_project_units=False):
kwargs = {
"frame_depth": self.frame_depth,
"frame_thickness": self.frame_thickness,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
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])
DoorType = Literal[
"SINGLE_SWING_LEFT",
"SINGLE_SWING_RIGHT",
"DOUBLE_SWING_LEFT",
"DOUBLE_SWING_RIGHT",
"DOUBLE_DOOR_SINGLE_SWING",
"SLIDING_TO_LEFT",
"SLIDING_TO_RIGHT",
"DOUBLE_DOOR_SLIDING",
]
class BIMDoorProperties(PropertyGroup):
non_si_units_props = ("is_editing", "door_type", "panel_width_ratio")
is_editing: bpy.props.BoolProperty(default=False)
door_type: bpy.props.EnumProperty(
name="Door Operation Type",
items=tuple((i, i, "") for i in get_args(DoorType)),
default="SINGLE_SWING_LEFT",
update=update_door,
)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE", update=update_door)
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE", update=update_door)
# lining properties
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE", update=update_door)
lining_thickness: bpy.props.FloatProperty(
name="Lining Thickness", default=0.050, subtype="DISTANCE", update=update_door
)
lining_offset: bpy.props.FloatProperty(
name="Lining Offset",
description="Offset from the outer side of the wall (by Y-axis). "
"If present then adding casing is not possible.\n"
"`0.025 mm` is good as default value",
default=0.0,
subtype="DISTANCE",
update=update_door,
)
lining_to_panel_offset_x: bpy.props.FloatProperty(
name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE", update=update_door
)
lining_to_panel_offset_y: bpy.props.FloatProperty(
name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE", update=update_door
)
transom_thickness: bpy.props.FloatProperty(
name="Transom Thickness",
description="Set values > 0 to add a transom.\n" "`0.050 mm` is good as default value",
default=0.000,
subtype="DISTANCE",
update=update_door,
)
transom_offset: bpy.props.FloatProperty(
name="Transom Offset",
description="Distance from the bottom door opening to the beginning of the transom (unlike windows)",
default=1.525,
subtype="DISTANCE",
update=update_door,
)
casing_thickness: bpy.props.FloatProperty(
name="Casing Thickness",
description="Set values > 0 and LiningOffset = 0 to add a casing.",
default=0.075,
subtype="DISTANCE",
update=update_door,
)
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005, subtype="DISTANCE", update=update_door)
threshold_thickness: bpy.props.FloatProperty(
name="Threshold Thickness",
description="Set values > 0 to add a threshold.",
default=0.025,
subtype="DISTANCE",
update=update_door,
)
threshold_depth: bpy.props.FloatProperty(
name="Threshold Depth", default=0.1, subtype="DISTANCE", update=update_door
)
threshold_offset: bpy.props.FloatProperty(
name="Threshold Offset",
description="`0.025 mm` is good as default value",
default=0.000,
subtype="DISTANCE",
update=update_door,
)
# panel properties
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035, subtype="DISTANCE", update=update_door)
panel_width_ratio: bpy.props.FloatProperty(
name="Panel Width Ratio",
description="Width of this panel, given as ratio " "relative to the total clear opening width of the door",
default=1.0,
soft_min=0,
soft_max=1,
update=update_door,
)
frame_thickness: bpy.props.FloatProperty(
name="Window Frame Thickness", default=0.035, subtype="DISTANCE", update=update_door
)
frame_depth: bpy.props.FloatProperty(
name="Window Frame Depth", default=0.035, subtype="DISTANCE", update=update_door
)
# Material properties
lining_material: bpy.props.EnumProperty(name="Lining Material", items=get_materials, options=set())
framing_material: bpy.props.EnumProperty(name="Framing Material", items=get_materials, options=set())
glazing_material: bpy.props.EnumProperty(name="Glazing Material", items=get_materials, options=set())
if TYPE_CHECKING:
is_editing: bool
door_type: DoorType
overall_height: float
overall_width: float
# Lining.
lining_depth: float
lining_thickness: float
lining_offset: float
lining_to_panel_offset_x: float
lining_to_panel_offset_y: float
# Transom.
transom_thickness: float
transom_offset: float
# Casing.
casing_thickness: float
casing_depth: float
# Threshold.
threshold_thickness: float
threshold_depth: float
threshold_offset: float
# Panel.
panel_depth: float
panel_width_ratio: float
frame_thickness: float
frame_depth: float
# Material.
panel_material: str
lining_material: str
glazing_material: str
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"door_type": self.door_type,
"overall_height": self.overall_height,
"overall_width": self.overall_width,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ["door_type"])
def get_lining_kwargs(self, convert_to_project_units=False, door_type=None, lining_data=None):
if not door_type:
door_type = self.door_type
transom_thickness = lining_data["transom_thickness"] if lining_data else self.transom_thickness
lining_offset = lining_data["lining_offset"] if lining_data else self.lining_offset
threshold_thickness = lining_data["threshold_thickness"] if lining_data else self.threshold_thickness
kwargs = {
"lining_depth": self.lining_depth,
"lining_thickness": self.lining_thickness,
"lining_offset": lining_offset,
}
if "SLIDING" not in door_type:
kwargs["lining_to_panel_offset_x"] = self.lining_to_panel_offset_x
kwargs["lining_to_panel_offset_y"] = self.lining_to_panel_offset_y
kwargs["transom_thickness"] = transom_thickness
if transom_thickness:
kwargs["transom_offset"] = self.transom_offset
if not lining_offset:
kwargs["casing_thickness"] = self.casing_thickness
if self.casing_thickness:
kwargs["casing_depth"] = self.casing_depth
kwargs["threshold_thickness"] = threshold_thickness
if threshold_thickness:
kwargs["threshold_depth"] = self.threshold_depth
kwargs["threshold_offset"] = self.threshold_offset
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def get_panel_kwargs(self, convert_to_project_units=False, lining_data=None):
transom_thickness = lining_data["transom_thickness"] if lining_data else self.transom_thickness
kwargs = {"panel_depth": self.panel_depth, "panel_width_ratio": self.panel_width_ratio}
if transom_thickness:
kwargs["frame_thickness"] = self.frame_thickness
kwargs["frame_depth"] = self.frame_depth
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ("panel_width_ratio",))
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])
class BIMRailingProperties(PropertyGroup):
non_si_units_props = (
"is_editing",
"railing_type",
"use_manual_supports",
"terminal_type",
"path_data",
)
railing_types = (
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
("WALL_MOUNTED_HANDRAIL", "WALL_MOUNTED_HANDRAIL", ""),
)
cap_types = (
("TO_END_POST_AND_FLOOR", "TO_END_POST_AND_FLOOR", ""),
("TO_END_POST", "TO_END_POST", ""),
("TO_FLOOR", "TO_FLOOR", ""),
("TO_WALL", "TO_WALL", ""),
("180", "180", ""),
("NONE", "NONE", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
is_editing_path: bpy.props.BoolProperty(default=False)
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
height: bpy.props.FloatProperty(name="Height", default=1.0, subtype="DISTANCE")
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050, subtype="DISTANCE")
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050, subtype="DISTANCE")
# wall mounted handrail specific properties
use_manual_supports: bpy.props.BoolProperty(
name="Use Manual Supports",
default=False,
description="If enabled, supports are added on every vertex on the edges of the railing path.\n"
"If disabled, supports are added automatically based on the support spacing",
)
support_spacing: bpy.props.FloatProperty(
name="Support Spacing",
default=1.0,
min=0.01,
description="Distance between supports if automatic supports are used",
subtype="DISTANCE",
)
railing_diameter: bpy.props.FloatProperty(name="Railing Diameter", default=0.050, subtype="DISTANCE")
clear_width: bpy.props.FloatProperty(
name="Clear Width",
default=0.040,
description="Clear width between the railing and the wall",
subtype="DISTANCE",
)
terminal_type: bpy.props.EnumProperty(name="Terminal Type", items=cap_types, default="180")
def get_general_kwargs(self, railing_type=None, convert_to_project_units=False):
if railing_type is None:
railing_type = self.railing_type
base_kwargs = {
"railing_type": railing_type,
"height": self.height,
}
additional_kwargs = {}
if railing_type == "FRAMELESS_PANEL":
additional_kwargs = {
"thickness": self.thickness,
"spacing": self.spacing,
}
elif railing_type == "WALL_MOUNTED_HANDRAIL":
additional_kwargs = {
"railing_diameter": self.railing_diameter,
"clear_width": self.clear_width,
"use_manual_supports": self.use_manual_supports,
"support_spacing": self.support_spacing,
"terminal_type": self.terminal_type,
}
kwargs = base_kwargs | additional_kwargs
if not convert_to_project_units:
return kwargs
kwargs = tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
return kwargs
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])
def to_angle(percentage: float) -> float:
return math.atan(percentage / 100)
def to_percentage(angle: float) -> float:
return math.tan(angle) * 100
class BIMRoofProperties(PropertyGroup):
def update_angle(self, context) -> None:
self["angle"] = to_angle(self.percentage)
def update_percentage(self, context) -> None:
self["percentage"] = to_percentage(self.angle)
non_si_units_props = (
"is_editing",
"path_data",
"roof_type",
"generation_method",
"angle",
"percentage",
"rafter_edge_angle",
)
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
roof_generation_methods = (
("HEIGHT", "HEIGHT", ""),
("ANGLE", "ANGLE", ""),
)
is_editing: bpy.props.BoolProperty(default=False)
is_editing_path: bpy.props.BoolProperty(default=False)
roof_type: bpy.props.EnumProperty(name="Roof Type", items=roof_types, default="HIP/GABLE ROOF")
generation_method: bpy.props.EnumProperty(
name="Roof Generation Method", items=roof_generation_methods, default="ANGLE"
)
height: bpy.props.FloatProperty(
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",
update=update_percentage,
min=0.0,
max=pi / 2,
soft_min=radians(5.0),
soft_max=radians(60.0),
)
percentage: bpy.props.FloatProperty(
name="Slope %",
default=to_percentage(pi / 18),
subtype="PERCENTAGE",
update=update_angle,
min=0.0,
max=to_percentage(pi / 2),
soft_min=to_percentage(radians(5.0)),
soft_max=to_percentage(radians(60.0)),
)
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, max=pi / 2, default=pi / 2, subtype="ANGLE"
)
def get_general_kwargs(self, generation_method=None, convert_to_project_units=False):
if generation_method is None:
generation_method = self.generation_method
kwargs = {
"roof_type": self.roof_type,
"generation_method": generation_method,
"roof_thickness": self.roof_thickness,
"rafter_edge_angle": self.rafter_edge_angle,
}
if generation_method == "HEIGHT":
kwargs["height"] = self.height
else:
kwargs["angle"] = self.angle
kwargs["percentage"] = self.percentage
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])
class SnapMousePoint(PropertyGroup):
x: bpy.props.FloatProperty(name="X")
y: bpy.props.FloatProperty(name="Y")
z: bpy.props.FloatProperty(name="Z")
snap_type: bpy.props.StringProperty(name="Snap Type")
snap_object: bpy.props.StringProperty(name="Object Name")
class PolylinePoint(PropertyGroup):
x: bpy.props.FloatProperty(name="X")
y: bpy.props.FloatProperty(name="Y")
z: bpy.props.FloatProperty(name="Z")
dim: bpy.props.StringProperty(name="Dimension")
angle: bpy.props.StringProperty(name="Angle")
position: bpy.props.FloatVectorProperty(name="Decorator Position", size=3)
class Polyline(PropertyGroup):
polyline_points: bpy.props.CollectionProperty(type=PolylinePoint)
measurement_type: bpy.props.StringProperty(name="Measurement Type")
area: bpy.props.StringProperty(name="Measured Area")
total_length: bpy.props.StringProperty(name="Total Length")
class BIMPolylineProperties(PropertyGroup):
snap_mouse_point: bpy.props.CollectionProperty(type=SnapMousePoint)
snap_mouse_ref: bpy.props.CollectionProperty(type=SnapMousePoint)
insertion_polyline: bpy.props.CollectionProperty(type=Polyline)
measurement_polyline: bpy.props.CollectionProperty(type=Polyline)
class ProductPreviewItem(PropertyGroup):
value_3d: bpy.props.FloatVectorProperty()
value_2d: bpy.props.FloatVectorProperty(size=2)
class BIMProductPreviewProperties(PropertyGroup):
verts: bpy.props.CollectionProperty(type=ProductPreviewItem)
edges: bpy.props.CollectionProperty(type=ProductPreviewItem)
tris: bpy.props.CollectionProperty(type=ProductPreviewItem)