Add generated beam cross-section figure to concrete-beam
Add reinforcement inputs (cover, stirrup size/spacing, longitudinal and top bar size/count) and emit the derived bar geometry from calc.py. beam_section.py renders a native Typst drawing of the section to assets/beam-section.typ, which concrete-beam.typ imports as a figure. Effective depth d remains authoritative for the tension-steel position.
This commit is contained in:
parent
8f91baee41
commit
25f66bb4da
8 changed files with 973 additions and 568 deletions
|
|
@ -36,6 +36,23 @@ def quantity(value, unit: str, name: str) -> float:
|
|||
return magnitude
|
||||
|
||||
|
||||
def count(value, name: str, minimum: int = 1) -> int:
|
||||
try:
|
||||
number = int(value)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(f"{name}: expected an integer, got {value!r}") from exc
|
||||
if number < minimum:
|
||||
raise ValueError(f"{name} must be >= {minimum}")
|
||||
return number
|
||||
|
||||
|
||||
def bar_number(value, name: str) -> int:
|
||||
number = count(value, name, minimum=3)
|
||||
if number > 18:
|
||||
raise ValueError(f"{name} must be between 3 and 18")
|
||||
return number
|
||||
|
||||
|
||||
def compute(inp: dict) -> dict:
|
||||
span = quantity(inp["span"], "ft", "span")
|
||||
tributary = quantity(inp["tributary"], "ft", "tributary")
|
||||
|
|
@ -48,6 +65,13 @@ def compute(inp: dict) -> dict:
|
|||
fy = quantity(inp["fy"], "ksi", "fy")
|
||||
As = quantity(inp["As"], "in**2", "As")
|
||||
concrete_density = quantity(inp["concrete_density"], "pcf", "concrete_density")
|
||||
cover = quantity(inp["cover"], "in", "cover")
|
||||
stirrup_size = bar_number(inp["stirrup_size"], "stirrup_size")
|
||||
stirrup_spacing = quantity(inp["stirrup_spacing"], "in", "stirrup_spacing")
|
||||
bar_size = bar_number(inp["bar_size"], "bar_size")
|
||||
n_bars = count(inp["n_bars"], "n_bars")
|
||||
top_bar_size = bar_number(inp["top_bar_size"], "top_bar_size")
|
||||
n_top_bars = count(inp["n_top_bars"], "n_top_bars")
|
||||
|
||||
self_weight = (bw * h / 144.0) * concrete_density / 1000.0
|
||||
wD = D * tributary / 1000.0 + self_weight
|
||||
|
|
@ -73,6 +97,19 @@ def compute(inp: dict) -> dict:
|
|||
Vc = 2.0 * math.sqrt(fc * 1000.0) * bw * d / 1000.0
|
||||
phiVc = 0.75 * Vc
|
||||
|
||||
stirrup_db = stirrup_size / 8.0
|
||||
bar_db = bar_size / 8.0
|
||||
top_bar_db = top_bar_size / 8.0
|
||||
As_bars = n_bars * math.pi * bar_db**2 / 4.0
|
||||
|
||||
bottom_centroid = h - d
|
||||
min_bottom_centroid = cover + stirrup_db + bar_db / 2.0
|
||||
if bottom_centroid < min_bottom_centroid:
|
||||
raise ValueError("effective depth d is too large for the specified cover and bars")
|
||||
inner_width = bw - 2.0 * (cover + stirrup_db)
|
||||
if n_bars * bar_db > inner_width:
|
||||
raise ValueError("bottom bars do not fit within the stirrup width")
|
||||
|
||||
def q(value: float) -> float:
|
||||
return round(value, 6)
|
||||
|
||||
|
|
@ -106,6 +143,17 @@ def compute(inp: dict) -> dict:
|
|||
"As_min": q(As_min),
|
||||
"Vc": q(Vc),
|
||||
"phiVc": q(phiVc),
|
||||
"cover": q(cover),
|
||||
"stirrup_size": stirrup_size,
|
||||
"stirrup_spacing": q(stirrup_spacing),
|
||||
"stirrup_db": q(stirrup_db),
|
||||
"bar_size": bar_size,
|
||||
"n_bars": n_bars,
|
||||
"bar_db": q(bar_db),
|
||||
"top_bar_size": top_bar_size,
|
||||
"n_top_bars": n_top_bars,
|
||||
"top_bar_db": q(top_bar_db),
|
||||
"As_bars": q(As_bars),
|
||||
},
|
||||
"checks": {
|
||||
"flexure": {
|
||||
|
|
@ -131,6 +179,17 @@ def write_results(result: dict, path: Path) -> None:
|
|||
path.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _load_section_module():
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location("beam_section", HERE / "beam_section.py")
|
||||
if spec is None or spec.loader is None:
|
||||
raise SystemExit("Could not load beam_section.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main() -> int:
|
||||
input_path = Path(sys.argv[1]) if len(sys.argv) > 1 else HERE / "input.yaml"
|
||||
output_path = Path(sys.argv[2]) if len(sys.argv) > 2 else input_path.with_name("results.json")
|
||||
|
|
@ -139,6 +198,9 @@ def main() -> int:
|
|||
result = compute(inp)
|
||||
write_results(result, output_path)
|
||||
print(output_path)
|
||||
section_path = HERE / "assets" / "beam-section.typ"
|
||||
_load_section_module().write_fragment(result["values"], section_path)
|
||||
print(section_path)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue