-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #351 from Blueprints-org/adding_reinforcement_conf…
…igurations Big day for Blueprints :-)
- Loading branch information
Showing
27 changed files
with
2,051 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"""Module with the representation of the covers for cross-sections.""" | ||
|
||
from collections import defaultdict | ||
from dataclasses import asdict, dataclass | ||
|
||
from blueprints.type_alias import MM | ||
from blueprints.validations import raise_if_negative | ||
|
||
DEFAULT_COVER = 50 # mm | ||
|
||
|
||
@dataclass(frozen=True) | ||
class CoversRectangular: | ||
"""Representation of the covers of a rectangular cross-section.""" | ||
|
||
upper: MM = DEFAULT_COVER | ||
right: MM = DEFAULT_COVER | ||
lower: MM = DEFAULT_COVER | ||
left: MM = DEFAULT_COVER | ||
|
||
def __post_init__(self) -> None: | ||
"""Post initialization of the covers.""" | ||
self.validate() | ||
|
||
def get_covers_info(self) -> str: | ||
"""Return a string with the covers of the cross-section.""" | ||
caption_text = "Cover:" | ||
|
||
covers = defaultdict(list) | ||
|
||
for key, value in asdict(self).items(): | ||
covers[value].append(key) | ||
|
||
if len(covers) == 1: | ||
return f"{caption_text} {self.upper:.0f} mm" | ||
|
||
cover_texts = [caption_text] | ||
|
||
for cover, names in covers.items(): | ||
cover_texts.append(f"{'|'.join(names)}: {cover:.0f} mm") | ||
|
||
return "\n ".join(cover_texts) | ||
|
||
def validate(self) -> None: | ||
"""Validate the covers.""" | ||
raise_if_negative(upper=self.upper, right=self.right, lower=self.lower, left=self.left) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
203 changes: 203 additions & 0 deletions
203
blueprints/structural_sections/concrete/reinforced_concrete_sections/base.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
"""Base class of all reinforced cross-sections.""" | ||
|
||
from abc import ABC | ||
from functools import partial | ||
from typing import Callable | ||
|
||
from shapely import LineString | ||
|
||
from blueprints.materials.concrete import ConcreteMaterial | ||
from blueprints.materials.reinforcement_steel import ReinforcementSteelMaterial | ||
from blueprints.structural_sections.concrete.rebar import Rebar | ||
from blueprints.structural_sections.concrete.reinforced_concrete_sections.reinforcement_configurations import ( | ||
ReinforcementConfiguration, | ||
) | ||
from blueprints.structural_sections.concrete.stirrups import StirrupConfiguration | ||
from blueprints.structural_sections.cross_section_shapes import CrossSection | ||
from blueprints.type_alias import KG_M, KG_M3, M3_M, MM2_M | ||
from blueprints.unit_conversion import M_TO_MM, MM3_TO_M3 | ||
|
||
|
||
class ReinforcedCrossSection(ABC): | ||
"""Base class of all reinforced cross-sections.""" | ||
|
||
def __init__( | ||
self, | ||
cross_section: CrossSection, | ||
concrete_material: ConcreteMaterial, | ||
) -> None: | ||
"""Initialize the reinforced cross-section. | ||
Parameters | ||
---------- | ||
cross_section : CrossSection | ||
Cross-section of the reinforced concrete section. | ||
concrete_material : ConcreteMaterial | ||
Material properties of the concrete. | ||
""" | ||
self.cross_section = cross_section | ||
self.concrete_material = concrete_material | ||
self._reinforcement_configurations: list[tuple[LineString | Callable[..., LineString], ReinforcementConfiguration]] = [] | ||
self._single_longitudinal_rebars: list[Rebar] = [] | ||
self._stirrups: list[StirrupConfiguration] = [] | ||
|
||
@property | ||
def longitudinal_rebars(self) -> list[Rebar]: | ||
"""Return a list of all longitudinal rebars.""" | ||
rebars: list[Rebar] = [] | ||
|
||
# add the single longitudinal rebars | ||
rebars.extend(self._single_longitudinal_rebars) | ||
|
||
# add the rebars from the reinforcement configurations | ||
for line, configuration in self._reinforcement_configurations: | ||
if callable(line): | ||
# partial function with additional arguments where the line should be called to get the LineString | ||
# the implementation will be made at that level and inserted here to produce the rebars needed. | ||
# this keeps this ABC class clean and allows for a lot of flexibility in the implementation of the line. | ||
# this has been done to be able to add any shape of line to the cross-section (e.g. a circle or any other in the future). | ||
rebars.extend(configuration.to_rebars(line=line())) | ||
else: | ||
rebars.extend(configuration.to_rebars(line=line)) | ||
|
||
# check if all rebars are inside the cross-section. | ||
# needed for the case where custom configurations are added to the RCS | ||
for rebar in rebars: | ||
if not self.cross_section.geometry.contains(other=rebar.geometry): | ||
msg = f"Rebar (diameter={rebar.diameter}, x={rebar.x}, y={rebar.y}) is not (fully) inside the cross-section." | ||
raise ValueError(msg) | ||
|
||
return rebars | ||
|
||
@property | ||
def stirrups(self) -> list[StirrupConfiguration]: | ||
"""Return a list of all stirrups.""" | ||
return self._stirrups | ||
|
||
@property | ||
def reinforcement_weight_longitudinal_bars(self) -> KG_M: | ||
"""Total mass of the longitudinal reinforcement in the cross-section per meter length [kg/m].""" | ||
return sum(rebar.weight_per_meter for rebar in self.longitudinal_rebars) | ||
|
||
@property | ||
def reinforcement_weight_stirrups(self) -> KG_M: | ||
"""Total mass of the stirrups' reinforcement in the cross-section per meter length [kg/m].""" | ||
return sum(stirrup.weight_per_meter for stirrup in self._stirrups) | ||
|
||
@property | ||
def reinforcement_weight(self) -> KG_M: | ||
"""Total mass of the reinforcement in the cross-section per meter length [kg/m].""" | ||
return self.reinforcement_weight_longitudinal_bars + self.reinforcement_weight_stirrups | ||
|
||
@property | ||
def reinforcement_area_longitudinal_bars(self) -> MM2_M: | ||
"""Total area of the longitudinal reinforcement in the cross-section per meter length [mm²/m].""" | ||
return sum(rebar.area for rebar in self.longitudinal_rebars) | ||
|
||
@property | ||
def concrete_volume(self) -> M3_M: | ||
"""Total volume of the reinforced cross-section per meter length [m³/m].""" | ||
length = M_TO_MM | ||
return self.cross_section.area * length * MM3_TO_M3 | ||
|
||
@property | ||
def weight_per_volume(self) -> KG_M3: | ||
"""Total mass of the cross-section per meter length (concrete_checks+reinforcement) [kg/m³].""" | ||
return self.reinforcement_weight / self.concrete_volume | ||
|
||
def get_present_steel_materials(self) -> list[ReinforcementSteelMaterial]: | ||
"""Return a list of all present steel materials in the cross-section.""" | ||
materials = [rebar.material for rebar in self.longitudinal_rebars] | ||
materials.extend(stirrup.material for stirrup in self._stirrups) | ||
return list(set(materials)) | ||
|
||
def add_longitudinal_rebar( | ||
self, | ||
rebar: Rebar, | ||
) -> Rebar: | ||
"""Adds a single reinforcement bar to the cross-section. | ||
Parameters | ||
---------- | ||
rebar : Rebar | ||
Rebar to be added to the cross-section. | ||
Raises | ||
------ | ||
ValueError | ||
If the rebar is not fully inside the cross-section. | ||
Returns | ||
------- | ||
Rebar | ||
Newly created Rebar | ||
""" | ||
# check if given diameter/coordinates are fully inside the cross-section | ||
if not rebar.geometry.within(self.cross_section.geometry): | ||
msg = f"Rebar (diameter={rebar.diameter}, x={rebar.x}, y={rebar.y}) is not (fully) inside the cross-section." | ||
raise ValueError(msg) | ||
|
||
# add the rebar to the list of longitudinal rebars | ||
self._single_longitudinal_rebars.append(rebar) | ||
|
||
return rebar | ||
|
||
def add_stirrup_configuration(self, stirrup: StirrupConfiguration) -> StirrupConfiguration: | ||
"""Add a stirrup configuration to the cross-section. | ||
Parameters | ||
---------- | ||
stirrup : StirrupConfiguration | ||
Configuration of stirrup reinforcement in the cross-section. | ||
Returns | ||
------- | ||
StirrupConfiguration | ||
Newly created Stirrup | ||
Raises | ||
------ | ||
ValueError | ||
If the stirrup is not fully inside the cross-section. | ||
""" | ||
# check if the stirrup is inside the cross-section | ||
stirrup_outside_edge = stirrup.geometry.buffer(distance=stirrup.diameter / 2) | ||
if not self.cross_section.geometry.contains(stirrup_outside_edge): | ||
msg = "Stirrup is not (fully) inside the cross-section." | ||
raise ValueError(msg) | ||
|
||
# add the stirrup to the list | ||
self._stirrups.append(stirrup) | ||
|
||
return stirrup | ||
|
||
def add_reinforcement_configuration( | ||
self, | ||
line: LineString | Callable[..., LineString], | ||
configuration: ReinforcementConfiguration, | ||
*args, | ||
**kwargs, | ||
) -> None: | ||
"""Add a reinforcement configuration to the cross-section. | ||
Parameters | ||
---------- | ||
line : LineString | Callable[..., LineString] | ||
Representing the path of the reinforcement in the cross-section. | ||
Start of the line defines the first rebar of the configuration, end of the line defines the last rebar. | ||
If a callable is given, it should return a LineString. The callable can take additional arguments. | ||
Arguments can be passed to the callable using the *args and **kwargs. | ||
configuration : ReinforcementConfiguration | ||
Configuration of the reinforcement. | ||
args : Any | ||
Additional arguments for the callable line. If line is not a callable, these arguments are ignored. | ||
kwargs : Any | ||
Additional keyword arguments for the callable line. If line is not a callable, these arguments are ignored. | ||
""" | ||
# check if the line is a callable and wrap it with the given arguments | ||
if callable(line): | ||
line = partial(line, *args, **kwargs) # type: ignore[misc] | ||
|
||
# add the reinforcement configuration to the list | ||
self._reinforcement_configurations.append((line, configuration)) |
1 change: 1 addition & 0 deletions
1
blueprints/structural_sections/concrete/reinforced_concrete_sections/plotters/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Default plotters for Blueprint's reinforced concrete sections.""" |
Oops, something went wrong.