mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
For stairs, Lock total length now takes first and last tread widths into account as well.
This commit is contained in:
@@ -345,26 +345,138 @@ class BIMArrayProperties(PropertyGroup):
|
|||||||
method: Literal["OFFSET", "DISTRIBUTE"]
|
method: Literal["OFFSET", "DISTRIBUTE"]
|
||||||
sync_children: bool
|
sync_children: bool
|
||||||
relating_array_object: Union[bpy.types.Object, None]
|
relating_array_object: Union[bpy.types.Object, None]
|
||||||
|
|
||||||
|
|
||||||
def update_total_length_target(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
def update_total_length_target(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||||
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
"""Update tread_run when total_length_target changes"""
|
||||||
|
# Calculate available length for default treads
|
||||||
|
available_length = self.total_length_target
|
||||||
|
n_default_treads = self.number_of_treads + 1 # number of risers
|
||||||
|
|
||||||
|
# Subtract custom first tread if not locked and not zero
|
||||||
|
if not self.custom_tread_lock and self.custom_first_last_tread_run[0] != 0:
|
||||||
|
available_length -= self.custom_first_last_tread_run[0]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
# Subtract custom last tread if not locked and not zero
|
||||||
|
if not self.custom_tread_lock and self.custom_first_last_tread_run[1] != 0:
|
||||||
|
available_length -= self.custom_first_last_tread_run[1]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
# Calculate tread_run for remaining treads
|
||||||
|
if n_default_treads > 0:
|
||||||
|
self["tread_run"] = available_length / n_default_treads
|
||||||
|
else:
|
||||||
|
# All treads are custom, just use target length
|
||||||
|
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
||||||
|
|
||||||
|
|
||||||
def update_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
def update_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||||
|
"""Update either number_of_treads or total_length_target when tread_run changes"""
|
||||||
if self.total_length_lock:
|
if self.total_length_lock:
|
||||||
self["number_of_treads"] = int((self.total_length_target / self.tread_run) - 1)
|
# Calculate how much length custom treads take up
|
||||||
|
custom_length = 0
|
||||||
|
n_custom_treads = 0
|
||||||
|
|
||||||
|
if not self.custom_tread_lock:
|
||||||
|
if self.custom_first_last_tread_run[0] != 0:
|
||||||
|
custom_length += self.custom_first_last_tread_run[0]
|
||||||
|
n_custom_treads += 1
|
||||||
|
if self.custom_first_last_tread_run[1] != 0:
|
||||||
|
custom_length += self.custom_first_last_tread_run[1]
|
||||||
|
n_custom_treads += 1
|
||||||
|
|
||||||
|
# Calculate how many default treads fit in remaining space
|
||||||
|
available_length = self.total_length_target - custom_length
|
||||||
|
if self.tread_run > 0:
|
||||||
|
n_default_treads = available_length / self.tread_run
|
||||||
|
total_treads = n_default_treads + n_custom_treads
|
||||||
|
# number_of_treads = number_of_risers - 1
|
||||||
|
self["number_of_treads"] = int(total_treads - 1)
|
||||||
else:
|
else:
|
||||||
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
|
# Calculate total length from current settings
|
||||||
|
n_default_treads = self.number_of_treads + 1
|
||||||
|
total_length = 0
|
||||||
|
|
||||||
|
if not self.custom_tread_lock:
|
||||||
|
if self.custom_first_last_tread_run[0] != 0:
|
||||||
|
total_length += self.custom_first_last_tread_run[0]
|
||||||
|
n_default_treads -= 1
|
||||||
|
if self.custom_first_last_tread_run[1] != 0:
|
||||||
|
total_length += self.custom_first_last_tread_run[1]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
total_length += n_default_treads * self.tread_run
|
||||||
|
self["total_length_target"] = total_length
|
||||||
|
|
||||||
|
|
||||||
def update_number_of_treads(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
def update_number_of_treads(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||||
|
"""Update either tread_run or total_length_target when number_of_treads changes"""
|
||||||
if self.total_length_lock:
|
if self.total_length_lock:
|
||||||
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
# Calculate available length for default treads
|
||||||
|
available_length = self.total_length_target
|
||||||
|
n_default_treads = self.number_of_treads + 1
|
||||||
|
|
||||||
|
if not self.custom_tread_lock:
|
||||||
|
if self.custom_first_last_tread_run[0] != 0:
|
||||||
|
available_length -= self.custom_first_last_tread_run[0]
|
||||||
|
n_default_treads -= 1
|
||||||
|
if self.custom_first_last_tread_run[1] != 0:
|
||||||
|
available_length -= self.custom_first_last_tread_run[1]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
if n_default_treads > 0:
|
||||||
|
self["tread_run"] = available_length / n_default_treads
|
||||||
|
else:
|
||||||
|
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
||||||
else:
|
else:
|
||||||
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
|
# Calculate total length from current settings
|
||||||
|
n_default_treads = self.number_of_treads + 1
|
||||||
|
total_length = 0
|
||||||
|
|
||||||
|
if not self.custom_tread_lock:
|
||||||
|
if self.custom_first_last_tread_run[0] != 0:
|
||||||
|
total_length += self.custom_first_last_tread_run[0]
|
||||||
|
n_default_treads -= 1
|
||||||
|
if self.custom_first_last_tread_run[1] != 0:
|
||||||
|
total_length += self.custom_first_last_tread_run[1]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
total_length += n_default_treads * self.tread_run
|
||||||
|
self["total_length_target"] = total_length
|
||||||
|
|
||||||
|
|
||||||
|
def update_custom_first_last_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||||
|
"""Update tread_run or total_length when custom treads change"""
|
||||||
|
if self.total_length_lock:
|
||||||
|
# Recalculate tread_run to maintain total length
|
||||||
|
available_length = self.total_length_target
|
||||||
|
n_default_treads = self.number_of_treads + 1
|
||||||
|
|
||||||
|
if not self.custom_tread_lock:
|
||||||
|
if self.custom_first_last_tread_run[0] != 0:
|
||||||
|
available_length -= self.custom_first_last_tread_run[0]
|
||||||
|
n_default_treads -= 1
|
||||||
|
if self.custom_first_last_tread_run[1] != 0:
|
||||||
|
available_length -= self.custom_first_last_tread_run[1]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
if n_default_treads > 0:
|
||||||
|
self["tread_run"] = available_length / n_default_treads
|
||||||
|
else:
|
||||||
|
# Recalculate total length
|
||||||
|
n_default_treads = self.number_of_treads + 1
|
||||||
|
total_length = 0
|
||||||
|
|
||||||
|
if not self.custom_tread_lock:
|
||||||
|
if self.custom_first_last_tread_run[0] != 0:
|
||||||
|
total_length += self.custom_first_last_tread_run[0]
|
||||||
|
n_default_treads -= 1
|
||||||
|
if self.custom_first_last_tread_run[1] != 0:
|
||||||
|
total_length += self.custom_first_last_tread_run[1]
|
||||||
|
n_default_treads -= 1
|
||||||
|
|
||||||
|
total_length += n_default_treads * self.tread_run
|
||||||
|
self["total_length_target"] = total_length
|
||||||
|
|
||||||
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
|
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
|
||||||
|
|
||||||
|
|
||||||
@@ -419,13 +531,14 @@ class BIMStairProperties(PropertyGroup):
|
|||||||
update=update_custom_tread_lock,
|
update=update_custom_tread_lock,
|
||||||
)
|
)
|
||||||
custom_first_last_tread_run: bpy.props.FloatVectorProperty(
|
custom_first_last_tread_run: bpy.props.FloatVectorProperty(
|
||||||
name="Custom First / Last Treads Widths",
|
name="Custom First / Last Treads Widths",
|
||||||
description='Specify custom first / last treads widths, different from the general "Tread Run". Leave 0 to disable.',
|
description='Specify custom first / last treads widths, different from the general "Tread Run". Leave 0 to disable.',
|
||||||
default=(0, 0),
|
default=(0, 0),
|
||||||
min=0,
|
min=0,
|
||||||
unit="LENGTH",
|
unit="LENGTH",
|
||||||
size=2,
|
size=2,
|
||||||
)
|
update=update_custom_first_last_tread_run, # Added update callback
|
||||||
|
)
|
||||||
nosing_length: bpy.props.FloatProperty(
|
nosing_length: bpy.props.FloatProperty(
|
||||||
name="Nosing Length",
|
name="Nosing Length",
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -1324,7 +1324,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def is_parametric_door_active(cls) -> bool:
|
def is_parametric_door_active(cls) -> bool:
|
||||||
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
|
||||||
def get_active_stair_calculated_params(cls, pset_data: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
def get_active_stair_calculated_params(cls, pset_data: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
||||||
props = bpy.context.active_object.BIMStairProperties
|
props = bpy.context.active_object.BIMStairProperties
|
||||||
@@ -1352,24 +1352,31 @@ class Model(bonsai.core.tool.Model):
|
|||||||
calculated_params["Tread Rise"] = round(height / number_of_rises, 5)
|
calculated_params["Tread Rise"] = round(height / number_of_rises, 5)
|
||||||
|
|
||||||
# calculate stair length
|
# calculate stair length
|
||||||
|
# Start with all treads using default tread_run
|
||||||
n_default_tread_runs = number_of_rises
|
n_default_tread_runs = number_of_rises
|
||||||
length = 0
|
length = 0
|
||||||
|
|
||||||
|
# If first tread has custom width (non-zero), use it instead of default
|
||||||
if first_tread_run != 0:
|
if first_tread_run != 0:
|
||||||
n_default_tread_runs -= 1
|
n_default_tread_runs -= 1
|
||||||
length += first_tread_run
|
length += first_tread_run
|
||||||
|
|
||||||
|
# If last tread has custom width (non-zero), use it instead of default
|
||||||
if last_tread_run != 0:
|
if last_tread_run != 0:
|
||||||
n_default_tread_runs -= 1
|
n_default_tread_runs -= 1
|
||||||
if n_default_tread_runs >= 0:
|
length += last_tread_run
|
||||||
length += last_tread_run
|
|
||||||
length += tread_run * max(n_default_tread_runs, 0)
|
# Add remaining default tread runs
|
||||||
# nosing overlaps
|
length += tread_run * n_default_tread_runs
|
||||||
# are not part of the tread run
|
|
||||||
# so they don't affect the stair length
|
# Handle nosing length effects on total length
|
||||||
# except the first tread's nosing
|
# Nosing overlaps don't affect tread run spacing,
|
||||||
|
# but the first tread's nosing extends the total length
|
||||||
if nosing_length > 0: # nosing overlaps
|
if nosing_length > 0: # nosing overlaps
|
||||||
length += nosing_length
|
length += nosing_length
|
||||||
if nosing_length < 0: # tread gaps
|
if nosing_length < 0: # tread gaps between treads
|
||||||
length += abs(nosing_length) * number_of_treads
|
length += abs(nosing_length) * number_of_treads
|
||||||
|
|
||||||
calculated_params["Length"] = round(length, 5)
|
calculated_params["Length"] = round(length, 5)
|
||||||
pitch = height / length
|
pitch = height / length
|
||||||
pitch_formatted = str(round(pitch * 100, 1)) + " % / " + str(round(degrees(atan(pitch)), 1)) + " deg"
|
pitch_formatted = str(round(pitch * 100, 1)) + " % / " + str(round(degrees(atan(pitch)), 1)) + " deg"
|
||||||
@@ -1407,6 +1414,28 @@ class Model(bonsai.core.tool.Model):
|
|||||||
nosing_tread_gap = -min(nosing_length, 0)
|
nosing_tread_gap = -min(nosing_length, 0)
|
||||||
nosing_overlap_offset = -V_(nosing_overlap, 0)
|
nosing_overlap_offset = -V_(nosing_overlap, 0)
|
||||||
|
|
||||||
|
# ============ DEBUG LOGGING START ============
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("STAIR GENERATION DEBUG - WOOD/STEEL TYPE")
|
||||||
|
print("="*80)
|
||||||
|
print(f"Input Parameters:")
|
||||||
|
print(f" stair_type: {stair_type}")
|
||||||
|
print(f" number_of_treads: {number_of_treads}")
|
||||||
|
print(f" height: {height}")
|
||||||
|
print(f" width: {width}")
|
||||||
|
print(f" tread_run: {tread_run}")
|
||||||
|
print(f" tread_depth: {tread_depth}")
|
||||||
|
print(f" custom_first_last_tread_run: {custom_first_last_tread_run}")
|
||||||
|
print(f" nosing_length: {nosing_length}")
|
||||||
|
print(f"\nCalculated Parameters:")
|
||||||
|
print(f" number_of_risers: {number_of_risers}")
|
||||||
|
print(f" tread_rise: {tread_rise}")
|
||||||
|
print(f" nosing_overlap: {nosing_overlap}")
|
||||||
|
print(f" nosing_tread_gap: {nosing_tread_gap}")
|
||||||
|
print(f" nosing_overlap_offset: {nosing_overlap_offset}")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
# ============ DEBUG LOGGING END ============
|
||||||
|
|
||||||
def define_generic_stair_treads():
|
def define_generic_stair_treads():
|
||||||
vertices.append(Vector([0, 0]))
|
vertices.append(Vector([0, 0]))
|
||||||
nonlocal nosing_depth, nosing_overlap
|
nonlocal nosing_depth, nosing_overlap
|
||||||
@@ -1470,6 +1499,10 @@ class Model(bonsai.core.tool.Model):
|
|||||||
current_offset += tread_offset
|
current_offset += tread_offset
|
||||||
|
|
||||||
if stair_type == "WOOD/STEEL":
|
if stair_type == "WOOD/STEEL":
|
||||||
|
print("\n" + "-"*80)
|
||||||
|
print("WOOD/STEEL STAIR GENERATION PROCESS")
|
||||||
|
print("-"*80)
|
||||||
|
|
||||||
builder = ShapeBuilder(None)
|
builder = ShapeBuilder(None)
|
||||||
|
|
||||||
# full tread rectangle
|
# full tread rectangle
|
||||||
@@ -1480,6 +1513,11 @@ class Model(bonsai.core.tool.Model):
|
|||||||
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)
|
||||||
|
|
||||||
|
print(f"\nDefault Tread Configuration:")
|
||||||
|
print(f" default_tread_verts: {default_tread_verts}")
|
||||||
|
print(f" default_tread_offset: {default_tread_offset}")
|
||||||
|
print(f" tread rectangle size: ({tread_run + nosing_overlap}, {tread_depth})")
|
||||||
|
|
||||||
def get_tread_data(i):
|
def get_tread_data(i):
|
||||||
# Check if this is first or last tread with custom run
|
# Check if this is first or last tread with custom run
|
||||||
current_tread_run = None
|
current_tread_run = None
|
||||||
@@ -1488,28 +1526,48 @@ class Model(bonsai.core.tool.Model):
|
|||||||
elif i == number_of_risers - 1 and custom_first_last_tread_run[1] is not None:
|
elif i == number_of_risers - 1 and custom_first_last_tread_run[1] is not None:
|
||||||
current_tread_run = custom_first_last_tread_run[1]
|
current_tread_run = custom_first_last_tread_run[1]
|
||||||
|
|
||||||
|
print(f"\n Tread {i}:")
|
||||||
|
print(f" Is first tread: {i == 0}")
|
||||||
|
print(f" Is last tread: {i == number_of_risers - 1}")
|
||||||
|
print(f" current_tread_run: {current_tread_run}")
|
||||||
|
|
||||||
if current_tread_run is not None:
|
if current_tread_run is not None:
|
||||||
tread_offset = default_tread_offset.copy()
|
tread_offset = default_tread_offset.copy()
|
||||||
tread_offset.x = current_tread_run + nosing_tread_gap
|
tread_offset.x = current_tread_run + nosing_tread_gap
|
||||||
|
|
||||||
# Handle zero-width treads
|
# Handle zero-width treads
|
||||||
if current_tread_run == 0:
|
if current_tread_run == 0:
|
||||||
|
print(f" → Zero-width tread, skipping geometry")
|
||||||
|
print(f" → tread_offset: {tread_offset}")
|
||||||
return tread_offset, ()
|
return tread_offset, ()
|
||||||
|
|
||||||
tread_verts = get_tread_verts(size=V_(current_tread_run + nosing_overlap, tread_depth))
|
tread_verts = get_tread_verts(size=V_(current_tread_run + nosing_overlap, tread_depth))
|
||||||
|
print(f" → Custom tread")
|
||||||
|
print(f" → tread_offset: {tread_offset}")
|
||||||
|
print(f" → tread_verts: {tread_verts}")
|
||||||
|
print(f" → tread rectangle size: ({current_tread_run + nosing_overlap}, {tread_depth})")
|
||||||
return tread_offset, tread_verts
|
return tread_offset, tread_verts
|
||||||
|
|
||||||
|
print(f" → Using default tread")
|
||||||
|
print(f" → tread_offset: {default_tread_offset}")
|
||||||
return default_tread_offset, default_tread_verts
|
return default_tread_offset, default_tread_verts
|
||||||
|
|
||||||
# each tread is a separate shape
|
# each tread is a separate shape
|
||||||
cur_offset = V_(0, 0)
|
cur_offset = V_(0, 0)
|
||||||
tread_index = 0
|
tread_index = 0
|
||||||
|
|
||||||
|
print(f"\nGenerating Treads:")
|
||||||
|
print(f" Number of risers to process: {number_of_risers}")
|
||||||
|
|
||||||
for i in range(number_of_risers):
|
for i in range(number_of_risers):
|
||||||
tread_offset, tread_verts = get_tread_data(i)
|
tread_offset, tread_verts = get_tread_data(i)
|
||||||
|
|
||||||
# Skip adding vertices/edges for zero-width treads
|
# Skip adding vertices/edges for zero-width treads
|
||||||
if tread_verts:
|
if tread_verts:
|
||||||
cur_trade_shape = [v + cur_offset + nosing_overlap_offset for v in tread_verts]
|
cur_trade_shape = [v + cur_offset + nosing_overlap_offset for v in tread_verts]
|
||||||
|
print(f" Adding geometry at cur_offset: {cur_offset}")
|
||||||
|
print(f" Final vertices (after offset): {cur_trade_shape}")
|
||||||
|
|
||||||
vertices.extend(cur_trade_shape)
|
vertices.extend(cur_trade_shape)
|
||||||
|
|
||||||
cur_vertex = tread_index * 4
|
cur_vertex = tread_index * 4
|
||||||
@@ -1520,9 +1578,19 @@ class Model(bonsai.core.tool.Model):
|
|||||||
(cur_vertex + 3, cur_vertex),
|
(cur_vertex + 3, cur_vertex),
|
||||||
)
|
)
|
||||||
edges.extend(verts_to_add)
|
edges.extend(verts_to_add)
|
||||||
|
print(f" Edges added: {verts_to_add}")
|
||||||
tread_index += 1
|
tread_index += 1
|
||||||
|
else:
|
||||||
|
print(f" Skipped (no geometry)")
|
||||||
|
|
||||||
cur_offset += tread_offset
|
cur_offset += tread_offset
|
||||||
|
print(f" New cur_offset: {cur_offset}")
|
||||||
|
|
||||||
|
print(f"\nFinal Results:")
|
||||||
|
print(f" Total vertices: {len(vertices)}")
|
||||||
|
print(f" Total edges: {len(edges)}")
|
||||||
|
print(f" Total treads generated: {tread_index}")
|
||||||
|
print("-"*80 + "\n")
|
||||||
|
|
||||||
elif stair_type == "GENERIC":
|
elif stair_type == "GENERIC":
|
||||||
define_generic_stair_treads()
|
define_generic_stair_treads()
|
||||||
|
|||||||
Reference in New Issue
Block a user