generate_stair_2d_profile - add typing

This commit is contained in:
Andrej730
2025-11-06 17:49:12 +05:00
parent c9df87fc25
commit b5e8dfd863
3 changed files with 59 additions and 32 deletions
+1 -4
View File
@@ -480,9 +480,6 @@ def update_custom_first_last_tread_run(self: "BIMStairProperties", context: bpy.
self["total_length_target"] = total_length self["total_length_target"] = total_length
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
class BIMStairProperties(PropertyGroup): class BIMStairProperties(PropertyGroup):
def validate_nosing_value(self, context: bpy.types.Context) -> None: def validate_nosing_value(self, context: bpy.types.Context) -> None:
if self.stair_type != "WOOD/STEEL" and self.nosing_length < 0: if self.stair_type != "WOOD/STEEL" and self.nosing_length < 0:
@@ -523,7 +520,7 @@ class BIMStairProperties(PropertyGroup):
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True) has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True)
stair_type: bpy.props.EnumProperty( stair_type: bpy.props.EnumProperty(
name="Stair Type", name="Stair Type",
items=[(i, i.replace("/", " / ").title(), "") for i in get_args(StairType)], items=[(i, i.replace("/", " / ").title(), "") for i in get_args(tool.Model.StairType)],
default="CONCRETE", default="CONCRETE",
update=validate_nosing_value, update=validate_nosing_value,
) )
+56 -27
View File
@@ -44,11 +44,8 @@ import mathutils
from math import atan, cos, degrees, pi, radians from math import atan, cos, degrees, pi, radians
from mathutils import Matrix, Vector from mathutils import Matrix, Vector
from copy import deepcopy from copy import deepcopy
from functools import partial
from bonsai.bim import import_ifc from bonsai.bim import import_ifc
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from bonsai.bim.module.model.opening import FilledOpeningGenerator
from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never
from collections.abc import Iterable, Sequence from collections.abc import Iterable, Sequence
@@ -1103,6 +1100,7 @@ class Model(bonsai.core.tool.Model):
return return
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from bonsai.bim.module.model.data import AuthoringData
obj = tool.Ifc.get_object(element) obj = tool.Ifc.get_object(element)
if not obj: if not obj:
@@ -1208,6 +1206,8 @@ class Model(bonsai.core.tool.Model):
Allows postponing the thumbnail update until it is actually needed by the user. Allows postponing the thumbnail update until it is actually needed by the user.
""" """
from bonsai.bim.module.model.data import AuthoringData
element_id = element.id() element_id = element.id()
if element_id not in AuthoringData.type_thumbnails: if element_id not in AuthoringData.type_thumbnails:
return return
@@ -1311,18 +1311,26 @@ class Model(bonsai.core.tool.Model):
@classmethod @classmethod
def is_parametric_roof_active(cls) -> bool: def is_parametric_roof_active(cls) -> bool:
from bonsai.bim.module.model.data import RoofData
return bool((RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"]) return bool((RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"])
@classmethod @classmethod
def is_parametric_railing_active(cls) -> bool: def is_parametric_railing_active(cls) -> bool:
from bonsai.bim.module.model.data import RailingData
return bool((RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"]) return bool((RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"])
@classmethod @classmethod
def is_parametric_window_active(cls) -> bool: def is_parametric_window_active(cls) -> bool:
from bonsai.bim.module.model.data import WindowData
return bool((WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"]) return bool((WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"])
@classmethod @classmethod
def is_parametric_door_active(cls) -> bool: def is_parametric_door_active(cls) -> bool:
from bonsai.bim.module.model.data import DoorData
return bool((DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"]) return bool((DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"])
@classmethod @classmethod
@@ -1384,29 +1392,31 @@ class Model(bonsai.core.tool.Model):
return calculated_params return calculated_params
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
@classmethod @classmethod
def generate_stair_2d_profile( def generate_stair_2d_profile(
cls, cls,
number_of_treads, number_of_treads: int,
height, height: float,
width, width: float,
tread_run, tread_run: float,
stair_type, stair_type: StairType,
# WOOD/STEEL CONCRETE STAIR ARGUMENTS # WOOD/STEEL CONCRETE STAIR ARGUMENTS
tread_depth=None, tread_depth: Union[float, None] = None,
# CONCRETE STAIR ARGUMENTS # CONCRETE STAIR ARGUMENTS
has_top_nib=None, has_top_nib: Union[bool, None] = None,
top_slab_depth=None, top_slab_depth: Union[float, None] = None,
base_slab_depth=None, base_slab_depth: Union[float, None] = None,
custom_first_last_tread_run: Union[tuple[float, float], tuple[None, None]] = (None, None), custom_first_last_tread_run: Union[tuple[float, float], tuple[None, None]] = (None, None),
nosing_length=0, nosing_length: float = 0.0,
# CONCRETE GENERIC STAIR ARGUMENTS # CONCRETE GENERIC STAIR ARGUMENTS
nosing_depth=0, nosing_depth: float = 0.0,
): ) -> tuple[list[Vector], list[tuple[int, ...]], list[[list[int]]]]:
"""returns a tuple of stair profile data: (vertices, edges, faces)""" """returns a tuple of stair profile data: (vertices, edges, faces)"""
vertices = [] vertices: list[Vector] = []
edges = [] edges: list[tuple[int, ...]] = []
faces = [] faces: list[[list[int]]] = []
number_of_risers = number_of_treads + 1 number_of_risers = number_of_treads + 1
tread_rise = height / number_of_risers tread_rise = height / number_of_risers
@@ -1450,7 +1460,12 @@ class Model(bonsai.core.tool.Model):
elif nosing_depth == 0: elif nosing_depth == 0:
default_tread_verts = (V_(-nosing_overlap, tread_rise), V_(tread_run, tread_rise)) default_tread_verts = (V_(-nosing_overlap, tread_rise), V_(tread_run, tread_rise))
else: # nosing_overlap > 0 nosing_depth > 0 else: # nosing_overlap > 0 nosing_depth > 0
# kind of L shape # kind of L shape:
# (2)●───────────────────────────●(3)
# |
# |
# ●──────────────●
# (1) (0)
default_tread_verts = ( default_tread_verts = (
V_(0, tread_rise - nosing_depth), V_(0, tread_rise - nosing_depth),
V_(-nosing_overlap, tread_rise - nosing_depth), V_(-nosing_overlap, tread_rise - nosing_depth),
@@ -1503,12 +1518,12 @@ class Model(bonsai.core.tool.Model):
print("WOOD/STEEL STAIR GENERATION PROCESS") print("WOOD/STEEL STAIR GENERATION PROCESS")
print("-" * 80) print("-" * 80)
builder = ShapeBuilder(None) assert tread_depth is not None
# full tread rectangle # full tread rectangle
def get_tread_verts(*args, **kwargs): def get_tread_verts(size: Vector) -> list[Vector]:
fn = partial(builder.get_rectangle_coords, position=V_(0, -(tread_depth - tread_rise))) coords = ShapeBuilder.get_rectangle_coords(position=V_(0, -(tread_depth - tread_rise)), size=size)
return [Vector(x) for x in fn(*args, **kwargs)] return [Vector(x) for x in coords]
default_tread_verts = get_tread_verts(size=V_(tread_run + nosing_overlap, tread_depth)) default_tread_verts = get_tread_verts(size=V_(tread_run + nosing_overlap, tread_depth))
default_tread_offset = V_(tread_run + nosing_tread_gap, tread_rise) default_tread_offset = V_(tread_run + nosing_tread_gap, tread_rise)
@@ -1606,6 +1621,11 @@ class Model(bonsai.core.tool.Model):
elif stair_type == "CONCRETE": elif stair_type == "CONCRETE":
define_generic_stair_treads() define_generic_stair_treads()
assert has_top_nib is not None
assert top_slab_depth is not None
assert base_slab_depth is not None
assert tread_depth is not None
# add the nibs # add the nibs
# basically we define stair bottom line as a line at `tread_depth` distance # basically we define stair bottom line as a line at `tread_depth` distance
# from the tread diagonal line # from the tread diagonal line
@@ -1623,11 +1643,16 @@ class Model(bonsai.core.tool.Model):
# comes from y = stair_tan * x + b # comes from y = stair_tan * x + b
b = s0.y - stair_tan * s0.x b = s0.y - stair_tan * s0.x
def get_point_on_2d_line(x=None, y=None): def get_point_on_2d_line(
if y is None: x: Union[float, None] = None,
y: Union[float, None] = None,
) -> Vector:
if x is not None and y is None:
y = stair_tan * x + b y = stair_tan * x + b
elif x is None: elif x is None and y is not None:
x = (y - b) / stair_tan x = (y - b) / stair_tan
else:
assert False
return V_(x, y) return V_(x, y)
# top nib # top nib
@@ -1671,11 +1696,13 @@ class Model(bonsai.core.tool.Model):
else: else:
raise Exception(f"Unsupported stair type: {stair_type}") raise Exception(f"Unsupported stair type: {stair_type}")
vertices = (v.to_3d().xzy for v in vertices) vertices = [v.to_3d().xzy for v in vertices]
return (vertices, edges, faces) return (vertices, edges, faces)
@classmethod @classmethod
def update_simple_openings(cls, element: ifcopenshell.entity_instance) -> None: def update_simple_openings(cls, element: ifcopenshell.entity_instance) -> None:
from bonsai.bim.module.model.opening import FilledOpeningGenerator
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
fillings = {e: tool.Ifc.get_object(e) for e in tool.Ifc.get_all_element_occurrences(element)} fillings = {e: tool.Ifc.get_object(e) for e in tool.Ifc.get_all_element_occurrences(element)}
@@ -2238,6 +2265,8 @@ class Model(bonsai.core.tool.Model):
@classmethod @classmethod
def add_filled_opening(cls, voided_obj: bpy.types.Object, filling_obj: bpy.types.Object) -> None: def add_filled_opening(cls, voided_obj: bpy.types.Object, filling_obj: bpy.types.Object) -> None:
from bonsai.bim.module.model.opening import FilledOpeningGenerator
FilledOpeningGenerator().generate(filling_obj, voided_obj) FilledOpeningGenerator().generate(filling_obj, voided_obj)
@classmethod @classmethod
@@ -405,7 +405,8 @@ class ShapeBuilder:
ifc_curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments) ifc_curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return ifc_curve return ifc_curve
def get_rectangle_coords(self, size: VectorType = (1.0, 1.0), position: Optional[VectorType] = None) -> np.ndarray: @staticmethod
def get_rectangle_coords(size: VectorType = (1.0, 1.0), position: Optional[VectorType] = None) -> np.ndarray:
""" """
Get rectangle coords arranged as below: Get rectangle coords arranged as below: