mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Customize tread width for the first and last treads #3419
Available on all types of stairs, shouldn't break stairs previously created with BBIM. Example - https://imgur.com/a/2wSueTM
This commit is contained in:
@@ -361,7 +361,7 @@ class SverchokData:
|
||||
|
||||
|
||||
def get_prop_from_data(props, data, prop_name):
|
||||
prop_value = data[prop_name]
|
||||
prop_value = data.get(prop_name, tool.Blender.get_blender_prop_default_value(props, prop_name))
|
||||
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
return prop_readable_name, prop_value
|
||||
|
||||
@@ -210,6 +210,14 @@ class BIMStairProperties(PropertyGroup):
|
||||
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")
|
||||
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,
|
||||
)
|
||||
|
||||
def get_props_kwargs(self, convert_to_project_units=False, stair_type=None):
|
||||
if not stair_type:
|
||||
@@ -240,6 +248,9 @@ class BIMStairProperties(PropertyGroup):
|
||||
elif stair_type == "GENERIC":
|
||||
pass
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -266,7 +266,13 @@ class BIM_PT_stair(bpy.types.Panel):
|
||||
row.operator("bim.cancel_editing_stair", icon="CANCEL", text="")
|
||||
row = self.layout.row(align=True)
|
||||
for prop_name in props.get_props_kwargs():
|
||||
self.layout.prop(props, prop_name)
|
||||
prop_value = getattr(props, prop_name)
|
||||
if isinstance(prop_value, bpy.types.bpy_prop_array):
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
self.layout.label(text=f"{prop_readable_name}:")
|
||||
self.layout.prop(props, prop_name, text="")
|
||||
else:
|
||||
self.layout.prop(props, prop_name)
|
||||
regenerate_stair_mesh(context)
|
||||
else:
|
||||
calculated_params = StairData.data["calculated_params"]
|
||||
@@ -275,8 +281,14 @@ class BIM_PT_stair(bpy.types.Panel):
|
||||
row = self.layout.row(align=True)
|
||||
for prop_name, prop_value in StairData.data["general_params"].items():
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=prop_name)
|
||||
row.label(text=str(prop_value))
|
||||
if isinstance(prop_value, bpy.types.bpy_prop_array):
|
||||
row.label(text=f"{prop_name}:")
|
||||
row = self.layout.row(align=True)
|
||||
for prop_value_item in prop_value:
|
||||
row.label(text=str(prop_value_item))
|
||||
else:
|
||||
row.label(text=prop_name)
|
||||
row.label(text=str(prop_value))
|
||||
|
||||
# calculated properties
|
||||
for prop_name, prop_value in calculated_params.items():
|
||||
|
||||
@@ -29,6 +29,8 @@ import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry as geometry
|
||||
from mathutils import Matrix, Vector
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
|
||||
@@ -917,16 +919,32 @@ class Model(blenderbim.core.tool.Model):
|
||||
number_of_treads = props.number_of_treads
|
||||
height = props.height / si_conversion
|
||||
tread_run = props.tread_run / si_conversion
|
||||
first_tread_run = props.custom_first_last_tread_run[0] / si_conversion
|
||||
last_tread_run = props.custom_first_last_tread_run[1] / si_conversion
|
||||
else:
|
||||
number_of_treads = pset_data["number_of_treads"]
|
||||
height = pset_data["height"]
|
||||
tread_run = pset_data["tread_run"]
|
||||
# use .get to not break the old .ifc models
|
||||
custom_first_last_tread_run = pset_data.get("custom_first_last_tread_run", (0, 0))
|
||||
first_tread_run, last_tread_run = custom_first_last_tread_run
|
||||
|
||||
calculated_params = {}
|
||||
number_of_rises = number_of_treads + 1
|
||||
calculated_params["Number of Risers"] = number_of_rises
|
||||
calculated_params["Tread Rise"] = round(height / number_of_rises, 5)
|
||||
calculated_params["Length"] = round(tread_run * number_of_rises, 5)
|
||||
|
||||
n_default_tread_runs = number_of_rises
|
||||
length = 0
|
||||
if first_tread_run != 0:
|
||||
n_default_tread_runs -= 1
|
||||
length += first_tread_run
|
||||
if last_tread_run != 0:
|
||||
n_default_tread_runs -= 1
|
||||
if n_default_tread_runs >= 0:
|
||||
length += last_tread_run
|
||||
length += tread_run * max(n_default_tread_runs, 0)
|
||||
calculated_params["Length"] = round(length, 5)
|
||||
return calculated_params
|
||||
|
||||
@classmethod
|
||||
@@ -943,6 +961,7 @@ class Model(blenderbim.core.tool.Model):
|
||||
has_top_nib=None,
|
||||
top_slab_depth=None,
|
||||
base_slab_depth=None,
|
||||
custom_first_last_tread_run=(0, 0),
|
||||
):
|
||||
"""returns a tuple of stair profile data: (vertices, edges, faces)"""
|
||||
vertices = []
|
||||
@@ -951,18 +970,35 @@ class Model(blenderbim.core.tool.Model):
|
||||
|
||||
number_of_risers = number_of_treads + 1
|
||||
tread_rise = height / number_of_risers
|
||||
length = tread_run * number_of_risers
|
||||
custom_tread_run = any(run != 0 for run in custom_first_last_tread_run)
|
||||
|
||||
if stair_type == "WOOD/STEEL":
|
||||
builder = ShapeBuilder(None)
|
||||
tread_shape = builder.get_rectangle_coords(
|
||||
size=V(tread_run, tread_depth), position=V(0, -(tread_depth - tread_rise))
|
||||
)
|
||||
tread_offset = V(tread_run, tread_rise)
|
||||
# full tread rectangle
|
||||
get_tread_verts = partial(builder.get_rectangle_coords, position=V(0, -(tread_depth - tread_rise)))
|
||||
default_tread_verts = get_tread_verts(size=V(tread_run, tread_depth))
|
||||
default_tread_offset = V(tread_run, tread_rise)
|
||||
|
||||
def get_tread_data(i):
|
||||
if custom_tread_run:
|
||||
current_tread_run = None
|
||||
if i == 0 and custom_first_last_tread_run[0] != 0:
|
||||
current_tread_run = custom_first_last_tread_run[0]
|
||||
elif i == number_of_risers - 1 and custom_first_last_tread_run[1] != 0:
|
||||
current_tread_run = custom_first_last_tread_run[1]
|
||||
|
||||
if current_tread_run:
|
||||
tread_offset = default_tread_offset.copy()
|
||||
tread_offset.x = current_tread_run
|
||||
tread_verts = get_tread_verts(size=V(current_tread_run, tread_depth))
|
||||
return tread_offset, tread_verts
|
||||
return default_tread_offset, default_tread_verts
|
||||
|
||||
# each tread is a separate shape
|
||||
cur_offset = V(0, 0)
|
||||
for i in range(number_of_risers):
|
||||
cur_trade_shape = [v + tread_offset * i for v in tread_shape]
|
||||
tread_offset, tread_verts = get_tread_data(i)
|
||||
cur_trade_shape = [v + cur_offset for v in tread_verts]
|
||||
vertices.extend(cur_trade_shape)
|
||||
|
||||
cur_vertex = i * 4
|
||||
@@ -973,19 +1009,40 @@ class Model(blenderbim.core.tool.Model):
|
||||
(cur_vertex + 3, cur_vertex),
|
||||
)
|
||||
edges.extend(verts_to_add)
|
||||
cur_offset += tread_offset
|
||||
|
||||
elif stair_type == "GENERIC":
|
||||
vertices.append(Vector([0, 0]))
|
||||
|
||||
tread_verts = [Vector([0, tread_rise]), Vector([tread_run, tread_rise])]
|
||||
tread_offset = Vector([tread_run, tread_rise])
|
||||
# horizontal tread line
|
||||
default_tread_verts = [Vector([0, tread_rise]), Vector([tread_run, tread_rise])]
|
||||
default_tread_offset = Vector([tread_run, tread_rise])
|
||||
|
||||
def get_tread_data(i):
|
||||
if custom_tread_run:
|
||||
current_tread_run = None
|
||||
if i == 0:
|
||||
current_tread_run = custom_first_last_tread_run[0]
|
||||
elif i == number_of_risers - 1:
|
||||
current_tread_run = custom_first_last_tread_run[1]
|
||||
|
||||
if current_tread_run:
|
||||
tread_offset = default_tread_offset.copy()
|
||||
tread_offset.x = current_tread_run
|
||||
tread_verts = deepcopy(default_tread_verts)
|
||||
tread_verts[1].x = current_tread_run
|
||||
return tread_offset, tread_verts
|
||||
return default_tread_offset, default_tread_verts
|
||||
|
||||
# treads
|
||||
current_offset = V(0, 0)
|
||||
for i in range(number_of_risers):
|
||||
current_tread_verts = [v + tread_offset * i for v in tread_verts]
|
||||
tread_offset, tread_verts = get_tread_data(i)
|
||||
current_tread_verts = [v + current_offset for v in tread_verts]
|
||||
last_vert_i = i * 2
|
||||
edges.extend([(last_vert_i, last_vert_i + 1), (last_vert_i + 1, last_vert_i + 2)])
|
||||
vertices.extend(current_tread_verts)
|
||||
current_offset += tread_offset
|
||||
|
||||
# close the shape
|
||||
last_vert_i = len(vertices)
|
||||
@@ -993,33 +1050,62 @@ class Model(blenderbim.core.tool.Model):
|
||||
edges.extend([(last_vert_i - 1, last_vert_i), (last_vert_i, 0)])
|
||||
|
||||
elif stair_type == "CONCRETE":
|
||||
# treads
|
||||
for i in range(number_of_risers):
|
||||
vertices.append(Vector((tread_run * i, tread_rise * i)))
|
||||
vertices.append(Vector((tread_run * i, tread_rise * (i + 1))))
|
||||
cur_vertex = i * 2
|
||||
if i != 0:
|
||||
edges.append((cur_vertex - 1, cur_vertex))
|
||||
edges.append((cur_vertex, cur_vertex + 1))
|
||||
vertices.append(V(0, 0))
|
||||
|
||||
vertices.append(Vector((tread_run * number_of_risers, tread_rise * number_of_risers)))
|
||||
edges.append((number_of_risers * 2, number_of_risers * 2 - 1))
|
||||
# NOTE: code until adding nibs is very similar to GENERIC
|
||||
# horizontal tread line
|
||||
default_tread_verts = [Vector([0, tread_rise]), Vector([tread_run, tread_rise])]
|
||||
default_tread_offset = Vector([tread_run, tread_rise])
|
||||
|
||||
def get_tread_data(i):
|
||||
if custom_tread_run:
|
||||
current_tread_run = None
|
||||
if i == 0:
|
||||
current_tread_run = custom_first_last_tread_run[0]
|
||||
elif i == number_of_risers - 1:
|
||||
current_tread_run = custom_first_last_tread_run[1]
|
||||
|
||||
if current_tread_run:
|
||||
tread_offset = default_tread_offset.copy()
|
||||
tread_offset.x = current_tread_run
|
||||
tread_verts = deepcopy(default_tread_verts)
|
||||
tread_verts[1].x = current_tread_run
|
||||
return tread_offset, tread_verts
|
||||
return default_tread_offset, default_tread_verts
|
||||
|
||||
# treads
|
||||
current_offset = V(0, 0)
|
||||
for i in range(number_of_risers):
|
||||
tread_offset, tread_verts = get_tread_data(i)
|
||||
current_tread_verts = [v + current_offset for v in tread_verts]
|
||||
last_vert_i = i * 2
|
||||
edges.extend([(last_vert_i, last_vert_i + 1), (last_vert_i + 1, last_vert_i + 2)])
|
||||
vertices.extend(current_tread_verts)
|
||||
current_offset += tread_offset
|
||||
|
||||
# add the nibs
|
||||
tread_diagonal_dir = vertices[2].normalized()
|
||||
# basically we define stair bottom line as a line at `tread_depth` distance
|
||||
# from the tread diagonal line
|
||||
# we're going it define that line, sample it and abrupt it in case it meets a slab
|
||||
# graph: https://www.desmos.com/calculator/bilmnti3cp
|
||||
tread_diagonal_dir = V(tread_run, tread_rise).normalized()
|
||||
# td_vector is clockwise orthogonal vector
|
||||
td_vector = tread_diagonal_dir.yx * V(1, -1) * tread_depth
|
||||
|
||||
# graph: https://www.desmos.com/calculator/bilmnti3cp
|
||||
stair_tan = tread_rise / tread_run
|
||||
s0 = vertices[0] + td_vector
|
||||
# s0 is just a sampled point from the bottom line
|
||||
# we stick to the third point as the first point
|
||||
# is affected by customized tread run
|
||||
s0 = vertices[2] + td_vector
|
||||
# comes from y = stair_tan * x + b
|
||||
b = s0.y - stair_tan * s0.x
|
||||
# if we use td_vector as depth_vector
|
||||
# then stair won't be perpendicular to the X+
|
||||
# to make it perpendicular to X+ we calculate `b`
|
||||
# `b` is basically Z-offset where stair starts
|
||||
depth_vector = V(0, b)
|
||||
|
||||
def get_point_on_2d_line(x=None, y=None):
|
||||
if y is None:
|
||||
y = stair_tan * x + b
|
||||
elif x is None:
|
||||
x = (y - b) / stair_tan
|
||||
return V(x, y)
|
||||
|
||||
# top nib
|
||||
last_vert = vertices[-1]
|
||||
@@ -1027,30 +1113,27 @@ class Model(blenderbim.core.tool.Model):
|
||||
# NOTE: has_top_nib = False and top_slab_depth are different things
|
||||
if has_top_nib:
|
||||
vertices.append(last_vert + Vector((0, -top_slab_depth)))
|
||||
slab_overlaps_stair = abs(b) - top_slab_depth
|
||||
# run distance by X until stair will meet the slab
|
||||
run_slab_distance = slab_overlaps_stair / stair_tan
|
||||
vertices.append(last_vert + Vector((run_slab_distance, -top_slab_depth)))
|
||||
vertices.append(get_point_on_2d_line(y=last_vert.y - top_slab_depth))
|
||||
edges.append((last_vertex_i, last_vertex_i + 1))
|
||||
edges.append((last_vertex_i + 1, last_vertex_i + 2))
|
||||
else:
|
||||
vertices.append(last_vert + depth_vector)
|
||||
new_vert = get_point_on_2d_line(last_vert.x)
|
||||
vertices.append(new_vert)
|
||||
edges.append((last_vertex_i, last_vertex_i + 1))
|
||||
|
||||
top_nib_end = len(vertices) - 1
|
||||
|
||||
# bottom nib
|
||||
start_vert = vertices[0]
|
||||
slab_overlaps_stair = max(abs(b) - base_slab_depth, 0)
|
||||
if slab_overlaps_stair == 0:
|
||||
base_point = get_point_on_2d_line(x=start_vert.x)
|
||||
if base_point.y > -base_slab_depth:
|
||||
# stair doesn't meet the slab
|
||||
vertices.append(start_vert + depth_vector)
|
||||
vertices.append(base_point)
|
||||
edges.append((0, len(vertices) - 1))
|
||||
bottom_nib_end = len(vertices) - 1
|
||||
else:
|
||||
# run distance by X until stair will meet the slab
|
||||
run_slab_distance = slab_overlaps_stair / stair_tan
|
||||
vertices.append(start_vert + Vector((run_slab_distance, -base_slab_depth)))
|
||||
# slab overlaps stair
|
||||
vertices.append(get_point_on_2d_line(y=start_vert.y - base_slab_depth))
|
||||
vertices.append(start_vert + Vector((0, -base_slab_depth)))
|
||||
last_vertex_i = len(vertices) - 1
|
||||
edges.append((0, last_vertex_i))
|
||||
|
||||
Reference in New Issue
Block a user