from __future__ import annotations import sys from pathlib import Path HERE = Path(__file__).resolve().parent SCALE = 14.0 # points per inch LEFT = 80.0 RIGHT = 110.0 TOP = 34.0 BOTTOM = 44.0 MUTED = 'rgb("#626b73")' def _f(value: float) -> str: return f"{value:.4g}" def _centers(size: float, number: int, bw: float, cover: float, stirrup_db: float) -> list[float]: half = size / 2.0 left = cover + stirrup_db + half right = bw - left if number == 1: return [bw / 2.0] return [left + index * (right - left) / (number - 1) for index in range(number)] def render(values: dict) -> str: s = SCALE bw = float(values["bw"]) h = float(values["h"]) d = float(values["d"]) cover = float(values["cover"]) stirrup_size = int(values["stirrup_size"]) stirrup_db = float(values["stirrup_db"]) stirrup_spacing = float(values["stirrup_spacing"]) bar_size = int(values["bar_size"]) bar_db = float(values["bar_db"]) n_bars = int(values["n_bars"]) top_bar_size = int(values["top_bar_size"]) top_bar_db = float(values["top_bar_db"]) n_top_bars = int(values["n_top_bars"]) As = float(values["As"]) w_sec = bw * s h_sec = h * s width = LEFT + w_sec + RIGHT height = TOP + h_sec + BOTTOM ox = LEFT oy = TOP def sx(x_in: float) -> float: return ox + x_in * s def sy(y_in: float) -> float: return oy + (h - y_in) * s inset = cover + stirrup_db / 2.0 y_bottom = h - d bottom = _centers(bar_db, n_bars, bw, cover, stirrup_db) top_y = h - (cover + stirrup_db + top_bar_db / 2.0) top = _centers(top_bar_db, n_top_bars, bw, cover, stirrup_db) lines: list[str] = [] add = lines.append add("// Generated by beam_section.py from input.yaml - do not edit by hand.") add("#let beam-section() = {") add(f" box(width: {_f(width)}pt, height: {_f(height)}pt)[") add( f" #place(dx: {_f(ox)}pt, dy: {_f(oy)}pt, " f'rect(width: {_f(w_sec)}pt, height: {_f(h_sec)}pt, fill: rgb("#f2f2f2"), stroke: 1pt + black))' ) add( f" #place(dx: {_f(sx(inset))}pt, dy: {_f(sy(h - inset))}pt, " f"rect(width: {_f((bw - 2.0 * inset) * s)}pt, height: {_f((h - 2.0 * inset) * s)}pt, " f"radius: 1.5pt, stroke: {_f(stirrup_db * s)}pt + black))" ) for x_in in bottom: r = bar_db / 2.0 * s add( f" #place(dx: {_f(sx(x_in) - r)}pt, dy: {_f(sy(y_bottom) - r)}pt, " f"circle(radius: {_f(r)}pt, fill: black))" ) for x_in in top: r = top_bar_db / 2.0 * s add( f" #place(dx: {_f(sx(x_in) - r)}pt, dy: {_f(sy(top_y) - r)}pt, " f"circle(radius: {_f(r)}pt, fill: black))" ) dim_y = oy + h_sec + 16.0 add( f" #place(line(start: ({_f(ox)}pt, {_f(dim_y)}pt), end: ({_f(ox + w_sec)}pt, {_f(dim_y)}pt), " f"stroke: 0.6pt + black))" ) for tick in (ox, ox + w_sec): add( f" #place(line(start: ({_f(tick)}pt, {_f(dim_y - 4)}pt), end: ({_f(tick)}pt, {_f(dim_y + 4)}pt), " f"stroke: 0.6pt + black))" ) add( f" #place(dx: {_f(ox)}pt, dy: {_f(dim_y + 3)}pt, " f'box(width: {_f(w_sec)}pt, align(center)[$b_w = {_f(bw)} " in"$]))' ) x_h = ox - 12.0 add( f" #place(line(start: ({_f(x_h)}pt, {_f(oy)}pt), end: ({_f(x_h)}pt, {_f(oy + h_sec)}pt), " f"stroke: 0.6pt + black))" ) for tick in (oy, oy + h_sec): add( f" #place(line(start: ({_f(x_h - 4)}pt, {_f(tick)}pt), end: ({_f(x_h + 4)}pt, {_f(tick)}pt), " f"stroke: 0.6pt + black))" ) add( f" #place(dx: 2pt, dy: {_f(oy + h_sec / 2.0 - 5)}pt, " f'text(size: 8pt)[$h = {_f(h)} " in"$])' ) x_d = ox - 30.0 d_end = oy + d * s add( f" #place(line(start: ({_f(x_d)}pt, {_f(oy)}pt), end: ({_f(x_d)}pt, {_f(d_end)}pt), " f"stroke: 0.6pt + black))" ) for tick in (oy, d_end): add( f" #place(line(start: ({_f(x_d - 4)}pt, {_f(tick)}pt), end: ({_f(x_d + 4)}pt, {_f(tick)}pt), " f"stroke: 0.6pt + black))" ) add( f" #place(dx: 2pt, dy: {_f((oy + d_end) / 2.0 - 5)}pt, " f'text(size: 8pt)[$d = {_f(d)} " in"$])' ) label_x = ox + w_sec + 16.0 lead_x = ox + w_sec + 12.0 def leader(fx: float, fy: float, lx: float, ly: float) -> None: add( f" #place(line(start: ({_f(fx)}pt, {_f(fy)}pt), end: ({_f(lx)}pt, {_f(ly)}pt), " f"stroke: 0.5pt + {MUTED}))" ) add(f" #place(dx: {_f(fx - 1.5)}pt, dy: {_f(fy - 1.5)}pt, circle(radius: 1.5pt, fill: black))") leader(sx(top[-1]), sy(top_y), lead_x, oy + 18.0) add( f" #place(dx: {_f(label_x)}pt, dy: {_f(oy + 12)}pt, " f"text(size: 7.5pt)[{n_top_bars} No. {top_bar_size} hanger \\ bars])" ) leader(sx(bw - inset), sy(h / 2.0), lead_x, oy + h_sec / 2.0 - 4.0) add( f" #place(dx: {_f(label_x)}pt, dy: {_f(oy + h_sec / 2.0 - 10)}pt, " f"text(size: 7.5pt)[No. {stirrup_size} stirrup \\ at {_f(stirrup_spacing)} in o.c.])" ) leader(sx(bottom[-1]), sy(y_bottom), lead_x, oy + h_sec - 18.0) add( f" #place(dx: {_f(label_x)}pt, dy: {_f(oy + h_sec - 24)}pt, " f'text(size: 7.5pt)[{n_bars} No. {bar_size} bottom \\ $A_s = {_f(As)} " in"^2$])' ) leader(sx(inset), sy(inset), 40.0, oy + h_sec + 26.0) add( f" #place(dx: 2pt, dy: {_f(oy + h_sec + 30)}pt, " f'text(size: 7.5pt)[cover = {_f(cover)} in])' ) add(" ]") add("}") return "\n".join(lines) + "\n" def write_fragment(values: dict, path: Path) -> None: path.write_text(render(values), encoding="utf-8") def main(argv: list[str] | None = None) -> int: argv = sys.argv[1:] if argv is None else argv input_path = Path(argv[0]) if argv else HERE / "input.yaml" output_path = Path(argv[1]) if len(argv) > 1 else HERE / "assets" / "beam-section.typ" import yaml import calc with input_path.open(encoding="utf-8") as handle: result = calc.compute(yaml.safe_load(handle)) write_fragment(result["values"], output_path) print(output_path) return 0 if __name__ == "__main__": raise SystemExit(main())