diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 2b167bd730..052d14952a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -201,13 +201,13 @@ class BIMStairProperties(PropertyGroup): 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) + number_of_treads: bpy.props.IntProperty(name="Number of Treads", default=6, soft_min=1) 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") - 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") + 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") def get_props_kwargs(self, convert_to_project_units=False, stair_type=None): if not stair_type: diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index 11f0573d49..96fd58528c 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -924,8 +924,8 @@ class Model(blenderbim.core.tool.Model): 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["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) return calculated_params @@ -956,98 +956,111 @@ class Model(blenderbim.core.tool.Model): if stair_type == "WOOD/STEEL": builder = ShapeBuilder(None) tread_shape = builder.get_rectangle_coords( - size=V(tread_run, 0, tread_depth), position=V(0, 0, -(tread_depth - tread_rise)) + size=V(tread_run, tread_depth), position=V(0, -(tread_depth - tread_rise)) ) - tread_offset = V(tread_run, 0, tread_rise) + tread_offset = V(tread_run, tread_rise) + # each tread is a separate shape for i in range(number_of_risers): cur_trade_shape = [v + tread_offset * i for v in tread_shape] vertices.extend(cur_trade_shape) cur_vertex = i * 4 - edges.extend( - [ - (cur_vertex, cur_vertex + 1), - (cur_vertex + 1, cur_vertex + 2), - (cur_vertex + 2, cur_vertex + 3), - (cur_vertex + 3, cur_vertex), - ] + verts_to_add = ( + (cur_vertex, cur_vertex + 1), + (cur_vertex + 1, cur_vertex + 2), + (cur_vertex + 2, cur_vertex + 3), + (cur_vertex + 3, cur_vertex), ) - faces.append(list(range(cur_vertex, cur_vertex + 1))) - - return (vertices, edges, faces) + edges.extend(verts_to_add) elif stair_type == "GENERIC": - vertices.append(Vector([0, 0, 0])) + vertices.append(Vector([0, 0])) - tread_verts = [Vector([0, 0, tread_rise]), Vector([tread_run, 0, tread_rise])] - tread_offset = Vector([tread_run, 0, tread_rise]) + tread_verts = [Vector([0, tread_rise]), Vector([tread_run, tread_rise])] + tread_offset = Vector([tread_run, tread_rise]) + # treads for i in range(number_of_risers): current_tread_verts = [v + tread_offset * i for v in tread_verts] - last_vert_i = len(vertices) - 1 + 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) + # close the shape last_vert_i = len(vertices) - vertices.append(vertices[-1] * V(1, 0, 0)) + vertices.append(vertices[-1] * V(1, 0)) edges.extend([(last_vert_i - 1, last_vert_i), (last_vert_i, 0)]) - return (vertices, edges, faces) - elif stair_type == "CONCRETE": + # treads for i in range(number_of_risers): - vertices.extend( - [Vector((tread_run * i, 0, tread_rise * i)), Vector((tread_run * i, 0, tread_rise * (i + 1)))] - ) + 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(Vector((tread_run * number_of_risers, 0, tread_rise * number_of_risers))) + vertices.append(Vector((tread_run * number_of_risers, tread_rise * number_of_risers))) edges.append((number_of_risers * 2, number_of_risers * 2 - 1)) - td_vector = Vector((vertices[2][2], 0, -vertices[2][0])).normalized() * tread_depth + # add the nibs + tread_diagonal_dir = vertices[2].normalized() + # td_vector is clockwise orthogonal vector + td_vector = tread_diagonal_dir.yx * V(1, -1) * tread_depth - k = tread_rise / tread_run + # graph: https://www.desmos.com/calculator/bilmnti3cp + stair_tan = tread_rise / tread_run s0 = vertices[0] + td_vector - b = s0.z - k * s0.x # comes from y = kx + b - # you could use td_vector as depth_vector - # but then stair won't be perpendicular to the X+ - # b is kind of vertical tread_depth (along Z+) - depth_vector = Vector((0, 0, b)) + # 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) # top nib + last_vert = vertices[-1] + last_vertex_i = len(vertices) - 1 + # NOTE: has_top_nib = False and top_slab_depth are different things if has_top_nib: - vertices.append(vertices[number_of_risers * 2] + Vector((0, 0, -top_slab_depth))) - vertices.append( - vertices[number_of_risers * 2] + Vector(((-top_slab_depth - b) / k, 0, -top_slab_depth)) - ) - last_vertex_i = len(vertices) - 1 - edges.append((number_of_risers * 2, last_vertex_i - 1)) - edges.append((last_vertex_i - 1, last_vertex_i)) + 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))) + edges.append((last_vertex_i, last_vertex_i + 1)) + edges.append((last_vertex_i + 1, last_vertex_i + 2)) else: - vertices.append(vertices[number_of_risers * 2] + depth_vector) - last_vertex_i = len(vertices) - 1 - edges.append((number_of_risers * 2, last_vertex_i)) + vertices.append(last_vert + depth_vector) + edges.append((last_vertex_i, last_vertex_i + 1)) top_nib_end = len(vertices) - 1 # bottom nib - if abs(b) <= base_slab_depth: - vertices.append(vertices[0] + depth_vector) + start_vert = vertices[0] + slab_overlaps_stair = max(abs(b) - base_slab_depth, 0) + if slab_overlaps_stair == 0: + # stair doesn't meet the slab + vertices.append(start_vert + depth_vector) edges.append((0, len(vertices) - 1)) bottom_nib_end = len(vertices) - 1 else: - vertices.append(vertices[0] + Vector(((-base_slab_depth - b) / k, 0, -base_slab_depth))) - vertices.append(vertices[0] + Vector((0, 0, -base_slab_depth))) + # 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))) + vertices.append(start_vert + Vector((0, -base_slab_depth))) last_vertex_i = len(vertices) - 1 edges.append((0, last_vertex_i)) edges.append((last_vertex_i - 1, last_vertex_i)) bottom_nib_end = len(vertices) - 2 + # close the shape edges.append((bottom_nib_end, top_nib_end)) - faces = [list(range(len(vertices)))] + else: + raise Exception(f"Unsupported stair type: {stair_type}") - return (vertices, edges, faces) + vertices = (v.to_3d().xzy for v in vertices) + return (vertices, edges, faces) diff --git a/src/blenderbim/test/tool/test_model.py b/src/blenderbim/test/tool/test_model.py index a2d8375af9..8bab751641 100644 --- a/src/blenderbim/test/tool/test_model.py +++ b/src/blenderbim/test/tool/test_model.py @@ -158,7 +158,7 @@ class TestGenerateStair2DProfile(NewFile): (10, 11), (10, 9), ) - faces_data = ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),) + faces_data = () expected_profile = (verts_data, edges_data, faces_data) generated_profile = subject.generate_stair_2d_profile(**kwargs) self.compare_data(generated_profile, expected_profile) @@ -207,7 +207,7 @@ class TestGenerateStair2DProfile(NewFile): (11, 10), ) - faces_data = ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),) + faces_data = () expected_profile = (verts_data, edges_data, faces_data) generated_profile = subject.generate_stair_2d_profile(**kwargs) self.compare_data(generated_profile, expected_profile) @@ -259,12 +259,7 @@ class TestGenerateStair2DProfile(NewFile): (15, 12), ) - faces_data = ( - (0,), - (4,), - (8,), - (12,), - ) + faces_data = () expected_profile = (verts_data, edges_data, faces_data) generated_profile = subject.generate_stair_2d_profile(**kwargs)