move generate_stair_2d_profile to tool, add tests

This commit is contained in:
Andrej730
2023-11-02 10:54:47 +05:00
parent 4336b9c1ae
commit 7d25261395
3 changed files with 322 additions and 121 deletions
@@ -27,7 +27,6 @@ from bmesh.types import BMVert
from bpy.types import Operator from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty from bpy.props import FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add from bpy_extras.object_utils import AddObjectHelper, object_data_add
from ifcopenshell.util.shape_builder import V, ShapeBuilder
def add_dumb_stair_object(self, context): def add_dumb_stair_object(self, context):
@@ -78,129 +77,10 @@ class BIM_OT_add_object(Operator, AddObjectHelper):
return {"FINISHED"} return {"FINISHED"}
def generate_stair_2d_profile(
number_of_treads,
height,
width,
tread_run,
stair_type,
# WOOD/STEEL CONCRETE STAIR ARGUMENTS
tread_depth=None,
# CONCRETE STAIR ARGUMENTS
has_top_nib=None,
top_slab_depth=None,
base_slab_depth=None,
):
vertices = []
edges = []
faces = []
number_of_risers = number_of_treads + 1
tread_rise = height / number_of_risers
length = tread_run * number_of_risers
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))
)
tread_offset = V(tread_run, 0, tread_rise)
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),
]
)
faces.append(list(range(cur_vertex, cur_vertex + 1)))
return (vertices, edges, faces)
elif stair_type == "GENERIC":
vertices.append(Vector([0, 0, 0]))
tread_verts = [Vector([0, 0, tread_rise]), Vector([tread_run, 0, tread_rise])]
tread_offset = Vector([tread_run, 0, tread_rise])
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
edges.extend([(last_vert_i, last_vert_i + 1), (last_vert_i + 1, last_vert_i + 2)])
vertices.extend(current_tread_verts)
last_vert_i = len(vertices)
vertices.append(vertices[-1] * V(1, 0, 0))
edges.extend([(last_vert_i - 1, last_vert_i), (last_vert_i, 0)])
return (vertices, edges, faces)
elif stair_type == "CONCRETE":
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)))]
)
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)))
edges.append((number_of_risers * 2, number_of_risers * 2 - 1))
td_vector = Vector((vertices[2][2], 0, -vertices[2][0])).normalized() * tread_depth
k = 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))
# top nib
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))
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))
top_nib_end = len(vertices) - 1
# bottom nib
if abs(b) <= base_slab_depth:
vertices.append(vertices[0] + 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)))
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
edges.append((bottom_nib_end, top_nib_end))
faces = [list(range(len(vertices)))]
return (vertices, edges, faces)
def regenerate_stair_mesh(context): def regenerate_stair_mesh(context):
obj = context.active_object obj = context.active_object
props_kwargs = obj.BIMStairProperties.get_props_kwargs() props_kwargs = obj.BIMStairProperties.get_props_kwargs()
vertices, edges, faces = generate_stair_2d_profile(**props_kwargs) vertices, edges, faces = tool.Model.generate_stair_2d_profile(**props_kwargs)
obj = context.active_object obj = context.active_object
bm = bmesh.new() bm = bmesh.new()
+124
View File
@@ -32,6 +32,7 @@ from mathutils import Matrix, Vector
from blenderbim.bim import import_ifc from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.helper import Helper from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from ifcopenshell.util.shape_builder import V, ShapeBuilder
class Model(blenderbim.core.tool.Model): class Model(blenderbim.core.tool.Model):
@@ -906,3 +907,126 @@ class Model(blenderbim.core.tool.Model):
@classmethod @classmethod
def is_parametric_door_active(cls): def is_parametric_door_active(cls):
return (DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"] return (DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"]
@classmethod
def generate_stair_2d_profile(
cls,
number_of_treads,
height,
width,
tread_run,
stair_type,
# WOOD/STEEL CONCRETE STAIR ARGUMENTS
tread_depth=None,
# CONCRETE STAIR ARGUMENTS
has_top_nib=None,
top_slab_depth=None,
base_slab_depth=None,
):
"""returns a tuple of stair profile data: (vertices, edges, faces)"""
vertices = []
edges = []
faces = []
number_of_risers = number_of_treads + 1
tread_rise = height / number_of_risers
length = tread_run * number_of_risers
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))
)
tread_offset = V(tread_run, 0, tread_rise)
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),
]
)
faces.append(list(range(cur_vertex, cur_vertex + 1)))
return (vertices, edges, faces)
elif stair_type == "GENERIC":
vertices.append(Vector([0, 0, 0]))
tread_verts = [Vector([0, 0, tread_rise]), Vector([tread_run, 0, tread_rise])]
tread_offset = Vector([tread_run, 0, tread_rise])
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
edges.extend([(last_vert_i, last_vert_i + 1), (last_vert_i + 1, last_vert_i + 2)])
vertices.extend(current_tread_verts)
last_vert_i = len(vertices)
vertices.append(vertices[-1] * V(1, 0, 0))
edges.extend([(last_vert_i - 1, last_vert_i), (last_vert_i, 0)])
return (vertices, edges, faces)
elif stair_type == "CONCRETE":
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)))]
)
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)))
edges.append((number_of_risers * 2, number_of_risers * 2 - 1))
td_vector = Vector((vertices[2][2], 0, -vertices[2][0])).normalized() * tread_depth
k = 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))
# top nib
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))
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))
top_nib_end = len(vertices) - 1
# bottom nib
if abs(b) <= base_slab_depth:
vertices.append(vertices[0] + 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)))
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
edges.append((bottom_nib_end, top_nib_end))
faces = [list(range(len(vertices)))]
return (vertices, edges, faces)
+197
View File
@@ -23,6 +23,7 @@ import blenderbim.tool as tool
import numpy as np import numpy as np
from test.bim.bootstrap import NewFile from test.bim.bootstrap import NewFile
from blenderbim.tool.model import Model as subject from blenderbim.tool.model import Model as subject
from ifcopenshell.util.shape_builder import V
class TestImplementsTool(NewFile): class TestImplementsTool(NewFile):
@@ -105,3 +106,199 @@ class TestGetManualBooleans(NewFile):
assert len(subject.get_manual_booleans(element)) == 0 assert len(subject.get_manual_booleans(element)) == 0
subject.mark_manual_booleans(element, booleans) subject.mark_manual_booleans(element, booleans)
assert len(subject.get_manual_booleans(element)) == 2 assert len(subject.get_manual_booleans(element)) == 2
class TestGenerateStair2DProfile(NewFile):
def compare_data(self, generated_profile, expected_profile):
verts_gen, edges_gen, faces_gen = generated_profile
verts, edges, faces = expected_profile
assert edges == tuple(edges_gen)
assert faces == tuple(tuple(face) for face in faces_gen)
for vert, vert_gen in zip(verts, verts_gen, strict=True):
assert tool.Cad.are_vectors_equal(vert, vert_gen, 0.01)
def test_create_concrete_stair(self):
kwargs = {
"base_slab_depth": 0.25,
"has_top_nib": False,
"height": 1.0,
"number_of_treads": 3,
"stair_type": "CONCRETE",
"top_slab_depth": 0.25,
"tread_depth": 0.25,
"tread_run": 0.3,
"width": 1.2,
}
verts_data = (
V(0.0, 0, 0.0),
V(0.0, 0, 0.25),
V(0.3, 0, 0.25),
V(0.3, 0, 0.5),
V(0.6, 0, 0.5),
V(0.6, 0, 0.75),
V(0.9, 0, 0.75),
V(0.9, 0, 1.0),
V(1.2, 0, 1.0),
V(1.2, 0, 0.67457),
V(0.1, 0, -0.25),
V(0.0, 0, -0.25),
)
edges_data = (
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 5),
(5, 6),
(6, 7),
(8, 7),
(8, 9),
(0, 11),
(10, 11),
(10, 9),
)
faces_data = ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),)
expected_profile = (verts_data, edges_data, faces_data)
generated_profile = subject.generate_stair_2d_profile(**kwargs)
self.compare_data(generated_profile, expected_profile)
def test_create_concrete_stair_nib(self):
kwargs = {
"base_slab_depth": 0.25,
"has_top_nib": True,
"height": 1.0,
"number_of_treads": 3,
"stair_type": "CONCRETE",
"top_slab_depth": 0.25,
"tread_depth": 0.25,
"tread_run": 0.3,
"width": 1.2,
}
verts_data = (
V(0.0, 0, 0.0),
V(0.0, 0, 0.25),
V(0.3, 0, 0.25),
V(0.3, 0, 0.5),
V(0.6, 0, 0.5),
V(0.6, 0, 0.75),
V(0.9, 0, 0.75),
V(0.9, 0, 1.0),
V(1.2, 0, 1.0),
V(1.2, 0, 0.75),
V(1.3, 0, 0.75),
V(0.1, 0, -0.25),
V(0.0, 0, -0.25),
)
edges_data = (
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 5),
(5, 6),
(6, 7),
(8, 7),
(8, 9),
(9, 10),
(0, 12),
(11, 12),
(11, 10),
)
faces_data = ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),)
expected_profile = (verts_data, edges_data, faces_data)
generated_profile = subject.generate_stair_2d_profile(**kwargs)
self.compare_data(generated_profile, expected_profile)
def test_create_wood_steel_stair(self):
kwargs = {
"height": 1.0,
"number_of_treads": 3,
"stair_type": "WOOD/STEEL",
"tread_depth": 0.25,
"tread_run": 0.3,
"width": 1.2,
}
verts_data = (
V(0.0, 0, 0.0),
V(0.3, 0, 0.0),
V(0.3, 0, 0.25),
V(0.0, 0, 0.25),
V(0.3, 0, 0.25),
V(0.6, 0, 0.25),
V(0.6, 0, 0.5),
V(0.3, 0, 0.5),
V(0.6, 0, 0.5),
V(0.9, 0, 0.5),
V(0.9, 0, 0.75),
V(0.6, 0, 0.75),
V(0.9, 0, 0.75),
V(1.2, 0, 0.75),
V(1.2, 0, 1.0),
V(0.9, 0, 1.0),
)
edges_data = (
(0, 1),
(1, 2),
(2, 3),
(3, 0),
(4, 5),
(5, 6),
(6, 7),
(7, 4),
(8, 9),
(9, 10),
(10, 11),
(11, 8),
(12, 13),
(13, 14),
(14, 15),
(15, 12),
)
faces_data = (
(0,),
(4,),
(8,),
(12,),
)
expected_profile = (verts_data, edges_data, faces_data)
generated_profile = subject.generate_stair_2d_profile(**kwargs)
self.compare_data(generated_profile, expected_profile)
def test_create_generic_stair(self):
kwargs = {"height": 1.0, "number_of_treads": 3, "stair_type": "GENERIC", "tread_run": 0.3, "width": 1.2}
verts_data = (
V(0.0, 0, 0.0),
V(0.0, 0, 0.25),
V(0.3, 0, 0.25),
V(0.3, 0, 0.5),
V(0.6, 0, 0.5),
V(0.6, 0, 0.75),
V(0.9, 0, 0.75),
V(0.9, 0, 1.0),
V(1.2, 0, 1.0),
V(1.2, 0, 0.0),
)
edges_data = (
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 5),
(5, 6),
(6, 7),
(7, 8),
(8, 9),
(9, 0),
)
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)