Add structural calculation worksheets
Collection of engineering calculation projects (Python + Typst), each with input, calc script, tests, results, and generated PDF where available.
14
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Typst
|
||||||
|
*.aux
|
||||||
|
|
||||||
|
# Editors / OS
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
BIN
concentric-footing/CONCENTRIC-FOOTING.pdf
Executable file
211
concentric-footing/PROJECT_STATE.md
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
# Project State: Concentric Footing Analysis
|
||||||
|
|
||||||
|
Last updated: 2026-08-21
|
||||||
|
Project root: `calcs/concentric-footing/`
|
||||||
|
Parent worksheets root: `/home/smill/Sync/worksheets`
|
||||||
|
Reference: `CONCENTRIC-FOOTING.pdf` — Blavatnik concentric footing for steel column (square footing, ACI-based checks). Parent project conventions as documented in `worksheets/codemap.md` and `calcs/wood-joist/PROJECT_STATE.md`.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
New hybrid (Typst + Python) calculation at `calcs/concentric-footing/` that checks a square, concentrically loaded, reinforced concrete spread footing under combined service and ultimate axial load per **ACI 318-19**. Five checks are covered:
|
||||||
|
|
||||||
|
1. Soil bearing (service, ASD) — `q = Ps/Af <= qa`
|
||||||
|
2. One-way (beam) shear — ACI 22.5 — `Vc = 2*lambda*sqrt(f'c)*Bf*d`
|
||||||
|
3. Two-way (punching) shear — ACI 22.6 — `vc = min(4, 2+4/beta, 2+alpha_s*d/bo)*lambda*sqrt(f'c)`
|
||||||
|
4. Flexure (bending) — ACI 22.5/7 — Whitney block `a = As*fy/(0.85*f'c*Bf)`, `Mn = As*fy*(d-a/2)`
|
||||||
|
5. Concrete bearing on footing — ACI 22.8 — `Bn = 0.85*f'c*A1*sqrt(A2/A1) <= 2*0.85*f'c*A1`
|
||||||
|
|
||||||
|
Minimum reinforcement `rho = As/(Bf*d) >= 0.0018` is checked as `minimum_steel`.
|
||||||
|
|
||||||
|
The default `input.yaml` reproduces the Blavatnik reference example subject to documented corrections (bearing plate clarification and ACI-correct punching perimeter). The pytest suite locks the corrected numbers.
|
||||||
|
|
||||||
|
## Architecture decisions
|
||||||
|
|
||||||
|
- **Hybrid pattern** (same as `wood-joist` / `steel-beam`): `input.yaml` (Pint unit-bearing quantities, quoted strings) -> `calc.py` (`compute()` -> writes `results.json`) -> `footing.typ` (presents only, no recomputation) -> compiled PDF with `--root .` from `worksheets/`.
|
||||||
|
- **`results.json` shape** (unchanged contract): `{tool, version, project, prepared_by, values, checks}` with `tool = "concentric_footing"`, `version = "0.1"`.
|
||||||
|
- **Pint units**: all dimensional inputs are quoted strings (e.g. `"3000 psi"`, `"3 ft"`, `"18.4 kip"`). `calc.py` converts with a `quantity(value, unit, name)` helper identical to `wood-joist/calc.py`; dimensionless factors are plain floats.
|
||||||
|
- **`calc.py` CLI** mirrors `wood-joist`/`steel-beam`: `--input`, `--output`, `--stdout`; runnable as `python calcs/concentric-footing/calc.py` with no args.
|
||||||
|
- **ACI 318-19** is the governing standard and edition. All clause references are to ACI 318-19 Chapter 22 / 13.
|
||||||
|
- **`footing.typ` imports** from `../../lib/sheet.typ` and reads `results.json`. It also **derives loads in Typst** (mirroring `wood-joist`/`steel-beam`): `DLr`, `LLr`, `Br`, `Lr`, `Ar`, column weight `Wc = bc*bc*Lc*gamma_c` -> `Ps_derived = (DLr+LLr)*Ar + Wc`, `Pu_derived = 1.2*(DLr*Ar+Wc)+1.6*LLr*Ar`. These are emitted as `<concentric-footing-loads>` metadata and reconciled to the Python-checked `Ps`/`Pu` by the test suite. Capacity numbers are never recomputed in Typst.
|
||||||
|
- **Sheet helpers**: flexure, shear, bearing, and soil bearing use `check` (Demand/Capacity D/C). No `check_service` variant is needed; soil bearing is presented as `q` vs `qa`.
|
||||||
|
- **Compilation**: `typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf` so the shared logo at `assets/logo.png` resolves.
|
||||||
|
|
||||||
|
## Engineering decisions (pinned for the builder)
|
||||||
|
|
||||||
|
All equations, units, and applicability limits are pinned here. The builder must not invent behavior. Tolerances and benchmark values are under "Reference example".
|
||||||
|
|
||||||
|
### Inputs and units
|
||||||
|
|
||||||
|
Pint-parsed quantities (all positive, `ValueError` if <=0 or wrong dimension):
|
||||||
|
- `Ps` -> kip (service axial load, column + roof)
|
||||||
|
- `Pu` -> kip (factored axial load, 1.2D+1.6L)
|
||||||
|
- `qa` -> psf (allowable soil bearing, gross)
|
||||||
|
- `Bf` -> ft or in (square footing side; `Af = Bf^2` -> ft2, also `Bf_in = Bf_ft*12`)
|
||||||
|
- `Df` -> in (total footing thickness)
|
||||||
|
- `cover` -> in (to centroid of steel, so `d = Df - cover`; `d` is effective depth)
|
||||||
|
- `fc` -> psi or ksi (concrete `f'c`)
|
||||||
|
- `fy` -> psi or ksi (reinforcement yield)
|
||||||
|
- `lambda` -> float (lightweight factor, 1.0 normal weight, (0,1])
|
||||||
|
- `column_width` (`c`) -> in (square column side)
|
||||||
|
- `base_plate_width` (`bp`) -> in (square base plate side, `A1 = bp^2`; if omitted defaults to `c`)
|
||||||
|
- `rebar_size` -> int (e.g. 4 means #4 -> db = rebar_size/8 in)
|
||||||
|
- `N` -> int (number of bars per direction)
|
||||||
|
Dimensionless / integers are validated: `N` integer >=1, `rebar_size` integer 3..18, `lambda` in (0,1], `rho_min` hardcoded 0.0018.
|
||||||
|
|
||||||
|
Derived:
|
||||||
|
- `db_in = rebar_size/8`
|
||||||
|
- `As1_in2 = pi*db^2/4`
|
||||||
|
- `As_in2 = N*As1`
|
||||||
|
- `d_in = Df_in - cover_in` (cover to centroid per reference; `ValueError` if `d <=0` or `d > Df`)
|
||||||
|
- `Af_ft2 = Bf_ft^2`, `Af_in2 = Af_ft2*144`
|
||||||
|
- `A1_in2 = bp_in^2`, `A2_in2 = Af_in2`, `A2_ft2 = Af_ft2`
|
||||||
|
- `Bf_in = Bf_ft*12`, `L_cant_ft = (Bf_in - c_in)/2/12`, `L_cant_in = (Bf_in - c_in)/2`
|
||||||
|
|
||||||
|
### Check 1 — Soil bearing (service)
|
||||||
|
|
||||||
|
- `q_psf = Ps_lbf / Af_ft2` where `Ps_lbf = Ps_kip*1000`
|
||||||
|
- `qu_psf = Pu_lbf / Af_ft2` (ultimate pressure for concrete checks)
|
||||||
|
- `ok_soil = q_psf <= qa_psf`
|
||||||
|
- Report `q_psf`, `qu_psf`, `qa_psf`, `Af_ft2`.
|
||||||
|
- Note: footing self weight and soil surcharge are excluded (gross pressure follows reference). Scope note states this limitation.
|
||||||
|
|
||||||
|
### Check 2 — One-way (beam) shear — ACI 22.5.5, phi=0.75
|
||||||
|
|
||||||
|
- Critical section at distance `d` from column face.
|
||||||
|
- `L1_in = (Bf_in - c_in)/2 - d_in` (cantilever beyond section). If `L1_in <=0` then `Vu_kip = 0` (no shear beyond section).
|
||||||
|
- Otherwise `Vu_lbf = qu_psf * (Bf_ft) * (L1_in/12)` because `qu` (psf) * width (ft) * length (ft). So `Vu_kip = Vu_lbf/1000`.
|
||||||
|
- `Vc_lbf = 2*lambda*sqrt(fc_psi)*Bf_in*d_in` (ACI 22.5.5.1, `lambda` factor). `Vc_kip = Vc_lbf/1000`.
|
||||||
|
- `phiVc_kip = 0.75*Vc_kip`
|
||||||
|
- `ok_one_way = Vu_kip <= phiVc_kip`
|
||||||
|
- Also report `Vu/phiVc`.
|
||||||
|
|
||||||
|
### Check 3 — Two-way (punching) shear — ACI 22.6.5, phi=0.75
|
||||||
|
|
||||||
|
- `bo_in = 4*(c_in + d_in)` (interior square column; critical perimeter at d/2). Documented correction: reference shows 68in which is inconsistent with ACI; correct value for c=14,d=9 is 92in.
|
||||||
|
- `beta = 1.0` (square). `alpha_s = 40` (interior per ACI 22.6.5.3).
|
||||||
|
- `vc1 = 4*lambda*sqrt(fc_psi)`
|
||||||
|
- `vc2 = (2 + 4/beta)*lambda*sqrt(fc_psi)`
|
||||||
|
- `vc3 = (2 + alpha_s*d_in/bo_in)*lambda*sqrt(fc_psi)`
|
||||||
|
- `vc_psi = min(vc1, vc2, vc3)`
|
||||||
|
- `Vc_lbf = vc_psi*bo_in*d_in`, `Vc_kip = Vc_lbf/1000`, `phiVn_kip = 0.75*Vc_kip`
|
||||||
|
- `Apunch_in2 = (c_in + d_in)^2`, `Apunch_ft2 = Apunch_in2/144`
|
||||||
|
- `Vu_lbf = qu_psf*(Af_ft2 - Apunch_ft2)`, `Vu_kip = Vu_lbf/1000`
|
||||||
|
- `ok_two_way = Vu_kip <= phiVn_kip`
|
||||||
|
- Also report `vc_psi`, `bo_in`.
|
||||||
|
|
||||||
|
### Check 4 — Flexure — ACI 22.5 / 7, phi=0.90
|
||||||
|
|
||||||
|
- Cantilever length `Lc_in = (Bf_in - c_in)/2`, `Lc_ft = Lc_in/12`
|
||||||
|
- `Mu_kipft = qu_psf * Bf_ft * Lc_ft^2 / 2` (qu as psf -> psf*ft*ft^2 = lbf*ft/1000 = kip*ft). Equivalent presentation: `Mu = qu*Bf*((Bf-c)/2)^2/2`.
|
||||||
|
- `a_in = As_in2*fy_psi / (0.85*fc_psi*Bf_in)`
|
||||||
|
- `c_block_in = a_in / beta1` where `beta1 = max(0.65, min(0.85, 0.85 - 0.05*max(0, (fc_psi-4000)/1000)))` (ACI 22.2.2.4.3). Computed for strain check but not required for phi (phi=0.9 tension-controlled assumed; still compute `et` for report).
|
||||||
|
- `beta1` per above.
|
||||||
|
- `Mn_kipft = As_in2*fy_ksi*(d_in - a_in/2)/12` (fy in ksi). Or `As*fy*(d-a/2)/12`.
|
||||||
|
- `phiMn_kipft = 0.90*Mn_kipft`
|
||||||
|
- `ok_flexure = Mu_kipft <= phiMn_kipft`
|
||||||
|
- `rho = As_in2 / (Bf_in*d_in)`, `rho_min = 0.0018`, `ok_min_steel = rho >= rho_min` (separate check `minimum_steel` with demand `rho_min`, capacity `rho`). For `checks` dict, `minimum_steel` uses `demand = rho_min`, `capacity = rho`.
|
||||||
|
- Also report `Mu/phiMn`, `a_in`, `rho`.
|
||||||
|
|
||||||
|
### Check 5 — Concrete bearing — ACI 22.8, phi=0.65
|
||||||
|
|
||||||
|
- `A1_in2 = bp_in^2`, `A2_in2 = Af_in2`
|
||||||
|
- `sqrt_ratio = sqrt(A2_in2/A1_in2)`, capped at 2.0: `sqrt_ratio_capped = min(sqrt_ratio, 2.0)`
|
||||||
|
- `Bn_lbf = 0.85*fc_psi*A1_in2*sqrt_ratio_capped`, but upper bound `2*0.85*fc_psi*A1_in2` already enforced by cap.
|
||||||
|
- `Bn_kip = Bn_lbf/1000`, `phiBn_kip = 0.65*Bn_kip`
|
||||||
|
- `ok_bearing = Pu_kip <= phiBn_kip`
|
||||||
|
- Report `A1_in2`, `A2_in2`, `sqrt_ratio`, `Bn_kip`, `phiBn_kip`, `Pu/phiBn`.
|
||||||
|
|
||||||
|
### Values dictionary (all rounded to 6 decimals via q() helper, except labels)
|
||||||
|
|
||||||
|
Keys in `results.json` `values` (units encoded in name):
|
||||||
|
`Ps_kip, Pu_kip, qa_psf, q_psf, qu_psf, Af_ft2,
|
||||||
|
Bf_in, Bf_ft, Df_in, cover_in, d_in,
|
||||||
|
fc_psi, fy_psi, fy_ksi, lambda,
|
||||||
|
c_in, bp_in,
|
||||||
|
N, rebar_size, db_in, As1_in2, As_in2,
|
||||||
|
rho, rho_min,
|
||||||
|
L1_in, Vu_one_way_kip, Vc_one_way_kip, phiVc_one_way_kip,
|
||||||
|
bo_in, vc_psi, Vu_two_way_kip, Vc_two_way_kip, phiVn_two_way_kip,
|
||||||
|
Lc_in, Mu_kipft, a_in, beta1, Mn_kipft, phiMn_kipft,
|
||||||
|
A1_in2, A2_in2, sqrt_ratio, Bn_kip, phiBn_kip`
|
||||||
|
|
||||||
|
### Checks dictionary
|
||||||
|
|
||||||
|
Each entry `{demand, capacity, ok}` with appropriate units (kip, kip-ft, psf, or dimensionless for rho):
|
||||||
|
- `soil_bearing`: demand `q_psf`, capacity `qa_psf`
|
||||||
|
- `one_way_shear`: demand `Vu_one_way_kip`, capacity `phiVc_one_way_kip`
|
||||||
|
- `two_way_shear`: demand `Vu_two_way_kip`, capacity `phiVn_two_way_kip`
|
||||||
|
- `flexure`: demand `Mu_kipft`, capacity `phiMn_kipft`
|
||||||
|
- `minimum_steel`: demand `rho_min`, capacity `rho`
|
||||||
|
- `bearing`: demand `Pu_kip`, capacity `phiBn_kip`
|
||||||
|
|
||||||
|
Overall ok requires all six true.
|
||||||
|
|
||||||
|
## Reference example (ground truth to lock, corrected)
|
||||||
|
|
||||||
|
Project "Blavatnik", prepared_by "Conemco Engineering". Derived loads shown in Typst: `DLr=10 psf`, `LLr=20 psf`, `Br=18.9 ft`, `Lr=27.5 ft`, `Ar=519.75 ft2`, column `14 in x14 in x14 ft`, `gamma_c=145 pcf`, `Ps~18.4 kip`, `Pu~26.2 kip`.
|
||||||
|
|
||||||
|
Footing assumed square `Bf=3 ft (36 in)`, `Af=9 ft2`, `Df=12 in`, `cover=3 in -> d=9 in`, `f'c=3000 psi`, `fy=60 ksi`, `lambda=1`, `N=4`, `rebar_size=4` -> `As=0.785 in2` (reference rounds to 0.8), `c=14 in`, `bp=6 in`, `qa=2500 psf`.
|
||||||
|
|
||||||
|
Corrected benchmark (ACI-correct, Pint conversion, tolerance in test is approx):
|
||||||
|
|
||||||
|
| Quantity | Value (rounded for display) |
|
||||||
|
|---|---|
|
||||||
|
| Ps, Pu | 18.4 kip, 26.2 kip (typst-derived 18.36/26.17) |
|
||||||
|
| q, qu | 2044 psf, 2911 psf (reference 2039/2909 within rounding of Ps/Pu) |
|
||||||
|
| soil D/C | 0.82 (q/qa) |
|
||||||
|
| One-way Vu | 1.46 kip |
|
||||||
|
| One-way Vc | 35.45 kip (2*sqrt(fc)*B*d) -> phiVc 26.59 kip, D/C 0.055 |
|
||||||
|
| Two-way bo | 92 in (corrected from 68) |
|
||||||
|
| vc | 219.1 psi (4*sqrt(fc)) |
|
||||||
|
| Two-way Vc | 181.4 kip -> phiVn 136.0 kip, Vu 15.51 kip, D/C 0.114 |
|
||||||
|
| Mu | 3.68 kip-ft |
|
||||||
|
| a | 0.524 in |
|
||||||
|
| Mn | 34.27 kip-ft -> phiMn 30.84 kip-ft, D/C 0.12 |
|
||||||
|
| rho | 0.00242 (>0.0018) |
|
||||||
|
| Bearing A1/A2 | 36 / 1296 in2, sqrt 6 capped 2 |
|
||||||
|
| Bn | 183.6 kip -> phiBn 119.3 kip, D/C 0.22 |
|
||||||
|
|
||||||
|
The reference PDF shows Vu one-way 1.5 kip, Vc 35.5 kip, phiVc 26.6 kip, Vu two-way 15.5 kip, Vc 134 kip (using 68in), phiVn 100.6 kip, Mu 3.7 kip-ft, Mn 34.3, phiMn 30.9. Differences are documented in `footing.typ` Scope: 68in perimeter corrected to ACI 92in and plate vs column clarification.
|
||||||
|
|
||||||
|
Pinned tolerances for pytest.approx: psf within 1%, kip within 0.02 kip, inches within 0.01, phi capacities within 0.3 kip or rel 1e-3.
|
||||||
|
|
||||||
|
## Conventions (inherited)
|
||||||
|
|
||||||
|
- `lib/sheet.typ` is shared. Do not modify; `footing.typ` uses `check` (not `check_service`) for all checks.
|
||||||
|
- No existing calculation (`shore-post`, `concrete-beam`, `steel-beam`, `wood-joist`) may be modified; shared `README.md` is allowed to add the new calc entry.
|
||||||
|
- Compile from `worksheets/` with `--root .`.
|
||||||
|
- Lock the reference example in pytest with `pytest.approx` before treating tool as stable.
|
||||||
|
|
||||||
|
## Milestones
|
||||||
|
|
||||||
|
- 001 DONE: `calc.py` + `input.yaml` + `results.json` — numerical core complete; corrected benchmark reproduced in results.json.
|
||||||
|
- 002 DONE: `test_concentric_footing.py` locks corrected benchmark (10 tests pass; reviewer PASS).
|
||||||
|
- 003 DONE: `footing.typ` + `generated/footing.pdf` + Typst metadata queries (12 tests pass; reviewer PASS skipped per user instruction).
|
||||||
|
- 004 DONE: `README.md` + `codemap.md` refreshed (depends on 003).
|
||||||
|
- 005 DONE: Reviewer PASS — engineering, deterministic evidence, docs, and simplify all verified (12 tests pass; PDF compiles; results.json idempotent).
|
||||||
|
|
||||||
|
## Final deliverables
|
||||||
|
|
||||||
|
- `calcs/concentric-footing/calc.py` — ACI 318-19 footing checks, CLI --input/--output/--stdout
|
||||||
|
- `calcs/concentric-footing/input.yaml` — Pint quantities, defaults reproduce Blavatnik
|
||||||
|
- `calcs/concentric-footing/test_concentric_footing.py` — locks benchmark + units + error guards + Typst queries
|
||||||
|
- `calcs/concentric-footing/footing.typ` — presents checked values, derives Ps/Pu in Typst, embeds sketch, compiled to `generated/footing.pdf`
|
||||||
|
- `README.md`, `codemap.md` — indexed
|
||||||
|
|
||||||
|
## Known limitations
|
||||||
|
|
||||||
|
- Square footing and square column/plate only; rectangular footings not checked.
|
||||||
|
- Interior column only (alpha_s=40); edge/corner punching not covered.
|
||||||
|
- Concentric axial load only; no moment or eccentricity, no overturning, no sliding.
|
||||||
|
- Gross soil pressure (excludes footing self weight and overburden) per reference; net pressure option not provided.
|
||||||
|
- One-way shear assumes uniform `qu` and prismatic width; beam shear Vc uses 2*sqrt(fc) only (no axial or size effect).
|
||||||
|
- Bearing uses `sqrt(A2/A1) <=2` per ACI 22.8.3.2; confinement reinforcement not checked.
|
||||||
|
- d = Df - cover (cover to centroid); bar diameter not subtracted separately. If cover is to clear, adjust input.
|
||||||
|
- Deflection, crack control, development length, and settlement not checked.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Python: `pyyaml`, `pytest`, `pint` (already in `requirements.txt`)
|
||||||
|
- `typst` CLI for compile and metadata-query tests
|
||||||
|
- No new third-party packages
|
||||||
19
concentric-footing/TASKS.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Tasks: Concentric Footing Analysis
|
||||||
|
|
||||||
|
Ordered, dependency-ordered plan. Each task has a spec in `tasks/`: `001_calc_and_input.md`, `002_numerical_tests.md`, `003_typst_sheet.md`, `004_docs_refresh.md`, `005_review.md`.
|
||||||
|
|
||||||
|
| # | Task | Files | Status | Depends on |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 001 | Numerical core: `calc.py` + `input.yaml` + `results.json` | calcs/concentric-footing/calc.py, calcs/concentric-footing/input.yaml, calcs/concentric-footing/results.json | DONE | — |
|
||||||
|
| 002 | Numerical lock: `test_concentric_footing.py` (compute() only) | calcs/concentric-footing/test_concentric_footing.py | DONE | 001 |
|
||||||
|
| 003 | Presentation: `footing.typ` + compile + Typst metadata tests | calcs/concentric-footing/footing.typ, calcs/concentric-footing/generated/footing.pdf, calcs/concentric-footing/test_concentric_footing.py (append) | DONE | 001, 002 |
|
||||||
|
| 004 | Documentation refresh: `README.md` + `codemap.md` | README.md (at worksheets/calcs/wood-joist/README.md and/or worksheets root), codemap.md | DONE | 003 |
|
||||||
|
| 005 | Reviewer pass on the whole calculation | review notes only | DONE | 004 |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Task 001 is the numerical single source of truth; it pins every `results.json` value name and the exact ACI 318-19 equations. Tasks 002-005 depend on that contract.
|
||||||
|
- The Typst metadata tests (soil/structural reconciliation) live with Task 003 because they need `footing.typ` to exist; Task 002 is purely numerical to keep the dependency graph acyclic.
|
||||||
|
- `generated/` may not exist yet; Task 003 creates it before compiling.
|
||||||
|
- All tasks use hybrid Pint pattern: quoted quantity strings in YAML, `quantity()` conversion in Python, values+checks contract, presentation-only Typst.
|
||||||
|
- Load determination is derived in Typst (`DLr`, `LLr`, `Br`, `Lr`, `Ar`, column weight) and emitted as `<concentric-footing-loads>`; Python reads checked `Ps`/`Pu`. The suite reconciles them.
|
||||||
BIN
concentric-footing/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
concentric-footing/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
222
concentric-footing/calc.py
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit("Install dependencies: python -m pip install -r requirements.txt")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pint import DimensionalityError, UndefinedUnitError, UnitRegistry
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit("Install dependencies: python -m pip install -r requirements.txt")
|
||||||
|
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
ureg = UnitRegistry()
|
||||||
|
ureg.define("kip = 1000 * force_pound")
|
||||||
|
ureg.define("ksi = kip / inch ** 2")
|
||||||
|
ureg.define("psf = force_pound / foot ** 2")
|
||||||
|
ureg.define("pcf = force_pound / foot ** 3")
|
||||||
|
ureg.define("plf = force_pound / foot")
|
||||||
|
if "psi" not in ureg:
|
||||||
|
ureg.define("psi = force_pound / inch ** 2")
|
||||||
|
|
||||||
|
|
||||||
|
def quantity(value, unit: str, name: str) -> float:
|
||||||
|
try:
|
||||||
|
q = ureg.Quantity(value).to(unit)
|
||||||
|
except (DimensionalityError, UndefinedUnitError, TypeError, ValueError) as exc:
|
||||||
|
raise ValueError(f"{name}: expected {unit}, got {value!r}") from exc
|
||||||
|
magnitude = float(q.magnitude)
|
||||||
|
if magnitude <= 0:
|
||||||
|
raise ValueError(f"{name} must be positive")
|
||||||
|
return magnitude
|
||||||
|
|
||||||
|
|
||||||
|
def compute(inp: dict) -> dict:
|
||||||
|
Ps_kip = quantity(inp["Ps"], "kip", "Ps")
|
||||||
|
Pu_kip = quantity(inp["Pu"], "kip", "Pu")
|
||||||
|
qa_psf = quantity(inp["qa"], "psf", "qa")
|
||||||
|
Bf_ft = quantity(inp["Bf"], "ft", "Bf")
|
||||||
|
Df_in = quantity(inp["Df"], "in", "Df")
|
||||||
|
cover_in = quantity(inp["cover"], "in", "cover")
|
||||||
|
fc_psi = quantity(inp["fc"], "psi", "fc")
|
||||||
|
fy_psi = quantity(inp["fy"], "psi", "fy")
|
||||||
|
fy_ksi = fy_psi / 1000.0
|
||||||
|
lambda_f = float(inp.get("lambda", 1))
|
||||||
|
c_in = quantity(inp["column_width"], "in", "column_width")
|
||||||
|
bp_in = quantity(inp.get("base_plate_width", inp["column_width"]), "in", "base_plate_width")
|
||||||
|
N = int(inp["N"])
|
||||||
|
rebar_size = int(inp["rebar_size"])
|
||||||
|
|
||||||
|
if not (0 < lambda_f <= 1):
|
||||||
|
raise ValueError("lambda must be in (0, 1]")
|
||||||
|
if N < 1:
|
||||||
|
raise ValueError("N must be >= 1")
|
||||||
|
if not (3 <= rebar_size <= 18):
|
||||||
|
raise ValueError("rebar_size must be an integer in [3, 18]")
|
||||||
|
|
||||||
|
d_in = Df_in - cover_in
|
||||||
|
if d_in <= 0:
|
||||||
|
raise ValueError("d = Df - cover must be positive")
|
||||||
|
|
||||||
|
Bf_in = Bf_ft * 12.0
|
||||||
|
Af_ft2 = Bf_ft ** 2
|
||||||
|
Af_in2 = Af_ft2 * 144.0
|
||||||
|
|
||||||
|
# Derived rebar properties
|
||||||
|
db_in = rebar_size / 8.0
|
||||||
|
As1_in2 = math.pi * db_in ** 2 / 4.0
|
||||||
|
As_in2 = N * As1_in2
|
||||||
|
rho = As_in2 / (Bf_in * d_in)
|
||||||
|
rho_min = 0.0018
|
||||||
|
|
||||||
|
# Pressures
|
||||||
|
Ps_lbf = Ps_kip * 1000.0
|
||||||
|
Pu_lbf = Pu_kip * 1000.0
|
||||||
|
q_psf = Ps_lbf / Af_ft2
|
||||||
|
qu_psf = Pu_lbf / Af_ft2
|
||||||
|
|
||||||
|
# One-way shear — ACI 22.5.5, phi=0.75
|
||||||
|
L1_in = (Bf_in - c_in) / 2.0 - d_in
|
||||||
|
if L1_in <= 0:
|
||||||
|
Vu_one_kip = 0.0
|
||||||
|
else:
|
||||||
|
Vu_one_lbf = qu_psf * Bf_ft * (L1_in / 12.0)
|
||||||
|
Vu_one_kip = Vu_one_lbf / 1000.0
|
||||||
|
Vc_one_lbf = 2.0 * lambda_f * math.sqrt(fc_psi) * Bf_in * d_in
|
||||||
|
Vc_one_kip = Vc_one_lbf / 1000.0
|
||||||
|
phiVc_one_kip = 0.75 * Vc_one_kip
|
||||||
|
|
||||||
|
# Two-way (punching) shear — ACI 22.6.5, phi=0.75
|
||||||
|
bo_in = 4.0 * (c_in + d_in)
|
||||||
|
beta = 1.0
|
||||||
|
alpha_s = 40.0
|
||||||
|
vc1 = 4.0 * lambda_f * math.sqrt(fc_psi)
|
||||||
|
vc2 = (2.0 + 4.0 / beta) * lambda_f * math.sqrt(fc_psi)
|
||||||
|
vc3 = (2.0 + alpha_s * d_in / bo_in) * lambda_f * math.sqrt(fc_psi)
|
||||||
|
vc_psi = min(vc1, vc2, vc3)
|
||||||
|
Vc_two_lbf = vc_psi * bo_in * d_in
|
||||||
|
Vc_two_kip = Vc_two_lbf / 1000.0
|
||||||
|
phiVn_two_kip = 0.75 * Vc_two_kip
|
||||||
|
Apunch_in2 = (c_in + d_in) ** 2
|
||||||
|
Apunch_ft2 = Apunch_in2 / 144.0
|
||||||
|
Vu_two_lbf = qu_psf * (Af_ft2 - Apunch_ft2)
|
||||||
|
Vu_two_kip = Vu_two_lbf / 1000.0
|
||||||
|
|
||||||
|
# Flexure — ACI 22.5 / 7, phi=0.90
|
||||||
|
Lc_in = (Bf_in - c_in) / 2.0
|
||||||
|
Lc_ft = Lc_in / 12.0
|
||||||
|
Mu_kipft = qu_psf * Bf_ft * Lc_ft ** 2 / 2.0 / 1000.0
|
||||||
|
a_in = As_in2 * fy_psi / (0.85 * fc_psi * Bf_in)
|
||||||
|
beta1 = max(0.65, min(0.85, 0.85 - 0.05 * max(0.0, (fc_psi - 4000.0) / 1000.0)))
|
||||||
|
Mn_kipft = As_in2 * fy_ksi * (d_in - a_in / 2.0) / 12.0
|
||||||
|
phiMn_kipft = 0.90 * Mn_kipft
|
||||||
|
|
||||||
|
# Concrete bearing — ACI 22.8, phi=0.65
|
||||||
|
A1_in2 = bp_in ** 2
|
||||||
|
A2_in2 = Af_in2
|
||||||
|
sqrt_ratio = math.sqrt(A2_in2 / A1_in2)
|
||||||
|
sqrt_ratio_capped = min(sqrt_ratio, 2.0)
|
||||||
|
Bn_lbf = 0.85 * fc_psi * A1_in2 * sqrt_ratio_capped
|
||||||
|
Bn_kip = Bn_lbf / 1000.0
|
||||||
|
phiBn_kip = 0.65 * Bn_kip
|
||||||
|
|
||||||
|
def q(value: float) -> float:
|
||||||
|
return round(value, 6)
|
||||||
|
|
||||||
|
values = {
|
||||||
|
"Ps_kip": q(Ps_kip),
|
||||||
|
"Pu_kip": q(Pu_kip),
|
||||||
|
"qa_psf": q(qa_psf),
|
||||||
|
"q_psf": q(q_psf),
|
||||||
|
"qu_psf": q(qu_psf),
|
||||||
|
"Af_ft2": q(Af_ft2),
|
||||||
|
"Bf_in": q(Bf_in),
|
||||||
|
"Bf_ft": q(Bf_ft),
|
||||||
|
"Df_in": q(Df_in),
|
||||||
|
"cover_in": q(cover_in),
|
||||||
|
"d_in": q(d_in),
|
||||||
|
"fc_psi": q(fc_psi),
|
||||||
|
"fy_psi": q(fy_psi),
|
||||||
|
"fy_ksi": q(fy_ksi),
|
||||||
|
"lambda": q(lambda_f),
|
||||||
|
"c_in": q(c_in),
|
||||||
|
"bp_in": q(bp_in),
|
||||||
|
"N": N,
|
||||||
|
"rebar_size": rebar_size,
|
||||||
|
"db_in": q(db_in),
|
||||||
|
"As1_in2": q(As1_in2),
|
||||||
|
"As_in2": q(As_in2),
|
||||||
|
"rho": q(rho),
|
||||||
|
"rho_min": q(rho_min),
|
||||||
|
"L1_in": q(L1_in),
|
||||||
|
"Vu_one_way_kip": q(Vu_one_kip),
|
||||||
|
"Vc_one_way_kip": q(Vc_one_kip),
|
||||||
|
"phiVc_one_way_kip": q(phiVc_one_kip),
|
||||||
|
"bo_in": q(bo_in),
|
||||||
|
"vc_psi": q(vc_psi),
|
||||||
|
"Vu_two_way_kip": q(Vu_two_kip),
|
||||||
|
"Vc_two_way_kip": q(Vc_two_kip),
|
||||||
|
"phiVn_two_way_kip": q(phiVn_two_kip),
|
||||||
|
"Lc_in": q(Lc_in),
|
||||||
|
"Mu_kipft": q(Mu_kipft),
|
||||||
|
"a_in": q(a_in),
|
||||||
|
"beta1": q(beta1),
|
||||||
|
"Mn_kipft": q(Mn_kipft),
|
||||||
|
"phiMn_kipft": q(phiMn_kipft),
|
||||||
|
"A1_in2": q(A1_in2),
|
||||||
|
"A2_in2": q(A2_in2),
|
||||||
|
"sqrt_ratio": q(sqrt_ratio),
|
||||||
|
"Bn_kip": q(Bn_kip),
|
||||||
|
"phiBn_kip": q(phiBn_kip),
|
||||||
|
}
|
||||||
|
|
||||||
|
checks = {
|
||||||
|
"soil_bearing": {"demand": q(q_psf), "capacity": q(qa_psf), "ok": q_psf <= qa_psf},
|
||||||
|
"one_way_shear": {"demand": q(Vu_one_kip), "capacity": q(phiVc_one_kip), "ok": Vu_one_kip <= phiVc_one_kip},
|
||||||
|
"two_way_shear": {"demand": q(Vu_two_kip), "capacity": q(phiVn_two_kip), "ok": Vu_two_kip <= phiVn_two_kip},
|
||||||
|
"flexure": {"demand": q(Mu_kipft), "capacity": q(phiMn_kipft), "ok": Mu_kipft <= phiMn_kipft},
|
||||||
|
"minimum_steel": {"demand": q(rho_min), "capacity": q(rho), "ok": rho >= rho_min},
|
||||||
|
"bearing": {"demand": q(Pu_kip), "capacity": q(phiBn_kip), "ok": Pu_kip <= phiBn_kip},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"tool": "concentric_footing",
|
||||||
|
"version": "0.1",
|
||||||
|
"project": inp.get("project", ""),
|
||||||
|
"prepared_by": inp.get("prepared_by", ""),
|
||||||
|
"values": values,
|
||||||
|
"checks": checks,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Calculate the concentric footing design from a YAML input file.")
|
||||||
|
parser.add_argument("--input", "-i", type=Path, default=HERE / "input.yaml", help="YAML input path")
|
||||||
|
parser.add_argument("--output", "-o", type=Path, help="JSON output path; defaults beside the input")
|
||||||
|
parser.add_argument("--stdout", action="store_true", help="Write the complete JSON result to stdout instead of a file")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
input_path = args.input
|
||||||
|
output_path = args.output or input_path.with_name("results.json")
|
||||||
|
with input_path.open(encoding="utf-8") as handle:
|
||||||
|
result = compute(yaml.safe_load(handle))
|
||||||
|
serialized = json.dumps(result, indent=2) + "\n"
|
||||||
|
if args.stdout:
|
||||||
|
sys.stdout.write(serialized)
|
||||||
|
else:
|
||||||
|
output_path.write_text(serialized, encoding="utf-8")
|
||||||
|
print(output_path)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
41
concentric-footing/codemap.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Codemap: concentric-footing
|
||||||
|
|
||||||
|
Hybrid (Typst + Python) structural calculation that checks a square, concentrically loaded reinforced-concrete spread footing per ACI 318-19: soil bearing, one-way shear, two-way (punching) shear, flexure, minimum steel, and concrete bearing. `calc.py` is the single source of truth; `footing.typ` presents only.
|
||||||
|
|
||||||
|
Generated: 2026-08-21
|
||||||
|
Files indexed: 13 (all deliverables present)
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
calcs/concentric-footing/
|
||||||
|
├── calc.py [logic] — ACI 318-19 footing checks; Pint YAML → compute() → results.json; CLI --input/--output/--stdout.
|
||||||
|
├── input.yaml [config] — Pint-quoted quantities; Blavatnik defaults (Ps, Pu, qa, Bf, Df, cover, fc, fy, lambda, column_width, base_plate_width, N, rebar_size).
|
||||||
|
├── results.json [state] — last calc output: {tool, version, project, prepared_by, values, checks}.
|
||||||
|
├── test_concentric_footing.py [test] — pytest lock of corrected ACI 318-19 benchmark (12 tests); imports calc.py:compute via importlib.
|
||||||
|
├── footing.typ [logic] — Typst presentation sheet; imports lib/sheet.typ + results.json, derives loads, emits <concentric-footing-loads>/<concentric-footing-results>.
|
||||||
|
├── generated/footing.pdf [doc] — compiled artefact (~204 KB).
|
||||||
|
├── CONCENTRIC-FOOTING.pdf [doc] — reference: Blavatnik square footing 36in×36in×12in, 3000 psi, 4-#4 (contains known 68in vs 92in perimeter typo).
|
||||||
|
├── PROJECT_STATE.md [doc] — architecture, pinned equations, units, tolerances, corrected benchmark, known limitations.
|
||||||
|
├── TASKS.md [doc] — 5-task plan (001 numerical core, 002 pytest lock, 003 Typst sheet, 004 docs, 005 review).
|
||||||
|
└── tasks/
|
||||||
|
├── 001_calc_and_input.md [doc] — numerical core spec (calc.py + input.yaml + results.json).
|
||||||
|
├── 002_numerical_tests.md [doc] — pytest lock spec (test_concentric_footing.py, compute() only).
|
||||||
|
├── 003_typst_sheet.md [doc] — Typst presentation spec (footing.typ + compile + metadata query tests).
|
||||||
|
├── 004_docs_refresh.md [doc] — README + codemap refresh spec.
|
||||||
|
└── 005_review.md [doc] — reviewer pass spec (engineering + deterministic evidence + simplify).
|
||||||
|
```
|
||||||
|
|
||||||
|
(footing.typ and generated/footing.pdf are now present on disk — see layout above.)
|
||||||
|
|
||||||
|
## Hot Spots
|
||||||
|
|
||||||
|
- `calcs/concentric-footing/calc.py` — single source of truth for all six ACI 318-19 checks; any equation change must be reconciled with `test_concentric_footing.py` and the benchmark in `PROJECT_STATE.md`.
|
||||||
|
- `calcs/concentric-footing/PROJECT_STATE.md` — pins every equation, unit, tolerance, and the corrected benchmark; the builder must not invent behavior beyond it.
|
||||||
|
- `lib/sheet.typ` (in worksheets root, external) — shared `calc-line` / `check` helpers used by `footing.typ`; changing them breaks every sheet's PDF.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Hybrid Pint pattern: `input.yaml` (quoted quantity strings) → `calc.py:compute()` → `results.json` {tool, version, project, prepared_by, values, checks} → `footing.typ` (presents only, no recomputation) → PDF compiled with `--root .` from `worksheets/`.
|
||||||
|
- Load determination (DL/LL, tributary area, column self weight) is derived in Typst and emitted as `<concentric-footing-loads>`; Python reads checked `Ps`/`Pu`. The test suite reconciles them.
|
||||||
|
- Pin one hand-calculated example in `pytest.approx` before treating the tool as stable; correct reference typos in the Scope note rather than reproducing them.
|
||||||
1964
concentric-footing/concentric-footing.pdf
Normal file
110
concentric-footing/concentric-footing.typ
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#import "assets/sheet.typ": calcline, calcsheet, check
|
||||||
|
|
||||||
|
#let data = json("results.json")
|
||||||
|
#let n = data.values
|
||||||
|
#let checks = data.checks
|
||||||
|
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||||
|
|
||||||
|
#show: calcsheet.with(
|
||||||
|
title: "Concentric Footing Analysis",
|
||||||
|
project: data.project,
|
||||||
|
prepared-by: data.prepared_by,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Concentric Footing Analysis
|
||||||
|
|
||||||
|
Square spread footing under concentric axial load, ACI 318-19. Numbers come from
|
||||||
|
`calc.py`; this sheet only presents them. Typst derives gravity loads below;
|
||||||
|
checked Python demands are reconciled by the test suite.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
align(center)[
|
||||||
|
#box(width: 160pt, height: 130pt)[
|
||||||
|
#place(rect(width: 120pt, height: 120pt, stroke: 1pt))
|
||||||
|
#place(dx: 45pt, dy: 45pt, rect(width: 30pt, height: 30pt, fill: rgb("#cccccc"), stroke: 0.8pt))
|
||||||
|
#place(dx: 55pt, dy: 2pt, text(size: 8pt)[$B_f$])
|
||||||
|
#place(dx: 124pt, dy: 55pt, text(size: 8pt)[$B_f$])
|
||||||
|
#place(dx: 56pt, dy: 56pt, text(size: 7pt)[$c$])
|
||||||
|
]
|
||||||
|
],
|
||||||
|
caption: [Footing plan and section: #n.Bf_ft ft × #n.Bf_ft ft × #n.Df_in in, d=#n.d_in in.],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Loads Determination
|
||||||
|
|
||||||
|
#let DLr = 10 // psf
|
||||||
|
#let LLr = 20 // psf
|
||||||
|
#let Br = 18.9 // ft
|
||||||
|
#let Lr = 27.5 // ft
|
||||||
|
#let Ar = Br * Lr // ft2
|
||||||
|
#let bc = 14 // in
|
||||||
|
#let Lc = 14 // ft
|
||||||
|
#let gamma_c = 145 // pcf
|
||||||
|
#let Wc_kip = bc * bc / 144 * Lc * gamma_c / 1000 // kip column weight
|
||||||
|
#let Ps_typst = (DLr + LLr) * Ar / 1000 + Wc_kip // kip
|
||||||
|
#let Pu_typst = 1.2*(DLr*Ar/1000 + Wc_kip) + 1.6*LLr*Ar/1000
|
||||||
|
|
||||||
|
#metadata((Ps_kip: Ps_typst, Pu_kip: Pu_typst, Ar_ft2: Ar, Wc_kip: Wc_kip)) <concentric-footing-loads>
|
||||||
|
|
||||||
|
#calcline([$A_r = B_r L_r = #round(Ar, digits: 2) " ft"^2$], [Tributary area])
|
||||||
|
#calcline([$W_c = b_c b_c L_c gamma_c = #round(Wc_kip, digits: 2) " kip"$], [Column self weight])
|
||||||
|
#calcline([$P_s = (D_L_r + L_L_r) A_r + W_c = #round(Ps_typst, digits: 2) " kip"$], [Typst-derived service load])
|
||||||
|
#calcline([$P_(s,"checked") = #round(n.Ps_kip, digits: 2) " kip"$], [Python-checked service load])
|
||||||
|
#calcline([$P_u = 1.2(D_L_r A_r + W_c) + 1.6 L_L_r A_r = #round(Pu_typst, digits: 2) " kip"$], [Typst-derived factored load])
|
||||||
|
#calcline([$P_(u,"checked") = #round(n.Pu_kip, digits: 2) " kip"$], [Factored axial load])
|
||||||
|
#calcline([$q_u = P_u / A_f = #round(n.qu_psf, digits: 1) " psf"$], [Factored gross pressure])
|
||||||
|
|
||||||
|
== Geometry and Materials
|
||||||
|
|
||||||
|
#calcline([$B_f = #n.Bf_ft " ft"$, $A_f = #n.Af_ft2 " ft"^2$], [Footing plan dimensions])
|
||||||
|
#calcline([$D_f = #n.Df_in " in"$, $"cover" = #n.cover_in " in"$, $d = D_f - "cover" = #n.d_in " in"$], [Effective depth])
|
||||||
|
#calcline([$c = #n.c_in " in"$, $b_p = #n.bp_in " in"$], [Column and base-plate widths])
|
||||||
|
#calcline([$f'_c = #n.fc_psi " psi"$, $f_y = #n.fy_ksi " ksi"$, $lambda = #n.lambda$], [Concrete and steel])
|
||||||
|
#calcline([$N = #n.N$, #("#" + str(n.rebar_size) + " bars"), $A_(s,1) = #round(n.As1_in2, digits: 4) " in"^2$, $A_s = #round(n.As_in2, digits: 4) " in"^2$], [Reinforcement per direction])
|
||||||
|
#calcline([$rho = A_s/(B_f d) = #round(n.rho, digits: 4)$, $rho_min = #n.rho_min$], [Reinforcement ratio])
|
||||||
|
|
||||||
|
== Soil Bearing
|
||||||
|
|
||||||
|
#calcline([$q = P_s/A_f = #round(n.q_psf, digits: 1) " psf"$], [Acting service pressure])
|
||||||
|
#calcline([$q_a = #n.qa_psf " psf"$], [Allowable gross pressure])
|
||||||
|
|
||||||
|
#check("Soil bearing", checks.soil_bearing.demand, checks.soil_bearing.capacity, unit: "psf", ok: checks.soil_bearing.ok, demand-label: [$q$], capacity-label: [$q_a$])
|
||||||
|
|
||||||
|
== One-Way Shear
|
||||||
|
|
||||||
|
#calcline([$L_1 = (B_f - c)/2 - d = #round(n.L1_in, digits: 2) " in"$], [Cantilever beyond d])
|
||||||
|
#calcline([$V_u = q_u B_f L_1 = #round(n.Vu_one_way_kip, digits: 2) " kip"$], [Demand at d])
|
||||||
|
#calcline([$V_c = 2 lambda sqrt(f'_c) B_f d = #round(n.Vc_one_way_kip, digits: 1) " kip"$], [ACI 22.5])
|
||||||
|
#calcline([$phi V_c = #round(n.phiVc_one_way_kip, digits: 1) " kip"$], [phi=0.75])
|
||||||
|
|
||||||
|
#check("One-way shear", checks.one_way_shear.demand, checks.one_way_shear.capacity, unit: "kip", ok: checks.one_way_shear.ok, demand-label: [$V_u$], capacity-label: [$phi V_c$])
|
||||||
|
|
||||||
|
== Two-Way Shear (Punching)
|
||||||
|
|
||||||
|
#calcline([$b_o = 4(c+d) = #n.bo_in " in"$], [Critical perimeter at d/2])
|
||||||
|
#calcline([$v_c = min(4, 2+4/beta, 2+alpha_s d/b_o) lambda sqrt(f'_c) = #round(n.vc_psi, digits: 1) " psi"$], [ACI 22.6])
|
||||||
|
#calcline([$V_c = v_c b_o d = #round(n.Vc_two_way_kip, digits: 1) " kip"$], [Concrete shear strength])
|
||||||
|
#calcline([$V_u = q_u (A_f - (c+d)^2) = #round(n.Vu_two_way_kip, digits: 1) " kip"$], [Punch demand])
|
||||||
|
|
||||||
|
#check("Two-way shear", checks.two_way_shear.demand, checks.two_way_shear.capacity, unit: "kip", ok: checks.two_way_shear.ok, demand-label: [$V_u$], capacity-label: [$phi V_c$])
|
||||||
|
|
||||||
|
== Flexure
|
||||||
|
|
||||||
|
#calcline([$L_c = (B_f - c)/2 = #round(n.Lc_in, digits: 1) " in"$], [Cantilever])
|
||||||
|
#calcline([$M_u = q_u B_f L_c^2/2 = #round(n.Mu_kipft, digits: 2) " kip·ft"$], [Demand])
|
||||||
|
#calcline([$a = A_s f_y/(0.85 f'_c B_f) = #round(n.a_in, digits: 3) " in"$], [Whitney stress block])
|
||||||
|
#calcline([$M_n = A_s f_y (d - a/2) = #round(n.Mn_kipft, digits: 1) " kip·ft"$], [Nominal moment strength])
|
||||||
|
#calcline([$phi M_n = #round(n.phiMn_kipft, digits: 1) " kip·ft"$], [phi=0.90])
|
||||||
|
#calcline([$rho = A_s/(B_f d) = #round(n.rho, digits: 4)$], [vs rho_min 0.0018])
|
||||||
|
|
||||||
|
#check("Flexure", checks.flexure.demand, checks.flexure.capacity, unit: "kip·ft", ok: checks.flexure.ok, demand-label: [$M_u$], capacity-label: [$phi M_n$])
|
||||||
|
#check("Minimum steel", checks.minimum_steel.demand, checks.minimum_steel.capacity, unit: "", ok: checks.minimum_steel.ok, demand-label: [$rho$], capacity-label: [$rho_min$])
|
||||||
|
|
||||||
|
== Concrete Bearing
|
||||||
|
|
||||||
|
#calcline([$A_1 = b_p^2 = #round(n.A1_in2, digits: 1) " in"^2$, $A_2 = B_f^2 = #round(n.A2_in2, digits: 1) " in"^2$], [Plate and footing])
|
||||||
|
#calcline([$sqrt(A_2/A_1) = #round(n.sqrt_ratio, digits: 2)$], [Uncapped ratio, capped at 2.0 for strength])
|
||||||
|
#calcline([$B_n = 0.85 f'_c A_1 sqrt(...) = #round(n.Bn_kip, digits: 1) " kip"$], [ACI 22.8])
|
||||||
|
#calcline([$phi B_n = #round(n.phiBn_kip, digits: 1) " kip"$], [phi=0.65])
|
||||||
|
|
||||||
|
#check("Concrete bearing", checks.bearing.demand, checks.bearing.capacity, unit: "kip", ok: checks.bearing.ok, demand-label: [$P_u$], capacity-label: [$phi B_n$])
|
||||||
19
concentric-footing/input.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
project: "Blavatnik"
|
||||||
|
prepared_by: "Conemco Engineering"
|
||||||
|
|
||||||
|
Ps: "18.4 kip" # service axial (checked demand)
|
||||||
|
Pu: "26.2 kip" # factored axial (checked demand, 1.2D+1.6L)
|
||||||
|
qa: "2500 psf" # allowable soil bearing (gross)
|
||||||
|
Bf: "3 ft" # square footing side
|
||||||
|
Df: "12 in" # total thickness
|
||||||
|
cover: "3 in" # to centroid of steel -> d = Df - cover
|
||||||
|
fc: "3000 psi" # f'c
|
||||||
|
fy: "60 ksi" # fy
|
||||||
|
lambda: 1 # lightweight factor (float, (0,1])
|
||||||
|
column_width: "14 in" # c, square column
|
||||||
|
base_plate_width: "6 in" # bp, square base plate (A1 = bp^2); if omitted in code defaults to c
|
||||||
|
N: 4 # bars per direction
|
||||||
|
rebar_size: 4 # #4 -> db=0.5in
|
||||||
|
|
||||||
|
# Optional documentation keys (not used in compute, but keep for Typst derivation comments):
|
||||||
|
# DLr, LLr etc are NOT in input.yaml; Typst derives Ps/Pu there. This YAML holds checked demands only.
|
||||||
84
concentric-footing/results.json
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"tool": "concentric_footing",
|
||||||
|
"version": "0.1",
|
||||||
|
"project": "Blavatnik",
|
||||||
|
"prepared_by": "Conemco Engineering",
|
||||||
|
"values": {
|
||||||
|
"Ps_kip": 18.4,
|
||||||
|
"Pu_kip": 26.2,
|
||||||
|
"qa_psf": 2500.0,
|
||||||
|
"q_psf": 2044.444444,
|
||||||
|
"qu_psf": 2911.111111,
|
||||||
|
"Af_ft2": 9.0,
|
||||||
|
"Bf_in": 36.0,
|
||||||
|
"Bf_ft": 3.0,
|
||||||
|
"Df_in": 12.0,
|
||||||
|
"cover_in": 3.0,
|
||||||
|
"d_in": 9.0,
|
||||||
|
"fc_psi": 3000.0,
|
||||||
|
"fy_psi": 60000.0,
|
||||||
|
"fy_ksi": 60.0,
|
||||||
|
"lambda": 1.0,
|
||||||
|
"c_in": 14.0,
|
||||||
|
"bp_in": 6.0,
|
||||||
|
"N": 4,
|
||||||
|
"rebar_size": 4,
|
||||||
|
"db_in": 0.5,
|
||||||
|
"As1_in2": 0.19635,
|
||||||
|
"As_in2": 0.785398,
|
||||||
|
"rho": 0.002424,
|
||||||
|
"rho_min": 0.0018,
|
||||||
|
"L1_in": 2.0,
|
||||||
|
"Vu_one_way_kip": 1.455556,
|
||||||
|
"Vc_one_way_kip": 35.492422,
|
||||||
|
"phiVc_one_way_kip": 26.619316,
|
||||||
|
"bo_in": 92.0,
|
||||||
|
"vc_psi": 219.089023,
|
||||||
|
"Vu_two_way_kip": 15.50571,
|
||||||
|
"Vc_two_way_kip": 181.405711,
|
||||||
|
"phiVn_two_way_kip": 136.054283,
|
||||||
|
"Lc_in": 11.0,
|
||||||
|
"Mu_kipft": 3.669213,
|
||||||
|
"a_in": 0.513332,
|
||||||
|
"beta1": 0.85,
|
||||||
|
"Mn_kipft": 34.334992,
|
||||||
|
"phiMn_kipft": 30.901493,
|
||||||
|
"A1_in2": 36.0,
|
||||||
|
"A2_in2": 1296.0,
|
||||||
|
"sqrt_ratio": 6.0,
|
||||||
|
"Bn_kip": 183.6,
|
||||||
|
"phiBn_kip": 119.34
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"soil_bearing": {
|
||||||
|
"demand": 2044.444444,
|
||||||
|
"capacity": 2500.0,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"one_way_shear": {
|
||||||
|
"demand": 1.455556,
|
||||||
|
"capacity": 26.619316,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"two_way_shear": {
|
||||||
|
"demand": 15.50571,
|
||||||
|
"capacity": 136.054283,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"flexure": {
|
||||||
|
"demand": 3.669213,
|
||||||
|
"capacity": 30.901493,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"minimum_steel": {
|
||||||
|
"demand": 0.0018,
|
||||||
|
"capacity": 0.002424,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"bearing": {
|
||||||
|
"demand": 26.2,
|
||||||
|
"capacity": 119.34,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
131
concentric-footing/tasks/001_calc_and_input.md
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
# Task 001 — Numerical core: calc.py + input.yaml + results.json
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Implement the ACI 318-19 concentric footing calculator as the single source of truth for soil bearing, one-way shear, two-way shear, flexure, minimum steel, and concrete bearing.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Architecture is hybrid Pint pattern per PROJECT_STATE.md. This task establishes the contract every later task depends on: input.yaml Pint quantities, calc.py compute() with 6 checks, and results.json {tool,version,project,prepared_by,values,checks}. Parent conventions from wood-joist and steel-beam apply. No Typst work in this task.
|
||||||
|
|
||||||
|
The reference is CONCENTRIC-FOOTING.pdf (Blavatnik). Corrected benchmark is pinned in PROJECT_STATE.md (Bf=3ft, Af=9ft2, d=9in, fc=3000psi, fy=60ksi, Ps~18.4kip Pu~26.2kip, etc.). The 68in punching perimeter in the PDF is inconsistent; implement ACI-correct bo=4*(c+d)=92in.
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `calcs/concentric-footing/calc.py` — create. Single module, no external deps beyond pyyaml/pint.
|
||||||
|
- `calcs/concentric-footing/input.yaml` — create. Pint-quoted strings for all dimensional inputs.
|
||||||
|
- `calcs/concentric-footing/results.json` — create (generated by running calc.py on the default input.yaml). Do not hand-edit.
|
||||||
|
|
||||||
|
If `calcs/concentric-footing/generated/` does not exist, do not create it here (Task 003 does).
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. input.yaml — defaults reproduce corrected Blavatnik example
|
||||||
|
|
||||||
|
Write YAML with these keys (order as listed, comments allowed):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
project: "Blavatnik"
|
||||||
|
prepared_by: "Conemco Engineering"
|
||||||
|
|
||||||
|
Ps: "18.4 kip" # service axial (checked demand)
|
||||||
|
Pu: "26.2 kip" # factored axial (checked demand, 1.2D+1.6L)
|
||||||
|
qa: "2500 psf" # allowable soil bearing (gross)
|
||||||
|
Bf: "3 ft" # square footing side
|
||||||
|
Df: "12 in" # total thickness
|
||||||
|
cover: "3 in" # to centroid of steel -> d = Df - cover
|
||||||
|
fc: "3000 psi" # f'c
|
||||||
|
fy: "60 ksi" # fy
|
||||||
|
lambda: 1 # lightweight factor (float, (0,1])
|
||||||
|
column_width: "14 in" # c, square column
|
||||||
|
base_plate_width: "6 in" # bp, square base plate (A1 = bp^2); if omitted in code defaults to c but YAML provides it
|
||||||
|
N: 4 # bars per direction
|
||||||
|
rebar_size: 4 # #4 -> db=0.5in
|
||||||
|
|
||||||
|
# Optional documentation keys (not used in compute, but keep for Typst derivation comments):
|
||||||
|
# DLr, LLr etc are NOT in input.yaml; Typst derives Ps/Pu there. This YAML holds checked demands only.
|
||||||
|
```
|
||||||
|
|
||||||
|
All quantities must be quoted strings so Pint parses them. `lambda`, `N`, `rebar_size` are unquoted numbers.
|
||||||
|
|
||||||
|
Accept alternative units via Pint (e.g. Bf as "36 in", fc as "3 ksi", Ps as "18400 lbf") — conversion handled in calc.py.
|
||||||
|
|
||||||
|
### 2. calc.py — implement compute(inp) -> dict
|
||||||
|
|
||||||
|
Create file at `calcs/concentric-footing/calc.py` with structure identical to `calcs/wood-joist/calc.py`:
|
||||||
|
|
||||||
|
- Imports: json, math, sys, argparse, pathlib Path, yaml, pint UnitRegistry, DimensionalityError etc.
|
||||||
|
- Define `HERE = Path(__file__).resolve().parent`
|
||||||
|
- `ureg = UnitRegistry()` and define `kip = 1000*force_pound`, `ksi = kip/inch**2`, `psf = force_pound/foot**2`, `pcf = force_pound/foot**3`, `plf`, `psi` if missing.
|
||||||
|
- Helper `quantity(value, unit, name) -> float`: `ureg.Quantity(value).to(unit).magnitude` with ValueError on bad dimension or <=0. Message must include field name.
|
||||||
|
- Helper `factor(value,name)` or inline validation for lambda (0<lambda<=1), integer checks for N and rebar_size.
|
||||||
|
- `def compute(inp: dict) -> dict:` implements pinned equations from PROJECT_STATE.md **verbatim**:
|
||||||
|
|
||||||
|
1. Parse:
|
||||||
|
```
|
||||||
|
Ps_kip = quantity(inp["Ps"],"kip","Ps")
|
||||||
|
Pu_kip = quantity(inp["Pu"],"kip","Pu")
|
||||||
|
qa_psf = quantity(inp["qa"],"psf","qa")
|
||||||
|
Bf_ft = quantity(inp["Bf"],"ft","Bf")
|
||||||
|
Df_in = quantity(inp["Df"],"in","Df")
|
||||||
|
cover_in = quantity(inp["cover"],"in","cover")
|
||||||
|
fc_psi = quantity(inp["fc"],"psi","fc")
|
||||||
|
fy_psi = quantity(inp["fy"],"psi","fy") # accept ksi via Pint -> psi
|
||||||
|
fy_ksi = fy_psi/1000
|
||||||
|
lambda_f = float(inp.get("lambda",1))
|
||||||
|
c_in = quantity(inp["column_width"],"in","column_width")
|
||||||
|
bp_in = quantity(inp.get("base_plate_width", inp["column_width"]),"in","base_plate_width")
|
||||||
|
N = int(inp["N"]); rebar_size = int(inp["rebar_size"])
|
||||||
|
```
|
||||||
|
Validate lambda (0,1], N>=1, rebar_size 3..18, d_in = Df_in - cover_in >0 else ValueError, Bf_in = Bf_ft*12, Af_ft2 = Bf_ft**2, Af_in2 = Af_ft2*144.
|
||||||
|
|
||||||
|
2. Derived rebar: db_in = rebar_size/8.0, As1_in2 = pi*db^2/4, As_in2 = N*As1_in2, rho = As_in2/(Bf_in*d_in), rho_min=0.0018
|
||||||
|
|
||||||
|
3. Pressures: Ps_lbf=Ps_kip*1000, Pu_lbf=Pu_kip*1000, q_psf = Ps_lbf/Af_ft2, qu_psf = Pu_lbf/Af_ft2
|
||||||
|
|
||||||
|
4. One-way: L1_in = (Bf_in - c_in)/2 - d_in; Vu_one = 0 if L1_in<=0 else qu_psf*Bf_ft*(L1_in/12)/1000 (kip); Vc_one_lbf = 2*lambda_f* sqrt(fc_psi) * Bf_in * d_in; phiVc =0.75*Vc/1000
|
||||||
|
|
||||||
|
5. Two-way: bo=4*(c_in+d_in); beta=1; alpha=40; vc1=4*lambda*sqrt(fc), vc2=(2+4/beta)*lambda*sqrt(fc), vc3=(2+alpha*d/bo)*lambda*sqrt(fc); vc=min(...); Vc_two = vc*bo*d/1000; phiVn=0.75*Vc; Apunch = (c+d)^2 /144 ft2; Vu_two = qu*(Af - Apunch)/1000
|
||||||
|
|
||||||
|
6. Flexure: Lc_in=(Bf_in - c_in)/2; Lc_ft = Lc_in/12. Mu_kipft = qu_psf * Bf_ft * Lc_ft**2 / 2 / 1000 (qu psf * Bf ft * Lc_ft^2 / 2 = lbf-ft, /1000 = kip-ft). a_in = As_in2*fy_psi/(0.85*fc_psi*Bf_in); beta1 = max(0.65, min(0.85, 0.85 - 0.05*max(0, (fc_psi-4000)/1000))); Mn_kipft = As_in2*fy_ksi*(d_in - a_in/2)/12; phiMn_kipft = 0.9*Mn_kipft
|
||||||
|
|
||||||
|
7. Bearing: A1=bp^2, A2=Af_in2, sqrt_ratio = sqrt(A2/A1), capped 2.0, Bn=0.85*fc_psi*A1*capped/1000, phiBn=0.65*Bn
|
||||||
|
|
||||||
|
8. Build values dict with keys listed in PROJECT_STATE.md, each q(value,6) rounded. Include N, rebar_size, db, As1, As etc. Use helper q=lambda v: round(float(v),6).
|
||||||
|
|
||||||
|
9. Build checks dict with 6 entries: soil_bearing, one_way_shear, two_way_shear, flexure, minimum_steel, bearing each {demand, capacity, ok}. For minimum_steel demand=rho_min capacity=rho.
|
||||||
|
|
||||||
|
10. Return {tool:"concentric_footing", version:"0.1", project: inp.get("project",""), prepared_by: inp.get("prepared_by",""), values:..., checks:...}
|
||||||
|
|
||||||
|
- Implement `main(argv)` with argparse `--input/-i` default HERE/"input.yaml", `--output/-o` default beside input, `--stdout` flag. Mirrors wood-joist: read yaml, compute, write json indent2, print path or stdout.
|
||||||
|
- `if __name__=="__main__": raise SystemExit(main())`
|
||||||
|
|
||||||
|
### 3. results.json
|
||||||
|
|
||||||
|
After writing calc.py and input.yaml, run `python calcs/concentric-footing/calc.py` (from worksheets root) to generate `calcs/concentric-footing/results.json`. Verify file exists and contains tool concentric_footing.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. `python calcs/concentric-footing/calc.py` exits 0 and writes `results.json` with `tool=="concentric_footing"` and `version=="0.1"`.
|
||||||
|
2. Re-running with `--stdout` produces identical JSON to the file (idempotent).
|
||||||
|
3. Converting alternative units yields same magnitudes: Bf "36 in" vs "3 ft", fc "3 ksi" vs "3000 psi", Ps "18400 lbf" vs "18.4 kip" within 1e-6 rel.
|
||||||
|
4. Wrong dimension raises ValueError with field name (e.g. `Bf: "3 kip"`).
|
||||||
|
5. Negative or zero values raise ValueError.
|
||||||
|
6. `d = Df - cover` validation: if cover >= Df, ValueError.
|
||||||
|
7. Values for default input within approx of corrected benchmark: q ~2044 psf (±30), qu~2911 psf, one-way phiVc~26.6 kip, two-way phiVn~136 kip, Mu~3.68 kip-ft, phiMn~30.8 kip-ft, rho~0.0024, phiBn~119 kip. (Exact locks in Task 002.)
|
||||||
|
8. No existing calcs are modified.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Do not create tests in this task. Manual verification commands (builder runs, reviewer will run pytest after Task 002):
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
python calcs/concentric-footing/calc.py
|
||||||
|
python calcs/concentric-footing/calc.py --stdout | python -m json.tool
|
||||||
|
python -c "import yaml, importlib.util; spec=importlib.util.spec_from_file_location('c','calcs/concentric-footing/calc.py'); m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m); print(m.compute(yaml.safe_load(open('calcs/concentric-footing/input.yaml'))))"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
None.
|
||||||
110
concentric-footing/tasks/002_numerical_tests.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Task 002 — Numerical lock: test_concentric_footing.py (compute() only)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Lock the corrected Blavatnik benchmark in pytest so the calculator cannot drift, and guard Pint units and applicability limits.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Depends on Task 001 contract. This task is purely numerical: it imports `calc.py:compute` and asserts `pytest.approx` on every value name. It does not require Typst. The reference PDF contains known inconsistencies (68in vs 92in punching perimeter, plate vs column area, 66in textual dimension vs 36in computed Af); tests lock the ACI-correct values documented in PROJECT_STATE.md.
|
||||||
|
|
||||||
|
All assertions target values produced by `calc.py:compute()` sub-blocks: Pressures, One-way shear, Two-way shear, Flexure, Concrete bearing.
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `calcs/concentric-footing/test_concentric_footing.py` — create.
|
||||||
|
- `calcs/concentric-footing/calc.py` — read-only, do not edit.
|
||||||
|
- `calcs/concentric-footing/input.yaml` — read-only.
|
||||||
|
- `calcs/concentric-footing/results.json` — read-only.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Create `calcs/concentric-footing/test_concentric_footing.py` following `calcs/wood-joist/test_wood_joist.py` structure:
|
||||||
|
|
||||||
|
- Imports: pathlib Path, importlib.util, json, subprocess, sys, pytest, yaml
|
||||||
|
- Load calc module via `importlib.util.spec_from_file_location("concentric_footing_calc", HERE/"calc.py")`; `compute = calc_module.compute`
|
||||||
|
- Helper `load_input()` reads `HERE/"input.yaml"` with yaml.safe_load
|
||||||
|
- `@pytest.fixture def result(): return compute(load_input())`
|
||||||
|
|
||||||
|
### Tests to implement (each as `def test_*` function)
|
||||||
|
|
||||||
|
1. `test_example_pressures_and_geometry(result)` — asserts:
|
||||||
|
- `Af_ft2 == 9.0` approx
|
||||||
|
- `Bf_ft == 3.0`, `Bf_in == 36.0`
|
||||||
|
- `d_in == 9.0`
|
||||||
|
- `q_psf == pytest.approx(2044.44, rel=0.01)` (18.4k/9) — allow 1% because Ps 18.4 vs derived 18.36
|
||||||
|
- `qu_psf == pytest.approx(2911.11, rel=0.01)` (26.2/9)
|
||||||
|
- `qa_psf == 2500.0`
|
||||||
|
|
||||||
|
2. `test_example_one_way_shear(result)` — asserts:
|
||||||
|
- `L1_in == pytest.approx(2.0, abs=0.01)` ((36-14)/2 -9 =2)
|
||||||
|
- `Vu_one_way_kip == pytest.approx(1.455, abs=0.05)` (qu*Bf*L1)
|
||||||
|
- `Vc_one_way_kip == pytest.approx(35.45, rel=0.01)` (2*sqrt(3000)*36*9/1000)
|
||||||
|
- `phiVc_one_way_kip == pytest.approx(26.59, rel=0.01)`
|
||||||
|
- `checks["one_way_shear"]["ok"] is True`
|
||||||
|
- D/C approx 0.055
|
||||||
|
|
||||||
|
3. `test_example_two_way_shear(result)` — asserts corrected ACI values:
|
||||||
|
- `bo_in == pytest.approx(92.0)` (4*(14+9))
|
||||||
|
- `vc_psi == pytest.approx(219.089, rel=1e-3)` (4*sqrt(3000))
|
||||||
|
- `Vc_two_way_kip == pytest.approx(181.4, rel=0.02)` (vc*bo*d)
|
||||||
|
- `phiVn_two_way_kip == pytest.approx(136.0, rel=0.02)`
|
||||||
|
- `Vu_two_way_kip == pytest.approx(15.51, abs=0.1)` (qu*(9 - (23/12)^2))
|
||||||
|
- `checks["two_way_shear"]["ok"] is True`
|
||||||
|
- Document that reference PDF reports bo 68in and Vc 134kip; this test locks ACI-correct 92in.
|
||||||
|
|
||||||
|
4. `test_example_flexure(result)` — asserts:
|
||||||
|
- `Lc_in == pytest.approx(11.0)` ((36-14)/2)
|
||||||
|
- `Mu_kipft == pytest.approx(3.68, abs=0.1)` (qu*Bf*Lc^2/2)
|
||||||
|
- `a_in == pytest.approx(0.524, abs=0.02)` (As*fy/(0.85*fc*B))
|
||||||
|
- `As_in2 == pytest.approx(0.785, abs=0.02)` (4*#4 -> 4*0.196=0.785; reference rounds to 0.8, accept both with rel 0.03 but assert within 0.785±0.02)
|
||||||
|
- `Mn_kipft == pytest.approx(34.27, abs=0.5)`
|
||||||
|
- `phiMn_kipft == pytest.approx(30.84, abs=0.5)`
|
||||||
|
- `rho == pytest.approx(0.00242, rel=0.02)`
|
||||||
|
- `checks["flexure"]["ok"] is True`
|
||||||
|
- `checks["minimum_steel"]["ok"] is True` (rho >=0.0018)
|
||||||
|
|
||||||
|
5. `test_example_bearing(result)` — asserts:
|
||||||
|
- `A1_in2 == pytest.approx(36.0)` (6*6)
|
||||||
|
- `A2_in2 == pytest.approx(1296.0)` (36*36)
|
||||||
|
- `sqrt_ratio == pytest.approx(6.0, abs=0.01)` capped at 2 for Bn actually but store raw ratio and report capped separately? Store raw 6.0 and separately use capped 2 for Bn; test asserts stored sqrt_ratio is 6.0 and Bn uses capped 2.
|
||||||
|
- `Bn_kip == pytest.approx(183.6, abs=0.5)` (0.85*3000*36*2/1000)
|
||||||
|
- `phiBn_kip == pytest.approx(119.34, abs=0.5)`
|
||||||
|
- `checks["bearing"]["ok"] is True`
|
||||||
|
|
||||||
|
6. `test_all_checks_pass(result)` — asserts all six checks ok is True.
|
||||||
|
|
||||||
|
7. `test_alternate_units_match_default(result)` — copies input, replaces Bf with "36 in", fc with "3 ksi", fy with "60000 psi", Ps with "18400 lbf", Pu with "26200 lbf" etc, computes converted, asserts for keys ("Af_ft2","q_psf","Mu_kipft","Vu_one_way_kip","phiBn_kip") approx equal rel 1e-6.
|
||||||
|
|
||||||
|
8. `test_wrong_dimension_is_rejected()` — sets `Bf: "3 kip"` and expects ValueError match "Bf".
|
||||||
|
|
||||||
|
9. `test_effective_depth_validation()` — sets cover "13 in" with Df "12 in" (d negative) expects ValueError.
|
||||||
|
|
||||||
|
10. `test_zero_footing_size_rejected()` — sets Bf "0 ft" expects ValueError.
|
||||||
|
|
||||||
|
If any additional guard is implemented (lambda >1, N zero) add matching tests, but at minimum the 10 above.
|
||||||
|
|
||||||
|
Each test that reads values should use `v = result["values"]` and `pytest.approx`. The file must be runnable with `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v` shows at least 10 tests, all passed.
|
||||||
|
2. Tests lock the corrected benchmark within stated tolerances; changing any equation in calc.py causes at least one failure.
|
||||||
|
3. Wrong-dimension and validation tests raise ValueError with correct field name.
|
||||||
|
4. Alternate units test demonstrates Pint equivalence.
|
||||||
|
5. No modification to `calc.py`, `input.yaml`, `results.json`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Builder runs:
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `10 passed` (or more if extra guards).
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Task 001 must be DONE.
|
||||||
163
concentric-footing/tasks/003_typst_sheet.md
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
# Task 003 — Presentation: footing.typ + compile + Typst metadata tests
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Present the checked numbers in a printable Typst sheet that also derives the Blavatnik gravity loads inline, emits queryable metadata, and compiles to PDF.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Architecture: `footing.typ` imports `../../lib/sheet.typ` and `json("results.json")`. It must not recompute capacities; it only presents `results.json` values via `calc-line` and `check` boxes. Load determination (roof DL/LL, tributary area, column self weight) is derived in Typst and reconciled to Python's checked `Ps`/`Pu` by the test suite — mirroring `wood-joist` and `steel-beam` where `<wood-joist-loads>` is queried.
|
||||||
|
|
||||||
|
The `check`, `calc-line`, and `calculation-sheet` helpers are defined in `lib/sheet.typ` (worksheets root). Signatures used here: `check(label, demand, capacity, unit:, ok:)`, `calc-line(content, note)`, and `calculation-sheet.with(title:, project:, prepared-by:)`.
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `calcs/concentric-footing/footing.typ` — create.
|
||||||
|
- `calcs/concentric-footing/generated/footing.pdf` — create via `typst compile --root .`.
|
||||||
|
- `calcs/concentric-footing/test_concentric_footing.py` — append two Typst query tests (do not rewrite existing tests); create `generated/` dir if needed.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. footing.typ structure
|
||||||
|
|
||||||
|
Follow `calcs/wood-joist/beam.typ` as template:
|
||||||
|
|
||||||
|
- Header:
|
||||||
|
```
|
||||||
|
#import "../../lib/sheet.typ": calc-line, calculation-sheet, check
|
||||||
|
#let data = json("results.json")
|
||||||
|
#let n = data.values
|
||||||
|
#let checks = data.checks
|
||||||
|
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||||
|
#show: calculation-sheet.with(title: "Concentric Footing Analysis", project: data.project, prepared-by: data.prepared_by)
|
||||||
|
= Concentric Footing Analysis
|
||||||
|
Square spread footing under concentric axial load, ACI 318-19. Numbers come from `calc.py`; this sheet only presents them. Typst derives gravity loads below; checked Python demands are reconciled by the test suite.
|
||||||
|
```
|
||||||
|
|
||||||
|
- Add a simple footing sketch using Typst `box`/`rect`/`line` drawing (no external image needed). Example: centered square footing with column on top, label `Bf` and `Df`. Wrap in `#figure(..., caption: [Footing plan and section: #n.Bf_ft ft × #n.Bf_ft ft × #n.Df_in in, d=#n.d_in in.])`. Keep geometry text as `calc-line` below.
|
||||||
|
|
||||||
|
- Section `== Loads Determination`:
|
||||||
|
Derive Blavatnik loads in Typst (visible arithmetic):
|
||||||
|
```
|
||||||
|
#let DLr = 10 // psf
|
||||||
|
#let LLr = 20 // psf
|
||||||
|
#let Br = 18.9 // ft
|
||||||
|
#let Lr = 27.5 // ft
|
||||||
|
#let Ar = Br * Lr // ft2
|
||||||
|
#let bc = 14 // in
|
||||||
|
#let Lc = 14 // ft
|
||||||
|
#let gamma_c = 145 // pcf
|
||||||
|
#let Wc_kip = bc * bc / 144 * Lc * gamma_c / 1000 // kip column weight
|
||||||
|
#let Ps_typst = (DLr + LLr) * Ar / 1000 + Wc_kip // kip
|
||||||
|
#let Pu_typst = 1.2*(DLr*Ar/1000 + Wc_kip) + 1.6*LLr*Ar/1000
|
||||||
|
#metadata((Ps_kip: Ps_typst, Pu_kip: Pu_typst, Ar_ft2: Ar, Wc_kip: Wc_kip)) <concentric-footing-loads>
|
||||||
|
```
|
||||||
|
Then emit `calc-line` rows:
|
||||||
|
- `Ar = Br·Lr = #round(Ar,2) ft2` (tributary area)
|
||||||
|
- `Wc = bc·bc·Lc·γc = #round(Wc_kip,2) kip` (column self weight)
|
||||||
|
- `Ps = (DLr+LLr)·Ar + Wc = #round(Ps_typst,2) kip (Typst-derived)`
|
||||||
|
- `Ps_checked = #round(n.Ps_kip,2) kip (Python-checked)`
|
||||||
|
- `Pu = 1.2(DLr·Ar+Wc)+1.6·LLr·Ar = #round(Pu_typst,2) kip (Typst-derived)`
|
||||||
|
- `Pu_checked = #round(n.Pu_kip,2) kip`
|
||||||
|
- Also show `qu = Pu/Af = #round(n.qu_psf,1) psf` for context but do not recompute Af.
|
||||||
|
|
||||||
|
- Section `== Geometry and Materials`:
|
||||||
|
`calc-line` for Bf, Df, cover, d, Af, c, bp, fc, fy, lambda, N, rebar_size, As, rho. Example:
|
||||||
|
`calc-line([$B_f = #n.Bf_ft " ft"$, $A_f = #n.Af_ft2 " ft"^2$], [Footing plan area])` etc. Show As1, As, rho, rho_min.
|
||||||
|
|
||||||
|
- Section `== Soil Bearing`:
|
||||||
|
`calc-line([$q = P_s/A_f = #round(n.q_psf,1) " psf"$], [Acting service pressure])`
|
||||||
|
`calc-line([$q_a = #n.qa_psf " psf"$], [Allowable])`
|
||||||
|
Then `#check("Soil bearing", checks.soil_bearing.demand, checks.soil_bearing.capacity, unit: "psf", ok: checks.soil_bearing.ok)`
|
||||||
|
Also show `qu` as info line.
|
||||||
|
|
||||||
|
- Section `== One-Way Shear`:
|
||||||
|
`calc-line([$L_1 = (B_f - c)/2 - d = #round(n.L1_in,2) " in"$], [Cantilever beyond d])`
|
||||||
|
`calc-line([$V_u = q_u B_f L_1 = #round(n.Vu_one_way_kip,2) " kip"$], [Demand at d])`
|
||||||
|
`calc-line([$V_c = 2 lambda sqrt(f'_c) B_f d = #round(n.Vc_one_way_kip,1) " kip"$], [ACI 22.5])`
|
||||||
|
`calc-line([$phi V_c = #round(n.phiVc_one_way_kip,1) " kip"$], [phi=0.75])`
|
||||||
|
`#check("One-way shear", checks.one_way_shear.demand, checks.one_way_shear.capacity, unit: "kip", ok: checks.one_way_shear.ok)`
|
||||||
|
|
||||||
|
- Section `== Two-Way Shear (Punching)`:
|
||||||
|
`calc-line([$b_o = 4(c+d)= #n.bo_in " in"$], [Critical perimeter, d/2])`
|
||||||
|
`calc-line([$v_c = min(4,2+4/beta,2+alpha d/b_o) lambda sqrt(f'_c)= #round(n.vc_psi,1) " psi"$], [ACI 22.6])`
|
||||||
|
`calc-line([$V_c = v_c b_o d = #round(n.Vc_two_way_kip,1) " kip"$], [])`
|
||||||
|
`calc-line([$V_u = q_u(A_f - (c+d)^2)= #round(n.Vu_two_way_kip,1) " kip"$], [Punch demand])`
|
||||||
|
`#check("Two-way shear", checks.two_way_shear.demand, checks.two_way_shear.capacity, unit: "kip", ok: checks.two_way_shear.ok)`
|
||||||
|
|
||||||
|
- Section `== Flexure`:
|
||||||
|
`calc-line([$L_c = (B_f - c)/2 = #round(n.Lc_in,1) " in"$], [Cantilever])`
|
||||||
|
`calc-line([$M_u = q_u B_f L_c^2/2 = #round(n.Mu_kipft,2) " kip·ft"$], [])`
|
||||||
|
`calc-line([$a = A_s f_y/(0.85 f'_c B_f)= #round(n.a_in,3) " in"$], [Whitney block])`
|
||||||
|
`calc-line([$M_n = A_s f_y(d -a/2)= #round(n.Mn_kipft,1) " kip·ft"$], [])`
|
||||||
|
`calc-line([$rho = A_s/(B_f d)= #round(n.rho,4)$], [vs rho_min 0.0018])`
|
||||||
|
`#check("Flexure", checks.flexure.demand, checks.flexure.capacity, unit: "kip·ft", ok: checks.flexure.ok)`
|
||||||
|
`#check("Minimum steel", checks.minimum_steel.demand, checks.minimum_steel.capacity, unit: "", ok: checks.minimum_steel.ok)` — for rho, show ratio but check helper expects demand/capacity; use demand=rho_min capacity=rho; label accordingly.
|
||||||
|
|
||||||
|
- Section `== Concrete Bearing`:
|
||||||
|
`calc-line([$A_1 = b_p^2 = #round(n.A1_in2,1) " in"^2$, $A_2 = B_f^2 = #round(n.A2_in2,1) " in"^2$], [Plate and footing])`
|
||||||
|
`calc-line([$sqrt(A_2/A_1)= #round(n.sqrt_ratio,2)$, capped at 2], [])`
|
||||||
|
`calc-line([$B_n = 0.85 f'_c A_1 sqrt(...)= #round(n.Bn_kip,1) " kip"$], [ACI 22.8])`
|
||||||
|
`calc-line([$phi B_n = #round(n.phiBn_kip,1) " kip"$], [phi=0.65])`
|
||||||
|
`#check("Concrete bearing", checks.bearing.demand, checks.bearing.capacity, unit: "kip", ok: checks.bearing.ok)`
|
||||||
|
|
||||||
|
- Section `== Scope And Limitations`:
|
||||||
|
Text stating square footing only, interior concentric axial only, gross pressure, no moment/overturning/sliding, no settlement, d from cover to centroid, bo correction noted (reference 68in corrected to ACI 92in), plate vs column clarification, no deflection/crack/development, final design by engineer.
|
||||||
|
|
||||||
|
- Footer metadata for reconciliation:
|
||||||
|
```
|
||||||
|
#metadata((soil_util: checks.soil_bearing.demand/checks.soil_bearing.capacity, one_way_util: checks.one_way_shear.demand/checks.one_way_shear.capacity, two_way_util: checks.two_way_shear.demand/checks.two_way_shear.capacity, flexure_util: checks.flexure.demand/checks.flexure.capacity, bearing_util: checks.bearing.demand/checks.bearing.capacity, q_psf: n.q_psf, qu_psf: n.qu_psf)) <concentric-footing-results>
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure all `n.*` keys match Task 001 values dict exactly.
|
||||||
|
|
||||||
|
### 2. Compile
|
||||||
|
|
||||||
|
From `worksheets/` run:
|
||||||
|
```
|
||||||
|
python calcs/concentric-footing/calc.py
|
||||||
|
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||||
|
```
|
||||||
|
Verify PDF exists and >50KB.
|
||||||
|
|
||||||
|
### 3. Append Typst query tests
|
||||||
|
|
||||||
|
Append to `calcs/concentric-footing/test_concentric_footing.py` two new tests (do not delete existing 10):
|
||||||
|
|
||||||
|
- `test_typst_compiles_and_presents_python_numbers(result)` — runs `calc.py` with `subprocess.run([sys.executable, str(HERE/"calc.py")], check=True, cwd=HERE.parents[1])`, then `typst compile ...` check pdf exists, then `typst query` for `<concentric-footing-results>` via `typst eval "query(<concentric-footing-results>)"`? Actually use `typst eval` or `typst query` command as in wood-joist: `subprocess.run(["typst","query", ...])`? Wood-joist uses `typst compile` and `typst eval query`. Use same pattern as wood-joist test:
|
||||||
|
```
|
||||||
|
subprocess.run(["typst","compile","--root",".",...], check=True, cwd=HERE.parents[1])
|
||||||
|
query = subprocess.run(["typst","query","..."]?)
|
||||||
|
```
|
||||||
|
Check wood-joist: it runs `["typst","eval","query(<wood-joist-results>)","--root",...,"--in","calcs/wood-joist/beam.typ","--format","json"]`. Mirror that: `["typst","query", str(HERE/"footing.typ"), "<concentric-footing-results>", "--root", "."]` may vary; adapt to whatever works but assert exactly one entry and that flexure_util approx equals Python f. Specifically query the published metadata and assert `meta["flexure_util"] approx values["Mu"]/values["phiMn"]` etc for soil, one-way, two-way, bearing.
|
||||||
|
|
||||||
|
- `test_typst_load_demands_match_checked_inputs(result)` — queries `<concentric-footing-loads>` and asserts `derived Ps_kip approx values["Ps_kip"]` within 0.1 kip (allow rounding of column weight 18.36 vs 18.4) and same for `Pu_kip`.
|
||||||
|
|
||||||
|
Implementation must be robust: if `typst query` syntax differs, use `typst compile` + `typst eval` as in wood-joist. Ensure tests pass on the CI image where typst is installed.
|
||||||
|
|
||||||
|
If `generated/` does not exist, create it with `Path(...).mkdir(parents=True, exist_ok=True)`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. `footing.typ` imports only `lib/sheet.typ` and `results.json`; no arithmetic beyond load derivation and rounding.
|
||||||
|
2. Every `check` box reflects `checks.*.ok` from JSON; D/C printed as demand/capacity.
|
||||||
|
3. `typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf` succeeds and PDF exists.
|
||||||
|
4. Two appended pytest tests pass, reconciling Typst-derived Ps/Pu to Python-checked Ps/Pu and Typst-presented utils to Python values.
|
||||||
|
5. All prior 10 tests still pass (total 12).
|
||||||
|
6. No existing calc files modified; `generated/` is git-ignored regenerable.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Builder runs:
|
||||||
|
|
||||||
|
```
|
||||||
|
python calcs/concentric-footing/calc.py
|
||||||
|
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||||
|
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 12 passed, PDF exists.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Tasks 001, 002 must be DONE.
|
||||||
73
concentric-footing/tasks/004_docs_refresh.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Task 004 — Documentation refresh: README.md + codemap.md
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Index the new concentric footing calculation in the shared documentation so the next engineer can discover and compile it without reading source.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Depends on footing.typ existing. This task is typst-builder-local (documentation only). Conventions per PROJECT_STATE.md: compile with --root ., hybrid pattern, no modification to existing calcs.
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `README.md` — the shared worksheets readme that lists calcs. At present this file lives at `calcs/wood-joist/README.md` which serves as the de-facto worksheets README (it documents all sheets). Also check for `README.md` at `worksheets/` root if it exists. Update whichever exists; if both exist update both with same concentric-footing entry. If only `calcs/wood-joist/README.md` exists, that is the file to edit.
|
||||||
|
- `codemap.md` — update at `worksheets/codemap.md` (and copy to `calcs/concentric-footing/codemap.md` if the per-calc map is expected). Regenerate the whole map rather than editing in place per codemap rules.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. README.md
|
||||||
|
|
||||||
|
Add a new section `## Concentric footing` after the Wood joist section, following the same style. Include:
|
||||||
|
|
||||||
|
- One-sentence description: "Square concentric spread footing under axial load per ACI 318-19 — soil bearing, one-way/two-way shear, flexure, minimum steel, concrete bearing."
|
||||||
|
- Compile commands:
|
||||||
|
```
|
||||||
|
python calcs/concentric-footing/calc.py
|
||||||
|
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||||
|
python -m pytest calcs/concentric-footing/test_concentric_footing.py
|
||||||
|
```
|
||||||
|
- Stdout variant:
|
||||||
|
```
|
||||||
|
python calcs/concentric-footing/calc.py --input calcs/concentric-footing/input.yaml --stdout
|
||||||
|
```
|
||||||
|
and `--output` note.
|
||||||
|
- Note on load derivation: `input.yaml` supplies checked `Ps`/`Pu` (Pint quantities) and footing geometry/materials; `footing.typ` derives `Ps`/`Pu` inline from `DLr=10psf, LLr=20psf, Br=18.9ft, Lr=27.5ft, column 14in×14ft @145pcf` and reconciles via `<concentric-footing-loads>` query; bearing plate width `bp=6in` governs `A1`.
|
||||||
|
- Update the top index code block to add line:
|
||||||
|
```
|
||||||
|
calcs/concentric-footing/ YAML quantities → Pint → JSON → Typst (ACI 318-19)
|
||||||
|
```
|
||||||
|
- Ensure the 5-step New calculation list remains unchanged.
|
||||||
|
|
||||||
|
If editing `worksheets/README.md` vs `calcs/wood-joist/README.md`, keep both in sync; prefer editing the file that actually exists and then copying the change to the other if both are present.
|
||||||
|
|
||||||
|
### 2. codemap.md
|
||||||
|
|
||||||
|
Regenerate at `worksheets/codemap.md` (and also write copy to `calcs/concentric-footing/codemap.md` if needed for per-calc root check):
|
||||||
|
|
||||||
|
- Re-run classification for all 37+ files including the new `calcs/concentric-footing/calc.py [logic]`, `input.yaml [config]`, `footing.typ [logic]`, `test_concentric_footing.py [test]`, `results.json [state]`, `generated/footing.pdf [doc]`, `CONCENTRIC-FOOTING.pdf [doc]`.
|
||||||
|
- The layout section must now show `calcs/concentric-footing/` expanded with its 5 planned + 3 reference entries.
|
||||||
|
- Hot Spots must now include `calcs/concentric-footing/calc.py` as single source of truth.
|
||||||
|
- Conventions section must mention the new sheet's hybrid Pint pattern and Typst load derivation.
|
||||||
|
- Timestamp updated to now, Files indexed incremented.
|
||||||
|
|
||||||
|
Do not write summaries from imagination; if uncertain read first 20 lines.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. `README.md` contains a new `## Concentric footing` section with compile, stdout, and load-derivation notes; top index code block lists the new calc.
|
||||||
|
2. `codemap.md` at `worksheets/codemap.md` exists, is well-formed markdown with header, layout, hot spots, conventions; it lists `calcs/concentric-footing/` and its files.
|
||||||
|
3. `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v` still passes (12 tests).
|
||||||
|
4. No existing calc logic files modified; only docs/maps changed.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||||
|
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
Both succeed; PDF still compiles.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Task 003 must be DONE.
|
||||||
69
concentric-footing/tasks/005_review.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Task 005 — Reviewer pass on the whole calculation
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Verify engineering correctness, deterministic evidence, and documentation for the concentric footing analysis before marking the project done.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
This task is reviewer-local (reasoning) with simplify skill pass. It must not modify source except via review report; if issues are found the orchestrator will send them back to the builder. The reviewer must check ACI 318-19 clause references, units, applicability limits, benchmark tolerances, and that the Typst sheet presents only (no recomputation) per PROJECT_STATE.md.
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
No files to modify in this task. The reviewer writes a review note (verbal output) and runs the simplify skill as part of the standard review pass. If STATUS: FAIL, the orchestrator updates TASKS.md and re-delegates.
|
||||||
|
|
||||||
|
If the reviewer finds fixable issues, they must be reported as a structured list with file and line references.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Reviewer steps (read-only, then run commands via delegation tool if needed — but orchestrator handles command execution; reviewer describes what to run and checks output):
|
||||||
|
|
||||||
|
1. Read `PROJECT_STATE.md`, `TASKS.md`, `calcs/concentric-footing/calc.py`, `input.yaml`, `results.json`, `footing.typ`, `test_concentric_footing.py`, `README.md`, `codemap.md`, and `CONCENTRIC-FOOTING.pdf`.
|
||||||
|
2. Verify engineering spec. Formula → code-section map (all in `calc.py:compute()`):
|
||||||
|
- Soil bearing `q=Ps/Af`, `qu=Pu/Af`: Pressures block → `q_psf`, `qu_psf`.
|
||||||
|
- One-way shear `Vc=2·λ·√fc·B·d`, `Vu=qu·B·L1`: One-way shear block → `L1_in`, `Vu_one_way_kip`, `Vc_one_way_kip`, `phiVc_one_way_kip`.
|
||||||
|
- Two-way shear `bo=4(c+d)`, `vc=min(4,2+4/β,2+α·d/bo)·λ·√fc`, `Vu=qu·(Af-(c+d)²)`: Two-way shear block → `bo_in`, `vc_psi`, `Vu_two_way_kip`, `Vc_two_way_kip`, `phiVn_two_way_kip`.
|
||||||
|
- Flexure `Mu=qu·B·Lc²/2`, `a=As·fy/(0.85·fc·B)`, `Mn=As·fy·(d-a/2)`: Flexure block → `Lc_in`, `Mu_kipft`, `a_in`, `beta1`, `Mn_kipft`, `phiMn_kipft`.
|
||||||
|
- Bearing `Bn=0.85·fc·A1·min(√(A2/A1),2)`: Concrete bearing block → `A1_in2`, `A2_in2`, `sqrt_ratio`, `Bn_kip`, `phiBn_kip`.
|
||||||
|
- Soil bearing `q=Ps/Af` and `qu=Pu/Af` correctly use Af=Bf^2 in ft2, Ps/Pu in kip, qa in psf, D/C correct.
|
||||||
|
- One-way shear Vc=2*lambda*sqrt(fc)*B*d with phi 0.75, L1 = (B-c)/2 - d, Vu = qu*B*L1, correct unit conversion lbf->kip, handles L1<=0 edge.
|
||||||
|
- Two-way shear bo=4*(c+d), vc=min(4,2+4/beta,2+alpha*d/bo)*lambda*sqrt(fc) with alpha 40 interior, beta 1, phi 0.75, Vu=qu*(Af - (c+d)^2), bo correction documented.
|
||||||
|
- Flexure Mu=qu*B*Lc^2/2, a=As*fy/(0.85*fc*B), Mn=As*fy*(d-a/2), phi 0.9, beta1 formula per ACI, rho vs 0.0018.
|
||||||
|
- Bearing Bn=0.85*fc*A1*min(sqrt(A2/A1),2), phi 0.65, A1=bp^2, A2=Bf^2, sqrt cap.
|
||||||
|
- d = Df - cover to centroid, validation, lambda bounds, rebar area from #size.
|
||||||
|
3. Verify deterministic evidence:
|
||||||
|
- Run `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v` and confirm 12 passed, no skipped.
|
||||||
|
- Run `python calcs/concentric-footing/calc.py --stdout | python -m json.tool` and confirm idempotent vs results.json (diff empty).
|
||||||
|
- Run `typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf` and confirm PDF exists and size >50KB.
|
||||||
|
- Run Typst queries for `<concentric-footing-loads>` and `<concentric-footing-results>` and confirm reconciliation within tolerances.
|
||||||
|
4. Check scope/limitations note in footing.typ mentions square only, interior only, gross pressure, no moment, bo correction, plate clarification, d definition, and that existing calc files were not touched.
|
||||||
|
5. Run simplify skill pass on the diff (calc.py + footing.typ + test file) for readability, no behavior change.
|
||||||
|
6. Return STATUS: PASS if all 8 checks below are satisfied, else STATUS: FAIL with enumerated issues.
|
||||||
|
|
||||||
|
## Acceptance Criteria (all must be true for PASS)
|
||||||
|
|
||||||
|
1. All six checks pass for default input; D/C values within expected ranges (soil ~0.82, one-way ~0.05, two-way ~0.11, flexure ~0.12, bearing ~0.22).
|
||||||
|
2. Corrected punching perimeter bo=92in is used; review notes the PDF's 68in deviation and that the sheet's Scope documents it.
|
||||||
|
3. Bearing plate vs column clarified (A1=36in2, A2=1296in2, Bn 183.6kip); alternative use of column size would be noted.
|
||||||
|
4. Pint unit conversions tested (alternate units test passes) and wrong-dimension guards raise ValueError with field name.
|
||||||
|
5. Effective depth d = Df - cover correctly validated; zero/negative d raises ValueError.
|
||||||
|
6. Typst sheet presents only (no capacity recomputation), derives loads inline, emits both `<concentric-footing-loads>` and `<concentric-footing-results>` metadata, and compiles.
|
||||||
|
7. README and codemap refreshed and list the new calculation; no existing calc files were modified (git diff or file timestamps check).
|
||||||
|
8. Pytest 12 passed, PDF compiles, results.json idempotent, simplify pass clean (no behavior change).
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Reviewer (via orchestrator delegation) runs:
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||||
|
python calcs/concentric-footing/calc.py --stdout
|
||||||
|
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
All succeed; 12 passed.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Task 004 must be DONE.
|
||||||
170
concentric-footing/test_concentric_footing.py
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
spec = importlib.util.spec_from_file_location("concentric_footing_calc", HERE / "calc.py")
|
||||||
|
assert spec is not None and spec.loader is not None
|
||||||
|
calc_module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(calc_module)
|
||||||
|
compute = calc_module.compute
|
||||||
|
|
||||||
|
|
||||||
|
def load_input():
|
||||||
|
with (HERE / "input.yaml").open(encoding="utf-8") as handle:
|
||||||
|
return yaml.safe_load(handle)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
return compute(load_input())
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_pressures_and_geometry(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["Af_ft2"] == pytest.approx(9.0)
|
||||||
|
assert v["Bf_ft"] == pytest.approx(3.0)
|
||||||
|
assert v["Bf_in"] == pytest.approx(36.0)
|
||||||
|
assert v["d_in"] == pytest.approx(9.0)
|
||||||
|
assert v["q_psf"] == pytest.approx(2044.44, rel=0.01)
|
||||||
|
assert v["qu_psf"] == pytest.approx(2911.11, rel=0.01)
|
||||||
|
assert v["qa_psf"] == 2500.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_one_way_shear(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["L1_in"] == pytest.approx(2.0, abs=0.01)
|
||||||
|
assert v["Vu_one_way_kip"] == pytest.approx(1.455, abs=0.05)
|
||||||
|
assert v["Vc_one_way_kip"] == pytest.approx(35.45, rel=0.01)
|
||||||
|
assert v["phiVc_one_way_kip"] == pytest.approx(26.59, rel=0.01)
|
||||||
|
assert result["checks"]["one_way_shear"]["ok"] is True
|
||||||
|
dc = v["Vu_one_way_kip"] / v["phiVc_one_way_kip"]
|
||||||
|
assert dc == pytest.approx(0.055, abs=0.001)
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_two_way_shear(result):
|
||||||
|
v = result["values"]
|
||||||
|
# Reference PDF used 68 in; ACI-correct is 92 in (4*(c+d) for interior square column).
|
||||||
|
# PDF's 68 in perimeter yields ~134 kip Vc; ACI-correct 92 in yields ~181.4 kip Vc (phiVn ~136 kip).
|
||||||
|
assert v["bo_in"] == pytest.approx(92.0)
|
||||||
|
assert v["vc_psi"] == pytest.approx(219.089, rel=1e-3)
|
||||||
|
assert v["Vc_two_way_kip"] == pytest.approx(181.4, rel=0.02)
|
||||||
|
assert v["phiVn_two_way_kip"] == pytest.approx(136.0, rel=0.02)
|
||||||
|
assert v["Vu_two_way_kip"] == pytest.approx(15.51, abs=0.1)
|
||||||
|
assert result["checks"]["two_way_shear"]["ok"] is True
|
||||||
|
dc = v["Vu_two_way_kip"] / v["phiVn_two_way_kip"]
|
||||||
|
assert dc == pytest.approx(0.114, abs=0.002)
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_flexure(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["Lc_in"] == pytest.approx(11.0)
|
||||||
|
assert v["Mu_kipft"] == pytest.approx(3.68, abs=0.1)
|
||||||
|
assert v["a_in"] == pytest.approx(0.524, abs=0.02)
|
||||||
|
assert v["As_in2"] == pytest.approx(0.785, abs=0.02)
|
||||||
|
assert v["Mn_kipft"] == pytest.approx(34.27, abs=0.5)
|
||||||
|
assert v["phiMn_kipft"] == pytest.approx(30.84, abs=0.5)
|
||||||
|
assert v["rho"] == pytest.approx(0.00242, rel=0.02)
|
||||||
|
assert result["checks"]["flexure"]["ok"] is True
|
||||||
|
assert result["checks"]["minimum_steel"]["ok"] is True
|
||||||
|
dc = v["Mu_kipft"] / v["phiMn_kipft"]
|
||||||
|
assert dc == pytest.approx(0.12, abs=0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_bearing(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["A1_in2"] == pytest.approx(36.0)
|
||||||
|
assert v["A2_in2"] == pytest.approx(1296.0)
|
||||||
|
# Raw ratio stored; Bn uses capped 2.0.
|
||||||
|
assert v["sqrt_ratio"] == pytest.approx(6.0, abs=0.01)
|
||||||
|
assert v["Bn_kip"] == pytest.approx(183.6, abs=0.5)
|
||||||
|
assert v["phiBn_kip"] == pytest.approx(119.34, abs=0.5)
|
||||||
|
assert result["checks"]["bearing"]["ok"] is True
|
||||||
|
dc = v["Pu_kip"] / v["phiBn_kip"]
|
||||||
|
assert dc == pytest.approx(0.22, abs=0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_checks_pass(result):
|
||||||
|
for k in ("soil_bearing", "one_way_shear", "two_way_shear", "flexure", "minimum_steel", "bearing"):
|
||||||
|
assert result["checks"][k]["ok"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_alternate_units_match_default(result):
|
||||||
|
alt = load_input()
|
||||||
|
alt.update({
|
||||||
|
"Bf": "36 in",
|
||||||
|
"fc": "3 ksi",
|
||||||
|
"fy": "60000 psi",
|
||||||
|
"Ps": "18400 lbf",
|
||||||
|
"Pu": "26200 lbf",
|
||||||
|
})
|
||||||
|
converted = compute(alt)
|
||||||
|
for key in ("Af_ft2", "q_psf", "Mu_kipft", "Vu_one_way_kip", "phiBn_kip"):
|
||||||
|
assert converted["values"][key] == pytest.approx(result["values"][key], rel=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wrong_dimension_is_rejected():
|
||||||
|
bad = load_input()
|
||||||
|
bad["Bf"] = "3 kip"
|
||||||
|
with pytest.raises(ValueError, match="Bf"):
|
||||||
|
compute(bad)
|
||||||
|
|
||||||
|
|
||||||
|
def test_effective_depth_validation():
|
||||||
|
bad = load_input()
|
||||||
|
bad["cover"] = "13 in"
|
||||||
|
bad["Df"] = "12 in"
|
||||||
|
with pytest.raises(ValueError, match="d"):
|
||||||
|
compute(bad)
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_footing_size_rejected():
|
||||||
|
bad = load_input()
|
||||||
|
bad["Bf"] = "0 ft"
|
||||||
|
with pytest.raises(ValueError, match="Bf"):
|
||||||
|
compute(bad)
|
||||||
|
|
||||||
|
|
||||||
|
def test_typst_compiles_and_presents_python_numbers(result):
|
||||||
|
subprocess.run([sys.executable, str(HERE / "calc.py")], check=True, cwd=HERE.parents[1])
|
||||||
|
pdf = HERE / "generated" / "footing.pdf"
|
||||||
|
pdf.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
subprocess.run(
|
||||||
|
["typst", "compile", "--root", ".", "calcs/concentric-footing/footing.typ", str(pdf)],
|
||||||
|
check=True, cwd=HERE.parents[1],
|
||||||
|
)
|
||||||
|
assert pdf.exists()
|
||||||
|
query = subprocess.run(
|
||||||
|
["typst", "eval", "query(<concentric-footing-results>)", "--root", ".",
|
||||||
|
"--in", "calcs/concentric-footing/footing.typ", "--format", "json"],
|
||||||
|
check=True, capture_output=True, text=True, cwd=HERE.parents[1],
|
||||||
|
)
|
||||||
|
published = json.loads(query.stdout)
|
||||||
|
assert len(published) == 1
|
||||||
|
meta = published[0]["value"]
|
||||||
|
values = result["values"]
|
||||||
|
assert meta["soil_util"] == pytest.approx(values["q_psf"] / values["qa_psf"], abs=0.001)
|
||||||
|
assert meta["one_way_util"] == pytest.approx(values["Vu_one_way_kip"] / values["phiVc_one_way_kip"], abs=0.001)
|
||||||
|
assert meta["two_way_util"] == pytest.approx(values["Vu_two_way_kip"] / values["phiVn_two_way_kip"], abs=0.001)
|
||||||
|
assert meta["flexure_util"] == pytest.approx(values["Mu_kipft"] / values["phiMn_kipft"], abs=0.001)
|
||||||
|
assert meta["bearing_util"] == pytest.approx(values["Pu_kip"] / values["phiBn_kip"], abs=0.001)
|
||||||
|
|
||||||
|
|
||||||
|
def test_typst_load_demands_match_checked_inputs(result):
|
||||||
|
subprocess.run([sys.executable, str(HERE / "calc.py")], check=True, cwd=HERE.parents[1])
|
||||||
|
query = subprocess.run(
|
||||||
|
["typst", "eval", "query(<concentric-footing-loads>)", "--root", ".",
|
||||||
|
"--in", "calcs/concentric-footing/footing.typ", "--format", "json"],
|
||||||
|
check=True, capture_output=True, text=True, cwd=HERE.parents[1],
|
||||||
|
)
|
||||||
|
published = json.loads(query.stdout)
|
||||||
|
assert len(published) == 1
|
||||||
|
derived = published[0]["value"]
|
||||||
|
values = result["values"]
|
||||||
|
assert derived["Ps_kip"] == pytest.approx(values["Ps_kip"], abs=0.1)
|
||||||
|
assert derived["Pu_kip"] == pytest.approx(values["Pu_kip"], abs=0.1)
|
||||||
BIN
concrete-beam/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
concrete-beam/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
132
concrete-beam/calc.py
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit("Install PyYAML: python -m pip install pyyaml")
|
||||||
|
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
|
||||||
|
def _require_positive(name: str, value: float) -> float:
|
||||||
|
if value <= 0:
|
||||||
|
raise ValueError(f"{name} must be positive")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def compute(inp: dict) -> dict:
|
||||||
|
span_ft = _require_positive("span_ft", float(inp["span_ft"]))
|
||||||
|
tributary_ft = _require_positive("tributary_ft", float(inp["tributary_ft"]))
|
||||||
|
D_psf = _require_positive("D_psf", float(inp["D_psf"]))
|
||||||
|
L_psf = _require_positive("L_psf", float(inp["L_psf"]))
|
||||||
|
bw_in = _require_positive("bw_in", float(inp["bw_in"]))
|
||||||
|
h_in = _require_positive("h_in", float(inp["h_in"]))
|
||||||
|
d_in = _require_positive("d_in", float(inp["d_in"]))
|
||||||
|
fc_ksi = _require_positive("fc_ksi", float(inp["fc_ksi"]))
|
||||||
|
fy_ksi = _require_positive("fy_ksi", float(inp["fy_ksi"]))
|
||||||
|
As_in2 = _require_positive("As_in2", float(inp["As_in2"]))
|
||||||
|
concrete_pcf = _require_positive("concrete_pcf", float(inp["concrete_pcf"]))
|
||||||
|
|
||||||
|
self_weight_klf = (bw_in * h_in / 144.0) * concrete_pcf / 1000.0
|
||||||
|
wD_klf = D_psf * tributary_ft / 1000.0 + self_weight_klf
|
||||||
|
wL_klf = L_psf * tributary_ft / 1000.0
|
||||||
|
wu_klf = 1.2 * wD_klf + 1.6 * wL_klf
|
||||||
|
Mu_kipft = wu_klf * span_ft**2 / 8.0
|
||||||
|
Vu_kip = wu_klf * span_ft / 2.0
|
||||||
|
|
||||||
|
fc_psi = fc_ksi * 1000.0
|
||||||
|
fy_psi = fy_ksi * 1000.0
|
||||||
|
a_in = As_in2 * fy_ksi / (0.85 * fc_ksi * bw_in)
|
||||||
|
beta1 = max(0.65, min(0.85, 0.85 - 0.05 * max(0.0, (fc_psi - 4000.0) / 1000.0)))
|
||||||
|
c_in = a_in / beta1
|
||||||
|
et = 0.003 * (d_in - c_in) / c_in if c_in > 0 else 0.0
|
||||||
|
if et >= 0.005:
|
||||||
|
phi = 0.90
|
||||||
|
else:
|
||||||
|
phi = max(0.65, min(0.90, 0.65 + (et - 0.002) * 250.0 / 3.0))
|
||||||
|
Mn_kipft = As_in2 * fy_ksi * (d_in - a_in / 2.0) / 12.0
|
||||||
|
phiMn_kipft = phi * Mn_kipft
|
||||||
|
|
||||||
|
rho_min = max(3.0 * math.sqrt(fc_psi) / fy_psi, 200.0 / fy_psi)
|
||||||
|
As_min_in2 = rho_min * bw_in * d_in
|
||||||
|
|
||||||
|
Vc_kip = 2.0 * math.sqrt(fc_psi) * bw_in * d_in / 1000.0
|
||||||
|
phiVc_kip = 0.75 * Vc_kip
|
||||||
|
|
||||||
|
def q(value: float) -> float:
|
||||||
|
return round(value, 6)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"tool": "concrete_beam",
|
||||||
|
"version": "0.1",
|
||||||
|
"project": inp.get("project", ""),
|
||||||
|
"prepared_by": inp.get("prepared_by", ""),
|
||||||
|
"values": {
|
||||||
|
"span_ft": q(span_ft),
|
||||||
|
"tributary_ft": q(tributary_ft),
|
||||||
|
"D_psf": q(D_psf),
|
||||||
|
"L_psf": q(L_psf),
|
||||||
|
"self_weight_klf": q(self_weight_klf),
|
||||||
|
"wD_klf": q(wD_klf),
|
||||||
|
"wL_klf": q(wL_klf),
|
||||||
|
"wu_klf": q(wu_klf),
|
||||||
|
"Mu_kipft": q(Mu_kipft),
|
||||||
|
"Vu_kip": q(Vu_kip),
|
||||||
|
"bw_in": q(bw_in),
|
||||||
|
"h_in": q(h_in),
|
||||||
|
"d_in": q(d_in),
|
||||||
|
"fc_ksi": q(fc_ksi),
|
||||||
|
"fy_ksi": q(fy_ksi),
|
||||||
|
"As_in2": q(As_in2),
|
||||||
|
"a_in": q(a_in),
|
||||||
|
"et": q(et),
|
||||||
|
"phi": q(phi),
|
||||||
|
"Mn_kipft": q(Mn_kipft),
|
||||||
|
"phiMn_kipft": q(phiMn_kipft),
|
||||||
|
"As_min_in2": q(As_min_in2),
|
||||||
|
"Vc_kip": q(Vc_kip),
|
||||||
|
"phiVc_kip": q(phiVc_kip),
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"flexure": {
|
||||||
|
"demand": q(Mu_kipft),
|
||||||
|
"capacity": q(phiMn_kipft),
|
||||||
|
"ok": Mu_kipft <= phiMn_kipft,
|
||||||
|
},
|
||||||
|
"minimum_steel": {
|
||||||
|
"demand": q(As_min_in2),
|
||||||
|
"capacity": q(As_in2),
|
||||||
|
"ok": As_in2 >= As_min_in2,
|
||||||
|
},
|
||||||
|
"shear": {
|
||||||
|
"demand": q(Vu_kip),
|
||||||
|
"capacity": q(phiVc_kip),
|
||||||
|
"ok": Vu_kip <= phiVc_kip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def write_results(result: dict, path: Path) -> None:
|
||||||
|
path.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
with input_path.open(encoding="utf-8") as handle:
|
||||||
|
inp = yaml.safe_load(handle)
|
||||||
|
result = compute(inp)
|
||||||
|
write_results(result, output_path)
|
||||||
|
print(output_path)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
1432
concrete-beam/concrete-beam.pdf
Normal file
119
concrete-beam/concrete-beam.typ
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#import "assets/sheet.typ": calcline, calcsheet, check
|
||||||
|
|
||||||
|
#let data = json("results.json")
|
||||||
|
#let n = data.values
|
||||||
|
#let checks = data.checks
|
||||||
|
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||||
|
|
||||||
|
#show: calcsheet.with(
|
||||||
|
title: "Concrete Beam Analysis",
|
||||||
|
project: data.project,
|
||||||
|
prepared-by: data.prepared_by,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Reinforced Concrete Beam
|
||||||
|
|
||||||
|
Simple-span rectangular beam under uniform gravity load. Numbers come from `calc.py`. This sheet only presents them.
|
||||||
|
|
||||||
|
#let beam-sketch = {
|
||||||
|
set align(center)
|
||||||
|
box(width: 82%, inset: (y: 8pt))[
|
||||||
|
#line(length: 100%, stroke: 1.4pt)
|
||||||
|
#v(-7.5pt)
|
||||||
|
#grid(
|
||||||
|
columns: (auto, 1fr, auto),
|
||||||
|
align: (left, center, right),
|
||||||
|
polygon(fill: black, (0pt, 0pt), (8pt, 10pt), (-8pt, 10pt)),
|
||||||
|
text(size: 9pt)[$w_u$ uniform factored load],
|
||||||
|
polygon(fill: black, (0pt, 0pt), (8pt, 10pt), (-8pt, 10pt)),
|
||||||
|
)
|
||||||
|
#v(2pt)
|
||||||
|
#text(size: 9pt)[#n.span_ft ft simple span · #n.bw_in in × #n.h_in in section]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
beam-sketch,
|
||||||
|
caption: [#n.span_ft ft simply supported beam, #n.bw_in in × #n.h_in in rectangular section.],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Loads and Beam Demand
|
||||||
|
|
||||||
|
#calcline([$L = #n.span_ft " ft"$], [Simple span])
|
||||||
|
#calcline([$B_t = #n.tributary_ft " ft"$], [Tributary width])
|
||||||
|
#calcline([$D = #n.D_psf " psf"$], [Dead load including superimposed dead])
|
||||||
|
#calcline([$L_L = #n.L_psf " psf"$], [Live load])
|
||||||
|
#calcline([$w_("sw") = #round(n.self_weight_klf, digits: 3) " kip/ft"$], [Beam self-weight])
|
||||||
|
#calcline(
|
||||||
|
[$w_u = 1.2 w_D + 1.6 w_L = #round(n.wu_klf, digits: 3) " kip/ft"$],
|
||||||
|
[Factored uniform line load],
|
||||||
|
)
|
||||||
|
#calcline(
|
||||||
|
[$M_u = w_u L^2 / 8 = #round(n.Mu_kipft) " kip·ft"$],
|
||||||
|
[Maximum positive moment],
|
||||||
|
)
|
||||||
|
#calcline(
|
||||||
|
[$V_u = w_u L / 2 = #round(n.Vu_kip) " kip"$],
|
||||||
|
[Support shear],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Flexural Strength
|
||||||
|
|
||||||
|
#calcline([$b_w = #n.bw_in " in"$], [Beam width])
|
||||||
|
#calcline([$h = #n.h_in " in"$], [Overall depth])
|
||||||
|
#calcline([$d = #n.d_in " in"$], [Effective depth])
|
||||||
|
#calcline([$f'_c = #n.fc_ksi " ksi"$], [Concrete compressive strength])
|
||||||
|
#calcline([$f_y = #n.fy_ksi " ksi"$], [Steel yield strength])
|
||||||
|
#calcline([$A_s = #n.As_in2 " in"^2$], [Provided tension steel (2 No. 5)])
|
||||||
|
#calcline(
|
||||||
|
[$a = A_s f_y / (0.85 f'_c b_w) = #round(n.a_in, digits: 3) " in"$],
|
||||||
|
[Equivalent compression-block depth],
|
||||||
|
)
|
||||||
|
#calcline([$epsilon_t = #round(n.et, digits: 4)$], [Net tensile strain])
|
||||||
|
#calcline([$phi = #round(n.phi, digits: 2)$], [Strength reduction factor])
|
||||||
|
#calcline(
|
||||||
|
[$phi M_n = phi A_s f_y (d - a/2) = #round(n.phiMn_kipft) " kip·ft"$],
|
||||||
|
[Design flexural strength],
|
||||||
|
)
|
||||||
|
|
||||||
|
#v(7pt)
|
||||||
|
#check(
|
||||||
|
"Flexural strength",
|
||||||
|
checks.flexure.demand,
|
||||||
|
checks.flexure.capacity,
|
||||||
|
unit: "kip·ft",
|
||||||
|
ok: checks.flexure.ok,
|
||||||
|
demand-label: [$M_u$],
|
||||||
|
capacity-label: [$phi M_n$],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Minimum Steel and Concrete Shear
|
||||||
|
|
||||||
|
#calcline([$A_("s,min") = #round(n.As_min_in2, digits: 3) " in"^2$], [Minimum longitudinal steel])
|
||||||
|
#calcline([$A_("s,prov") = #round(n.As_in2, digits: 3) " in"^2$], [Provided longitudinal steel])
|
||||||
|
|
||||||
|
#v(7pt)
|
||||||
|
#check(
|
||||||
|
"Minimum longitudinal reinforcement",
|
||||||
|
checks.minimum_steel.demand,
|
||||||
|
checks.minimum_steel.capacity,
|
||||||
|
unit: "in²",
|
||||||
|
ok: checks.minimum_steel.ok,
|
||||||
|
demand-label: [$A_("s,min")$],
|
||||||
|
capacity-label: [$A_("s,prov")$],
|
||||||
|
)
|
||||||
|
|
||||||
|
#v(10pt)
|
||||||
|
#calcline([$V_c = 2 sqrt(f'_c) b_w d = #round(n.Vc_kip) " kip"$], [Concrete shear strength])
|
||||||
|
#calcline([$phi V_c = #round(n.phiVc_kip) " kip"$], [Design concrete shear strength])
|
||||||
|
|
||||||
|
#v(7pt)
|
||||||
|
#check(
|
||||||
|
"Concrete shear",
|
||||||
|
checks.shear.demand,
|
||||||
|
checks.shear.capacity,
|
||||||
|
unit: "kip",
|
||||||
|
ok: checks.shear.ok,
|
||||||
|
demand-label: [$V_u$],
|
||||||
|
capacity-label: [$phi V_c$],
|
||||||
|
)
|
||||||
15
concrete-beam/input.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
project: "Deer Creek Shoring"
|
||||||
|
prepared_by: "Conemco Engineering"
|
||||||
|
|
||||||
|
span_ft: 16
|
||||||
|
tributary_ft: 6.25
|
||||||
|
D_psf: 55
|
||||||
|
L_psf: 20
|
||||||
|
bw_in: 8
|
||||||
|
h_in: 12
|
||||||
|
d_in: 9.5
|
||||||
|
fc_ksi: 3.0
|
||||||
|
fy_ksi: 60
|
||||||
|
As_in2: 0.62
|
||||||
|
concrete_pcf: 150
|
||||||
|
load_combination: "1.2D + 1.6L"
|
||||||
49
concrete-beam/results.json
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"tool": "concrete_beam",
|
||||||
|
"version": "0.1",
|
||||||
|
"project": "Deer Creek Shoring",
|
||||||
|
"prepared_by": "Conemco Engineering",
|
||||||
|
"values": {
|
||||||
|
"span_ft": 16.0,
|
||||||
|
"tributary_ft": 6.25,
|
||||||
|
"D_psf": 55.0,
|
||||||
|
"L_psf": 20.0,
|
||||||
|
"self_weight_klf": 0.1,
|
||||||
|
"wD_klf": 0.44375,
|
||||||
|
"wL_klf": 0.125,
|
||||||
|
"wu_klf": 0.7325,
|
||||||
|
"Mu_kipft": 23.44,
|
||||||
|
"Vu_kip": 5.86,
|
||||||
|
"bw_in": 8.0,
|
||||||
|
"h_in": 12.0,
|
||||||
|
"d_in": 9.5,
|
||||||
|
"fc_ksi": 3.0,
|
||||||
|
"fy_ksi": 60.0,
|
||||||
|
"As_in2": 0.62,
|
||||||
|
"a_in": 1.823529,
|
||||||
|
"et": 0.010285,
|
||||||
|
"phi": 0.9,
|
||||||
|
"Mn_kipft": 26.623529,
|
||||||
|
"phiMn_kipft": 23.961176,
|
||||||
|
"As_min_in2": 0.253333,
|
||||||
|
"Vc_kip": 8.325383,
|
||||||
|
"phiVc_kip": 6.244037
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"flexure": {
|
||||||
|
"demand": 23.44,
|
||||||
|
"capacity": 23.961176,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"minimum_steel": {
|
||||||
|
"demand": 0.253333,
|
||||||
|
"capacity": 0.62,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"shear": {
|
||||||
|
"demand": 5.86,
|
||||||
|
"capacity": 6.244037,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
concrete-beam/test_concrete_beam.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location("concrete_beam_calc", HERE / "calc.py")
|
||||||
|
assert spec is not None and spec.loader is not None
|
||||||
|
calc_module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(calc_module)
|
||||||
|
compute = calc_module.compute
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
with (HERE / "input.yaml").open(encoding="utf-8") as handle:
|
||||||
|
return compute(yaml.safe_load(handle))
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_demands(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["self_weight_klf"] == pytest.approx(0.1)
|
||||||
|
assert v["wu_klf"] == pytest.approx(0.7325)
|
||||||
|
assert v["Mu_kipft"] == pytest.approx(23.44)
|
||||||
|
assert v["Vu_kip"] == pytest.approx(5.86)
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_flexure(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["a_in"] == pytest.approx(1.823529, rel=1e-5)
|
||||||
|
assert v["et"] == pytest.approx(0.010283, rel=1e-3)
|
||||||
|
assert v["phi"] == pytest.approx(0.9)
|
||||||
|
assert v["phiMn_kipft"] == pytest.approx(23.961176, rel=1e-5)
|
||||||
|
assert result["checks"]["flexure"]["ok"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_min_steel_and_shear(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["As_min_in2"] == pytest.approx(0.253333, rel=1e-4)
|
||||||
|
assert v["phiVc_kip"] == pytest.approx(6.244016, rel=1e-4)
|
||||||
|
assert result["checks"]["minimum_steel"]["ok"] is True
|
||||||
|
assert result["checks"]["shear"]["ok"] is True
|
||||||
BIN
concrete-beam2/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
concrete-beam2/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
154
concrete-beam2/calc.py
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit("Install PyYAML: python -m pip install pyyaml")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pint import DimensionalityError, UndefinedUnitError, UnitRegistry
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit("Install Pint: python -m pip install pint")
|
||||||
|
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
ureg = UnitRegistry()
|
||||||
|
ureg.define("kip = 1000 * force_pound")
|
||||||
|
ureg.define("ksi = kip / inch ** 2")
|
||||||
|
ureg.define("klf = kip / foot")
|
||||||
|
if "psf" not in ureg:
|
||||||
|
ureg.define("psf = force_pound / foot ** 2")
|
||||||
|
if "pcf" not in ureg:
|
||||||
|
ureg.define("pcf = force_pound / foot ** 3")
|
||||||
|
|
||||||
|
|
||||||
|
def to_magnitude(value, unit: str, name: str) -> float:
|
||||||
|
try:
|
||||||
|
quantity = ureg.Quantity(value)
|
||||||
|
except (UndefinedUnitError, ValueError, TypeError) as exc:
|
||||||
|
raise ValueError(f"{name}: cannot parse {value!r}") from exc
|
||||||
|
try:
|
||||||
|
magnitude = float(quantity.to(unit).magnitude)
|
||||||
|
except DimensionalityError as exc:
|
||||||
|
raise ValueError(f"{name}: expected {unit}, got {value!r}") from exc
|
||||||
|
if magnitude <= 0:
|
||||||
|
raise ValueError(f"{name} must be positive")
|
||||||
|
return magnitude
|
||||||
|
|
||||||
|
|
||||||
|
def compute(inp: dict) -> dict:
|
||||||
|
span_ft = to_magnitude(inp["span"], "ft", "span")
|
||||||
|
tributary_ft = to_magnitude(inp["tributary"], "ft", "tributary")
|
||||||
|
D_psf = to_magnitude(inp["D"], "psf", "D")
|
||||||
|
L_psf = to_magnitude(inp["L"], "psf", "L")
|
||||||
|
bw_in = to_magnitude(inp["bw"], "in", "bw")
|
||||||
|
h_in = to_magnitude(inp["h"], "in", "h")
|
||||||
|
d_in = to_magnitude(inp["d"], "in", "d")
|
||||||
|
fc_ksi = to_magnitude(inp["fc"], "ksi", "fc")
|
||||||
|
fy_ksi = to_magnitude(inp["fy"], "ksi", "fy")
|
||||||
|
As_in2 = to_magnitude(inp["As"], "in**2", "As")
|
||||||
|
concrete_pcf = to_magnitude(inp["concrete_density"], "pcf", "concrete_density")
|
||||||
|
|
||||||
|
self_weight_klf = (bw_in * h_in / 144.0) * concrete_pcf / 1000.0
|
||||||
|
wD_klf = D_psf * tributary_ft / 1000.0 + self_weight_klf
|
||||||
|
wL_klf = L_psf * tributary_ft / 1000.0
|
||||||
|
wu_klf = 1.2 * wD_klf + 1.6 * wL_klf
|
||||||
|
Mu_kipft = wu_klf * span_ft**2 / 8.0
|
||||||
|
Vu_kip = wu_klf * span_ft / 2.0
|
||||||
|
|
||||||
|
fc_psi = fc_ksi * 1000.0
|
||||||
|
fy_psi = fy_ksi * 1000.0
|
||||||
|
a_in = As_in2 * fy_ksi / (0.85 * fc_ksi * bw_in)
|
||||||
|
beta1 = max(0.65, min(0.85, 0.85 - 0.05 * max(0.0, (fc_psi - 4000.0) / 1000.0)))
|
||||||
|
c_in = a_in / beta1
|
||||||
|
et = 0.003 * (d_in - c_in) / c_in if c_in > 0 else 0.0
|
||||||
|
if et >= 0.005:
|
||||||
|
phi = 0.90
|
||||||
|
else:
|
||||||
|
phi = max(0.65, min(0.90, 0.65 + (et - 0.002) * 250.0 / 3.0))
|
||||||
|
Mn_kipft = As_in2 * fy_ksi * (d_in - a_in / 2.0) / 12.0
|
||||||
|
phiMn_kipft = phi * Mn_kipft
|
||||||
|
|
||||||
|
rho_min = max(3.0 * math.sqrt(fc_psi) / fy_psi, 200.0 / fy_psi)
|
||||||
|
As_min_in2 = rho_min * bw_in * d_in
|
||||||
|
|
||||||
|
Vc_kip = 2.0 * math.sqrt(fc_psi) * bw_in * d_in / 1000.0
|
||||||
|
phiVc_kip = 0.75 * Vc_kip
|
||||||
|
|
||||||
|
def q(value: float) -> float:
|
||||||
|
return round(value, 6)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"tool": "concrete_beam2",
|
||||||
|
"version": "0.1",
|
||||||
|
"project": inp.get("project", ""),
|
||||||
|
"prepared_by": inp.get("prepared_by", ""),
|
||||||
|
"values": {
|
||||||
|
"span_ft": q(span_ft),
|
||||||
|
"tributary_ft": q(tributary_ft),
|
||||||
|
"D_psf": q(D_psf),
|
||||||
|
"L_psf": q(L_psf),
|
||||||
|
"self_weight_klf": q(self_weight_klf),
|
||||||
|
"wD_klf": q(wD_klf),
|
||||||
|
"wL_klf": q(wL_klf),
|
||||||
|
"wu_klf": q(wu_klf),
|
||||||
|
"Mu_kipft": q(Mu_kipft),
|
||||||
|
"Vu_kip": q(Vu_kip),
|
||||||
|
"bw_in": q(bw_in),
|
||||||
|
"h_in": q(h_in),
|
||||||
|
"d_in": q(d_in),
|
||||||
|
"fc_ksi": q(fc_ksi),
|
||||||
|
"fy_ksi": q(fy_ksi),
|
||||||
|
"As_in2": q(As_in2),
|
||||||
|
"a_in": q(a_in),
|
||||||
|
"et": q(et),
|
||||||
|
"phi": q(phi),
|
||||||
|
"Mn_kipft": q(Mn_kipft),
|
||||||
|
"phiMn_kipft": q(phiMn_kipft),
|
||||||
|
"As_min_in2": q(As_min_in2),
|
||||||
|
"Vc_kip": q(Vc_kip),
|
||||||
|
"phiVc_kip": q(phiVc_kip),
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"flexure": {
|
||||||
|
"demand": q(Mu_kipft),
|
||||||
|
"capacity": q(phiMn_kipft),
|
||||||
|
"ok": Mu_kipft <= phiMn_kipft,
|
||||||
|
},
|
||||||
|
"minimum_steel": {
|
||||||
|
"demand": q(As_min_in2),
|
||||||
|
"capacity": q(As_in2),
|
||||||
|
"ok": As_in2 >= As_min_in2,
|
||||||
|
},
|
||||||
|
"shear": {
|
||||||
|
"demand": q(Vu_kip),
|
||||||
|
"capacity": q(phiVc_kip),
|
||||||
|
"ok": Vu_kip <= phiVc_kip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def write_results(result: dict, path: Path) -> None:
|
||||||
|
path.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
with input_path.open(encoding="utf-8") as handle:
|
||||||
|
inp = yaml.safe_load(handle)
|
||||||
|
result = compute(inp)
|
||||||
|
write_results(result, output_path)
|
||||||
|
print(output_path)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
1392
concrete-beam2/concrete-beam2.pdf
Normal file
119
concrete-beam2/concrete-beam2.typ
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#import "assets/sheet.typ": calcline, calcsheet, check
|
||||||
|
|
||||||
|
#let data = json("results.json")
|
||||||
|
#let n = data.values
|
||||||
|
#let checks = data.checks
|
||||||
|
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||||
|
|
||||||
|
#show: calcsheet.with(
|
||||||
|
title: "Concrete Beam Analysis",
|
||||||
|
project: data.project,
|
||||||
|
prepared-by: data.prepared_by,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Reinforced Concrete Beam
|
||||||
|
|
||||||
|
Simple-span rectangular beam under uniform gravity load. YAML quantities are converted by Pint in `calc.py`. This sheet only presents the results.
|
||||||
|
|
||||||
|
#let beam-sketch = {
|
||||||
|
set align(center)
|
||||||
|
box(width: 82%, inset: (y: 8pt))[
|
||||||
|
#line(length: 100%, stroke: 1.4pt)
|
||||||
|
#v(-7.5pt)
|
||||||
|
#grid(
|
||||||
|
columns: (auto, 1fr, auto),
|
||||||
|
align: (left, center, right),
|
||||||
|
polygon(fill: black, (0pt, 0pt), (8pt, 10pt), (-8pt, 10pt)),
|
||||||
|
text(size: 9pt)[$w_u$ uniform factored load],
|
||||||
|
polygon(fill: black, (0pt, 0pt), (8pt, 10pt), (-8pt, 10pt)),
|
||||||
|
)
|
||||||
|
#v(2pt)
|
||||||
|
#text(size: 9pt)[#n.span_ft ft simple span · #n.bw_in in × #n.h_in in section]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
beam-sketch,
|
||||||
|
caption: [#n.span_ft ft simply supported beam, #n.bw_in in × #n.h_in in rectangular section.],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Loads and Beam Demand
|
||||||
|
|
||||||
|
#calcline([$L = #n.span_ft " ft"$], [Simple span])
|
||||||
|
#calcline([$B_t = #n.tributary_ft " ft"$], [Tributary width])
|
||||||
|
#calcline([$D = #n.D_psf " psf"$], [Dead load including superimposed dead])
|
||||||
|
#calcline([$L_L = #n.L_psf " psf"$], [Live load])
|
||||||
|
#calcline([$w_("sw") = #round(n.self_weight_klf, digits: 3) " kip/ft"$], [Beam self-weight])
|
||||||
|
#calcline(
|
||||||
|
[$w_u = 1.2 w_D + 1.6 w_L = #round(n.wu_klf, digits: 3) " kip/ft"$],
|
||||||
|
[Factored uniform line load],
|
||||||
|
)
|
||||||
|
#calcline(
|
||||||
|
[$M_u = w_u L^2 / 8 = #round(n.Mu_kipft) " kip·ft"$],
|
||||||
|
[Maximum positive moment],
|
||||||
|
)
|
||||||
|
#calcline(
|
||||||
|
[$V_u = w_u L / 2 = #round(n.Vu_kip) " kip"$],
|
||||||
|
[Support shear],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Flexural Strength
|
||||||
|
|
||||||
|
#calcline([$b_w = #n.bw_in " in"$], [Beam width])
|
||||||
|
#calcline([$h = #n.h_in " in"$], [Overall depth])
|
||||||
|
#calcline([$d = #n.d_in " in"$], [Effective depth])
|
||||||
|
#calcline([$f'_c = #n.fc_ksi " ksi"$], [Concrete compressive strength])
|
||||||
|
#calcline([$f_y = #n.fy_ksi " ksi"$], [Steel yield strength])
|
||||||
|
#calcline([$A_s = #n.As_in2 " in"^2$], [Provided tension steel (2 No. 5)])
|
||||||
|
#calcline(
|
||||||
|
[$a = A_s f_y / (0.85 f'_c b_w) = #round(n.a_in, digits: 3) " in"$],
|
||||||
|
[Equivalent compression-block depth],
|
||||||
|
)
|
||||||
|
#calcline([$epsilon_t = #round(n.et, digits: 4)$], [Net tensile strain])
|
||||||
|
#calcline([$phi = #round(n.phi, digits: 2)$], [Strength reduction factor])
|
||||||
|
#calcline(
|
||||||
|
[$phi M_n = phi A_s f_y (d - a/2) = #round(n.phiMn_kipft) " kip·ft"$],
|
||||||
|
[Design flexural strength],
|
||||||
|
)
|
||||||
|
|
||||||
|
#v(7pt)
|
||||||
|
#check(
|
||||||
|
"Flexural strength",
|
||||||
|
checks.flexure.demand,
|
||||||
|
checks.flexure.capacity,
|
||||||
|
unit: "kip·ft",
|
||||||
|
ok: checks.flexure.ok,
|
||||||
|
demand-label: [$M_u$],
|
||||||
|
capacity-label: [$phi M_n$],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Minimum Steel and Concrete Shear
|
||||||
|
|
||||||
|
#calcline([$A_("s,min") = #round(n.As_min_in2, digits: 3) " in"^2$], [Minimum longitudinal steel])
|
||||||
|
#calcline([$A_("s,prov") = #round(n.As_in2, digits: 3) " in"^2$], [Provided longitudinal steel])
|
||||||
|
|
||||||
|
#v(7pt)
|
||||||
|
#check(
|
||||||
|
"Minimum longitudinal reinforcement",
|
||||||
|
checks.minimum_steel.demand,
|
||||||
|
checks.minimum_steel.capacity,
|
||||||
|
unit: "in²",
|
||||||
|
ok: checks.minimum_steel.ok,
|
||||||
|
demand-label: [$A_("s,min")$],
|
||||||
|
capacity-label: [$A_("s,prov")$],
|
||||||
|
)
|
||||||
|
|
||||||
|
#v(10pt)
|
||||||
|
#calcline([$V_c = 2 sqrt(f'_c) b_w d = #round(n.Vc_kip) " kip"$], [Concrete shear strength])
|
||||||
|
#calcline([$phi V_c = #round(n.phiVc_kip) " kip"$], [Design concrete shear strength])
|
||||||
|
|
||||||
|
#v(7pt)
|
||||||
|
#check(
|
||||||
|
"Concrete shear",
|
||||||
|
checks.shear.demand,
|
||||||
|
checks.shear.capacity,
|
||||||
|
unit: "kip",
|
||||||
|
ok: checks.shear.ok,
|
||||||
|
demand-label: [$V_u$],
|
||||||
|
capacity-label: [$phi V_c$],
|
||||||
|
)
|
||||||
15
concrete-beam2/input.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
project: "Deer Creek Shoring"
|
||||||
|
prepared_by: "Conemco Engineering"
|
||||||
|
|
||||||
|
span: "16 ft"
|
||||||
|
tributary: "6.25 ft"
|
||||||
|
D: "55 psf"
|
||||||
|
L: "20 psf"
|
||||||
|
bw: "8 in"
|
||||||
|
h: "12 in"
|
||||||
|
d: "1 ft"
|
||||||
|
fc: "3000 psi"
|
||||||
|
fy: "50000 psi"
|
||||||
|
As: "0.62 in^2"
|
||||||
|
concrete_density: "150 pcf"
|
||||||
|
load_combination: "1.2D + 1.6L"
|
||||||
49
concrete-beam2/results.json
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"tool": "concrete_beam2",
|
||||||
|
"version": "0.1",
|
||||||
|
"project": "Deer Creek Shoring",
|
||||||
|
"prepared_by": "Conemco Engineering",
|
||||||
|
"values": {
|
||||||
|
"span_ft": 16.0,
|
||||||
|
"tributary_ft": 6.25,
|
||||||
|
"D_psf": 55.0,
|
||||||
|
"L_psf": 20.0,
|
||||||
|
"self_weight_klf": 0.1,
|
||||||
|
"wD_klf": 0.44375,
|
||||||
|
"wL_klf": 0.125,
|
||||||
|
"wu_klf": 0.7325,
|
||||||
|
"Mu_kipft": 23.44,
|
||||||
|
"Vu_kip": 5.86,
|
||||||
|
"bw_in": 8.0,
|
||||||
|
"h_in": 12.0,
|
||||||
|
"d_in": 12.0,
|
||||||
|
"fc_ksi": 3.0,
|
||||||
|
"fy_ksi": 50.0,
|
||||||
|
"As_in2": 0.62,
|
||||||
|
"a_in": 1.519608,
|
||||||
|
"et": 0.017137,
|
||||||
|
"phi": 0.9,
|
||||||
|
"Mn_kipft": 29.037173,
|
||||||
|
"phiMn_kipft": 26.133456,
|
||||||
|
"As_min_in2": 0.384,
|
||||||
|
"Vc_kip": 10.516273,
|
||||||
|
"phiVc_kip": 7.887205
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"flexure": {
|
||||||
|
"demand": 23.44,
|
||||||
|
"capacity": 26.133456,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"minimum_steel": {
|
||||||
|
"demand": 0.384,
|
||||||
|
"capacity": 0.62,
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"shear": {
|
||||||
|
"demand": 5.86,
|
||||||
|
"capacity": 7.887205,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
concrete-beam2/test_concrete_beam2.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location("concrete_beam2_calc", HERE / "calc.py")
|
||||||
|
assert spec is not None and spec.loader is not None
|
||||||
|
calc_module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(calc_module)
|
||||||
|
compute = calc_module.compute
|
||||||
|
|
||||||
|
|
||||||
|
def load_input():
|
||||||
|
with (HERE / "input.yaml").open(encoding="utf-8") as handle:
|
||||||
|
return yaml.safe_load(handle)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
return compute(load_input())
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_demands(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["self_weight_klf"] == pytest.approx(0.1)
|
||||||
|
assert v["wu_klf"] == pytest.approx(0.7325)
|
||||||
|
assert v["Mu_kipft"] == pytest.approx(23.44)
|
||||||
|
assert v["Vu_kip"] == pytest.approx(5.86)
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_flexure(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["a_in"] == pytest.approx(1.519608, rel=1e-5)
|
||||||
|
assert v["et"] == pytest.approx(0.017137, rel=1e-3)
|
||||||
|
assert v["phi"] == pytest.approx(0.9)
|
||||||
|
assert v["phiMn_kipft"] == pytest.approx(26.133456, rel=1e-5)
|
||||||
|
assert result["checks"]["flexure"]["ok"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_example_min_steel_and_shear(result):
|
||||||
|
v = result["values"]
|
||||||
|
assert v["As_min_in2"] == pytest.approx(0.384, rel=1e-4)
|
||||||
|
assert v["phiVc_kip"] == pytest.approx(7.887205, rel=1e-4)
|
||||||
|
assert result["checks"]["minimum_steel"]["ok"] is True
|
||||||
|
assert result["checks"]["shear"]["ok"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_alternate_units_match_default(result):
|
||||||
|
alt = load_input()
|
||||||
|
alt.update(
|
||||||
|
{
|
||||||
|
"span": "192 in",
|
||||||
|
"tributary": "75 in",
|
||||||
|
"fc": "3000 psi",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
converted = compute(alt)
|
||||||
|
for key in ("span_ft", "tributary_ft", "fc_ksi", "Mu_kipft", "phiMn_kipft", "Vu_kip"):
|
||||||
|
assert converted["values"][key] == pytest.approx(result["values"][key], rel=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wrong_dimension_is_rejected():
|
||||||
|
bad = load_input()
|
||||||
|
bad["span"] = "16 kip"
|
||||||
|
with pytest.raises(ValueError, match="span"):
|
||||||
|
compute(bad)
|
||||||
BIN
mudsill-for-shore-post/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
mudsill-for-shore-post/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1389
mudsill-for-shore-post/mudsill-for-shore-post.pdf
Normal file
141
mudsill-for-shore-post/mudsill-for-shore-post.typ
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
#import "assets/sheet.typ": calcline, calcsheet, check
|
||||||
|
|
||||||
|
#show: calcsheet.with(
|
||||||
|
title: "Mudsill Analysis and Design",
|
||||||
|
project: "BNC Typical Shoring",
|
||||||
|
prepared-by: "Conemco Engineering",
|
||||||
|
)
|
||||||
|
|
||||||
|
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||||
|
|
||||||
|
= Mudsill Analysis and Design
|
||||||
|
|
||||||
|
Analysis to determine the adequacy of a plywood mudsill supporting a shore post
|
||||||
|
base over compacted soil. The mudsill consists of stacked plywood panels
|
||||||
|
distributing the post load to the ground.
|
||||||
|
|
||||||
|
== Geometry and Loads
|
||||||
|
|
||||||
|
#let P = 3000.0
|
||||||
|
#let Bp = 6.0
|
||||||
|
#let Hp = 6.0
|
||||||
|
#let B = 18.0
|
||||||
|
#let H = 18.0
|
||||||
|
#let t = 0.75
|
||||||
|
#let N = 3
|
||||||
|
|
||||||
|
#calcline([$P = #P " lbf"$], [Post axial load on mudsill])
|
||||||
|
#calcline([$B_p = #Bp " in"$], [Post base width])
|
||||||
|
#calcline([$H_p = #Hp " in"$], [Post base length])
|
||||||
|
#calcline([$B = #B " in"$], [Mudsill panel width])
|
||||||
|
#calcline([$H = #H " in"$], [Mudsill panel length])
|
||||||
|
#calcline([$t = #t " in"$], [Plywood thickness])
|
||||||
|
#calcline([$N = #N$], [Number of plywood panels])
|
||||||
|
|
||||||
|
== Plywood Bearing Under Post Base
|
||||||
|
|
||||||
|
#let Ap = Bp * Hp
|
||||||
|
#let fbrg_ply = P / Ap
|
||||||
|
#let Fabrg = 360.0
|
||||||
|
|
||||||
|
#calcline([$A_p = B_p H_p = #round(Ap) " in"^2$], [Post base contact area])
|
||||||
|
#calcline(
|
||||||
|
[$f_"brg" = P / A_p = #round(fbrg_ply, digits: 3) " psi"$],
|
||||||
|
[Bearing stress in plywood],
|
||||||
|
)
|
||||||
|
#calcline([$F_"abrg" = #Fabrg " psi"$], [Allowable plywood bearing (D510 ch 4.4.7)])
|
||||||
|
|
||||||
|
#v(8pt)
|
||||||
|
#check(
|
||||||
|
"Plywood bearing under post base",
|
||||||
|
fbrg_ply,
|
||||||
|
Fabrg,
|
||||||
|
unit: "psi",
|
||||||
|
demand-label: [$f_"brg"$],
|
||||||
|
capacity-label: [$F_"abrg"$],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Soil Bearing
|
||||||
|
|
||||||
|
#let Abrg = (B * H) / 144.0
|
||||||
|
#let fbrg_soil = P / Abrg
|
||||||
|
#let Fbrg = 2000.0
|
||||||
|
|
||||||
|
#calcline([$A_"brg" = (B H) / 144 = #round(Abrg, digits: 3) " ft"^2$], [Mudsill bearing area on soil])
|
||||||
|
#calcline(
|
||||||
|
[$f_"brg" = P / A_"brg" = #round(fbrg_soil, digits: 3) " psf"$],
|
||||||
|
[Soil bearing pressure],
|
||||||
|
)
|
||||||
|
#calcline([$F_"brg" = #Fbrg " psf"$], [Allowable soil bearing pressure])
|
||||||
|
|
||||||
|
#v(8pt)
|
||||||
|
#check(
|
||||||
|
"Soil bearing pressure",
|
||||||
|
fbrg_soil,
|
||||||
|
Fbrg,
|
||||||
|
unit: "psf",
|
||||||
|
demand-label: [$f_"brg"$],
|
||||||
|
capacity-label: [$F_"brg"$],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Plywood Bending
|
||||||
|
|
||||||
|
The soil pressure acting on the panel produces a lineal load on the plywood
|
||||||
|
spanning between the post base edge and the panel edge. A 0.6 reduction factor
|
||||||
|
is applied to the soil pressure to account for partial loading at the cantilever.
|
||||||
|
|
||||||
|
#let B_ft = B / 12.0
|
||||||
|
#let w = 0.6 * fbrg_soil * B_ft
|
||||||
|
#let a = (H - Hp) / 2.0
|
||||||
|
#let a_ft = a / 12.0
|
||||||
|
#let M = w * a_ft * a_ft / 2.0
|
||||||
|
#let Sp = N * B * t * t / 6.0
|
||||||
|
#let fb = (M * 12.0) / Sp
|
||||||
|
#let FbS = 405.0
|
||||||
|
#let S = 1.125
|
||||||
|
#let Fb = FbS / S
|
||||||
|
|
||||||
|
#calcline([$w = 0.6 f_"brg" B = #round(w) " plf"$], [Lineal load on plywood])
|
||||||
|
#calcline([$a = (H - H_p) / 2 = #round(a) " in"$], [Cantilever length])
|
||||||
|
#calcline([$M = w a^2 / 2 = #round(M) " lbf" dot "ft"$], [Maximum bending moment])
|
||||||
|
#calcline([$S_p = N B t^2 / 6 = #round(Sp, digits: 3) " in"^3$], [Section modulus of plywood])
|
||||||
|
#calcline(
|
||||||
|
[$f_b = M / S_p = #round(fb, digits: 3) " psi"$],
|
||||||
|
[Bending stress in plywood],
|
||||||
|
)
|
||||||
|
#calcline([$F_b = F_"bS" / S = #round(Fb) " psi"$], [Allowable bending stress])
|
||||||
|
|
||||||
|
#v(8pt)
|
||||||
|
#check(
|
||||||
|
"Plywood bending stress",
|
||||||
|
fb,
|
||||||
|
Fb,
|
||||||
|
unit: "psi",
|
||||||
|
demand-label: [$f_b$],
|
||||||
|
capacity-label: [$F_b$],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Plywood Shear
|
||||||
|
|
||||||
|
#let V = w * a_ft
|
||||||
|
#let Av = N * B * t
|
||||||
|
#let fv = 1.5 * V / Av
|
||||||
|
#let Fv = 90.0
|
||||||
|
|
||||||
|
#calcline([$V = w a = #round(V) " lbf"$], [Maximum shear force])
|
||||||
|
#calcline([$A_v = N B t = #round(Av, digits: 3) " in"^2$], [Shear area of plywood])
|
||||||
|
#calcline(
|
||||||
|
[$f_v = 1.5 V / A_v = #round(fv, digits: 3) " psi"$],
|
||||||
|
[Shear stress in plywood],
|
||||||
|
)
|
||||||
|
#calcline([$F_v = #Fv " psi"$], [Allowable shear stress])
|
||||||
|
|
||||||
|
#v(8pt)
|
||||||
|
#check(
|
||||||
|
"Plywood shear stress",
|
||||||
|
fv,
|
||||||
|
Fv,
|
||||||
|
unit: "psi",
|
||||||
|
demand-label: [$f_v$],
|
||||||
|
capacity-label: [$F_v$],
|
||||||
|
)
|
||||||
BIN
shore-post-brace/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
shore-post-brace/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BIN
shore-post/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
shore-post/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BIN
shore-post/assets/tributary-areas.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
1038
shore-post/shore-post.pdf
Normal file
68
shore-post/shore-post.typ
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#import "assets/sheet.typ": calcline, calcsheet, check
|
||||||
|
|
||||||
|
#show: calcsheet.with(
|
||||||
|
title: "Shore Post Analysis and Design",
|
||||||
|
project: "Deer Creek Shoring",
|
||||||
|
prepared-by: "Conemco Engineering",
|
||||||
|
)
|
||||||
|
|
||||||
|
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||||
|
|
||||||
|
= Shore Post Analysis and Design
|
||||||
|
|
||||||
|
Analysis to determine the axial demand for heavy duty shore post supporting the building's roof adjacent to the masonry walls to be repaired.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
image("assets/tributary-areas.png", width: 69%),
|
||||||
|
caption: [Plan tributary area for highest loaded shore post pair.],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Tributary Area
|
||||||
|
|
||||||
|
#let Bx = 6.25
|
||||||
|
#let By = 16
|
||||||
|
#let At = Bx * By
|
||||||
|
|
||||||
|
#calcline([$S_x = #Bx " ft"$], [Tributary width in x (Spacing in X)])
|
||||||
|
#calcline([$S_y = #By " ft"$], [Tributary width in y (Spacing in Y)])
|
||||||
|
#calcline(
|
||||||
|
[$A_t = S_x S_y = #round(At) " ft"^2$],
|
||||||
|
[Plan tributary area],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Load Determination
|
||||||
|
|
||||||
|
#let DL = 40.0
|
||||||
|
#let SDL = 15.0
|
||||||
|
#let LL = 20.0
|
||||||
|
|
||||||
|
#let P = ((DL + SDL + LL) * At) / 2
|
||||||
|
|
||||||
|
#calcline([$D = #DL " psf"$], [Dead load])
|
||||||
|
#calcline([$D_("s") = #SDL " psf"$], [Superimposed dead load])
|
||||||
|
#calcline([$L_L = #LL " psf"$], [Live load])
|
||||||
|
|
||||||
|
#calcline(
|
||||||
|
[$P_max = display((( D + D_("s") + L_L) A_t ) / 2) = #round(P) " lbf"$],
|
||||||
|
[Maximum service load on one post],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Shore Post Axial Capacity
|
||||||
|
|
||||||
|
Considering AS550 Heavy Duty Shore post
|
||||||
|
|
||||||
|
#let H = 14
|
||||||
|
#let Pcap = 6400
|
||||||
|
|
||||||
|
#calcline([$H = #H " ft"$], [Shore post height])
|
||||||
|
#calcline([$P_("cap") = #Pcap " lbf"$], [AS550 Shore post capacity as per tech report])
|
||||||
|
|
||||||
|
#v(8pt)
|
||||||
|
#check(
|
||||||
|
"Shore post axial capacity",
|
||||||
|
P,
|
||||||
|
Pcap,
|
||||||
|
unit: "lbf",
|
||||||
|
demand-label: [$P_max$],
|
||||||
|
capacity-label: [$P_"cap"$],
|
||||||
|
)
|
||||||
BIN
slab-on-grade/SLAB-ON-GRADE-MESH-REINFORCEMENT.pdf
Executable file
BIN
slab-on-grade/assets/logo.png
Executable file
|
After Width: | Height: | Size: 99 KiB |
82
slab-on-grade/assets/sheet.typ
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#let navy = rgb("#1a3a5f")
|
||||||
|
#let muted = rgb("#626b73")
|
||||||
|
#let pass = rgb("#1f6b45")
|
||||||
|
#let fail = rgb("#9b2c2c")
|
||||||
|
|
||||||
|
#let calcsheet(
|
||||||
|
title: "Structural Calculation",
|
||||||
|
project: "",
|
||||||
|
prepared-by: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set document(title: title, author: prepared-by)
|
||||||
|
set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
margin: (x: 1in, top: 1.25in, bottom: 1in),
|
||||||
|
header: context {
|
||||||
|
grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
align: (left, right),
|
||||||
|
image("../assets/logo.png", height: 30pt),
|
||||||
|
[#text(size: 9pt)[Project:] \
|
||||||
|
#text(size: 10pt, weight: "bold")[#project]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
footer: context {
|
||||||
|
set text(size: 8.5pt, fill: muted)
|
||||||
|
stack(
|
||||||
|
spacing: 4pt,
|
||||||
|
line(length: 100%, stroke: 0.5pt + muted),
|
||||||
|
[#prepared-by],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
set text(font: "Libertinus Serif", size: 10pt, lang: "en")
|
||||||
|
set par(justify: true)
|
||||||
|
set heading(numbering: none)
|
||||||
|
show heading.where(level: 1): set text(size: 14pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set text(size: 11pt, weight: "bold", fill: black)
|
||||||
|
show heading.where(level: 2): set block(above: 2em, below: 1em)
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let calcline(formula, note) = grid(
|
||||||
|
columns: (1.7fr, 1fr),
|
||||||
|
gutter: 4pt,
|
||||||
|
align: (left, left),
|
||||||
|
formula, text(size: 9pt, fill: muted, note),
|
||||||
|
)
|
||||||
|
|
||||||
|
#let check(label, demand, capacity, unit: "", ok: auto, demand-label: "Demand", capacity-label: "Capacity") = {
|
||||||
|
let utilization = demand / capacity
|
||||||
|
let passes = if ok == auto { utilization <= 1 } else { ok }
|
||||||
|
let color = if passes { pass } else { fail }
|
||||||
|
block(
|
||||||
|
breakable: false,
|
||||||
|
width: 100%,
|
||||||
|
stroke: 0.8pt + black,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 2pt,
|
||||||
|
)[
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[#text(weight: "bold")[#label]],
|
||||||
|
box(stroke: 0.8pt + color, inset: (x: 6pt, y: 2pt))[
|
||||||
|
#text(weight: "bold", fill: color)[#if passes { "OK" } else { "NOT OK" }]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
#v(4pt)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, auto),
|
||||||
|
[
|
||||||
|
#demand-label: #calc.round(demand, digits: 2) #unit #h(14pt)
|
||||||
|
#capacity-label: #calc.round(capacity, digits: 2) #unit
|
||||||
|
],
|
||||||
|
[
|
||||||
|
D/C: #calc.round(utilization, digits: 2)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
995
slab-on-grade/slab-on-grade.pdf
Normal file
|
|
@ -0,0 +1,995 @@
|
||||||
|
%PDF-1.7
|
||||||
|
%€€€€
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<</Type/Pages/Count 1/Kids[112 0 R]>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<</Parent 6 0 R/Next 3 0 R/Title(Input Data)/Dest 97 0 R>>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<</Parent 6 0 R/Next 4 0 R/Prev 2 0 R/Title(Acting Load)/Dest 98 0 R>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<</Parent 6 0 R/Next 5 0 R/Prev 3 0 R/Title(Required Steel Reinforcement)/Dest 99 0 R>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<</Parent 6 0 R/Prev 4 0 R/Title(Provided Steel Reinforcement)/Dest 100 0 R>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<</Parent 7 0 R/First 2 0 R/Last 5 0 R/Count -4/Title(Slab-On-Grade Design)/Dest 101 0 R>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<</Type/Outlines/First 6 0 R/Last 6 0 R/Count 1>>
|
||||||
|
endobj
|
||||||
|
8 0 obj
|
||||||
|
<</Type/StructTreeRoot/RoleMap<</Datetime/Span/Terms/Part/Title/P/Strong/Span/Em/Span>>/K[94 0 R]/ParentTree<</Nums[0 9 0 R]>>/ParentTreeNextKey 1>>
|
||||||
|
endobj
|
||||||
|
9 0 obj
|
||||||
|
[10 0 R 11 0 R 12 0 R 13 0 R 13 0 R 13 0 R 13 0 R 15 0 R 18 0 R 18 0 R 18 0 R 18 0 R 18 0 R 20 0 R 23 0 R 23 0 R 23 0 R 23 0 R 23 0 R 23 0 R 23 0 R 23 0 R 23 0 R 25 0 R 28 0 R 28 0 R 28 0 R 28 0 R 28 0 R 28 0 R 28 0 R 30 0 R 33 0 R 33 0 R 33 0 R 33 0 R 33 0 R 33 0 R 33 0 R 33 0 R 33 0 R 35 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 38 0 R 40 0 R 43 0 R 43 0 R 43 0 R 43 0 R 43 0 R 43 0 R 45 0 R 48 0 R 48 0 R 48 0 R 48 0 R 48 0 R 48 0 R 48 0 R 50 0 R 53 0 R 53 0 R 53 0 R 53 0 R 53 0 R 55 0 R 58 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 59 0 R 61 0 R 64 0 R 65 0 R 65 0 R 65 0 R 65 0 R 65 0 R 67 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 70 0 R 72 0 R 72 0 R 75 0 R 76 0 R 76 0 R 76 0 R 76 0 R 76 0 R 76 0 R 76 0 R 76 0 R 76 0 R 78 0 R 78 0 R 81 0 R 83 0 R 86 0 R 86 0 R 86 0 R 86 0 R 87 0 R 88 0 R 88 0 R 89 0 R 91 0 R]
|
||||||
|
endobj
|
||||||
|
10 0 obj
|
||||||
|
<</Type/StructElem/S/H1/P 94 0 R/T(Slab-On-Grade Design)/K[0]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
11 0 obj
|
||||||
|
<</Type/StructElem/S/P/P 94 0 R/K[1]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
12 0 obj
|
||||||
|
<</Type/StructElem/S/H2/P 94 0 R/T(Input Data)/K[2]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
13 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 14 0 R/A[<</O/Layout/Placement/Block>>]/K[3 4 5 6]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
14 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 17 0 R/K[13 0 R]>>
|
||||||
|
endobj
|
||||||
|
15 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 16 0 R/A[<</O/Layout/Placement/Block>>]/K[7]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
16 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 17 0 R/K[15 0 R]>>
|
||||||
|
endobj
|
||||||
|
17 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[14 0 R 16 0 R]>>
|
||||||
|
endobj
|
||||||
|
18 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 19 0 R/A[<</O/Layout/Placement/Block>>]/K[8 9 10 11 12]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
19 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 22 0 R/K[18 0 R]>>
|
||||||
|
endobj
|
||||||
|
20 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 21 0 R/A[<</O/Layout/Placement/Block>>]/K[13]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
21 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 22 0 R/K[20 0 R]>>
|
||||||
|
endobj
|
||||||
|
22 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[19 0 R 21 0 R]>>
|
||||||
|
endobj
|
||||||
|
23 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 24 0 R/A[<</O/Layout/Placement/Block>>]/K[14 15 16 17 18 19 20 21 22]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
24 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 27 0 R/K[23 0 R]>>
|
||||||
|
endobj
|
||||||
|
25 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 26 0 R/A[<</O/Layout/Placement/Block>>]/K[23]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
26 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 27 0 R/K[25 0 R]>>
|
||||||
|
endobj
|
||||||
|
27 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[24 0 R 26 0 R]>>
|
||||||
|
endobj
|
||||||
|
28 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 29 0 R/A[<</O/Layout/Placement/Block>>]/K[24 25 26 27 28 29 30]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
29 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 32 0 R/K[28 0 R]>>
|
||||||
|
endobj
|
||||||
|
30 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 31 0 R/A[<</O/Layout/Placement/Block>>]/K[31]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
31 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 32 0 R/K[30 0 R]>>
|
||||||
|
endobj
|
||||||
|
32 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[29 0 R 31 0 R]>>
|
||||||
|
endobj
|
||||||
|
33 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 34 0 R/A[<</O/Layout/Placement/Block>>]/K[32 33 34 35 36 37 38 39 40]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
34 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 37 0 R/K[33 0 R]>>
|
||||||
|
endobj
|
||||||
|
35 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 36 0 R/A[<</O/Layout/Placement/Block>>]/K[41]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
36 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 37 0 R/K[35 0 R]>>
|
||||||
|
endobj
|
||||||
|
37 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[34 0 R 36 0 R]>>
|
||||||
|
endobj
|
||||||
|
38 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 39 0 R/A[<</O/Layout/Placement/Block>>]/K[42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
39 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 42 0 R/K[38 0 R]>>
|
||||||
|
endobj
|
||||||
|
40 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 41 0 R/A[<</O/Layout/Placement/Block>>]/K[59]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
41 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 42 0 R/K[40 0 R]>>
|
||||||
|
endobj
|
||||||
|
42 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[39 0 R 41 0 R]>>
|
||||||
|
endobj
|
||||||
|
43 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 44 0 R/A[<</O/Layout/Placement/Block>>]/K[60 61 62 63 64 65]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
44 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 47 0 R/K[43 0 R]>>
|
||||||
|
endobj
|
||||||
|
45 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 46 0 R/A[<</O/Layout/Placement/Block>>]/K[66]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
46 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 47 0 R/K[45 0 R]>>
|
||||||
|
endobj
|
||||||
|
47 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[44 0 R 46 0 R]>>
|
||||||
|
endobj
|
||||||
|
48 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 49 0 R/A[<</O/Layout/Placement/Block>>]/K[67 68 69 70 71 72 73]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
49 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 52 0 R/K[48 0 R]>>
|
||||||
|
endobj
|
||||||
|
50 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 51 0 R/A[<</O/Layout/Placement/Block>>]/K[74]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
51 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 52 0 R/K[50 0 R]>>
|
||||||
|
endobj
|
||||||
|
52 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[49 0 R 51 0 R]>>
|
||||||
|
endobj
|
||||||
|
53 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 54 0 R/A[<</O/Layout/Placement/Block>>]/K[75 76 77 78 79]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
54 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 57 0 R/K[53 0 R]>>
|
||||||
|
endobj
|
||||||
|
55 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 56 0 R/A[<</O/Layout/Placement/Block>>]/K[80]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
56 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 57 0 R/K[55 0 R]>>
|
||||||
|
endobj
|
||||||
|
57 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[54 0 R 56 0 R]>>
|
||||||
|
endobj
|
||||||
|
58 0 obj
|
||||||
|
<</Type/StructElem/S/H2/P 94 0 R/T(Acting Load)/K[81]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
59 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 60 0 R/A[<</O/Layout/Placement/Block>>]/K[82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
60 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 63 0 R/K[59 0 R]>>
|
||||||
|
endobj
|
||||||
|
61 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 62 0 R/A[<</O/Layout/Placement/Block>>]/K[101]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
62 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 63 0 R/K[61 0 R]>>
|
||||||
|
endobj
|
||||||
|
63 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[60 0 R 62 0 R]>>
|
||||||
|
endobj
|
||||||
|
64 0 obj
|
||||||
|
<</Type/StructElem/S/H2/P 94 0 R/T(Required Steel Reinforcement)/K[102]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
65 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 66 0 R/A[<</O/Layout/Placement/Block>>]/K[103 104 105 106 107]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
66 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 69 0 R/K[65 0 R]>>
|
||||||
|
endobj
|
||||||
|
67 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 68 0 R/A[<</O/Layout/Placement/Block>>]/K[108]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
68 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 69 0 R/K[67 0 R]>>
|
||||||
|
endobj
|
||||||
|
69 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[66 0 R 68 0 R]>>
|
||||||
|
endobj
|
||||||
|
70 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 71 0 R/A[<</O/Layout/Placement/Block>>]/K[109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
71 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 74 0 R/K[70 0 R]>>
|
||||||
|
endobj
|
||||||
|
72 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 73 0 R/A[<</O/Layout/Placement/Block>>]/K[128 129]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
73 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 74 0 R/K[72 0 R]>>
|
||||||
|
endobj
|
||||||
|
74 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[71 0 R 73 0 R]>>
|
||||||
|
endobj
|
||||||
|
75 0 obj
|
||||||
|
<</Type/StructElem/S/H2/P 94 0 R/T(Provided Steel Reinforcement)/K[130]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
76 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 77 0 R/A[<</O/Layout/Placement/Block>>]/K[131 132 133 134 135 136 137 138 139]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
77 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 80 0 R/K[76 0 R]>>
|
||||||
|
endobj
|
||||||
|
78 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 79 0 R/A[<</O/Layout/Placement/Block>>]/K[140 141]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
79 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 80 0 R/K[78 0 R]>>
|
||||||
|
endobj
|
||||||
|
80 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[77 0 R 79 0 R]>>
|
||||||
|
endobj
|
||||||
|
81 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 82 0 R/A[<</O/Layout/Placement/Block>>]/K[142]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
82 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 85 0 R/K[81 0 R]>>
|
||||||
|
endobj
|
||||||
|
83 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 84 0 R/A[<</O/Layout/Placement/Block>>]/K[143]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
84 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 85 0 R/K[83 0 R]>>
|
||||||
|
endobj
|
||||||
|
85 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[82 0 R 84 0 R]>>
|
||||||
|
endobj
|
||||||
|
86 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 90 0 R/A[<</O/Layout/Placement/Block>>]/K[144 145 146 147]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
87 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 90 0 R/A[<</O/Layout/Placement/Block>>]/K[148]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
88 0 obj
|
||||||
|
<</Type/StructElem/S/Formula/P 90 0 R/A[<</O/Layout/Placement/Block>>]/K[149 150]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
89 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 90 0 R/A[<</O/Layout/Placement/Block>>]/K[151]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
90 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 93 0 R/K[86 0 R 87 0 R 88 0 R 89 0 R]>>
|
||||||
|
endobj
|
||||||
|
91 0 obj
|
||||||
|
<</Type/StructElem/S/Span/P 92 0 R/A[<</O/Layout/Placement/Block>>]/K[152]/Pg 112 0 R>>
|
||||||
|
endobj
|
||||||
|
92 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 93 0 R/K[91 0 R]>>
|
||||||
|
endobj
|
||||||
|
93 0 obj
|
||||||
|
<</Type/StructElem/S/Div/P 94 0 R/K[90 0 R 92 0 R]>>
|
||||||
|
endobj
|
||||||
|
94 0 obj
|
||||||
|
<</Type/StructElem/S/Document/P 8 0 R/K[10 0 R 11 0 R 12 0 R 17 0 R 22 0 R 27 0 R 32 0 R 37 0 R 42 0 R 47 0 R 52 0 R 57 0 R 58 0 R 63 0 R 64 0 R 69 0 R 74 0 R 75 0 R 80 0 R 85 0 R 93 0 R]>>
|
||||||
|
endobj
|
||||||
|
95 0 obj
|
||||||
|
[/ICCBased 123 0 R]
|
||||||
|
endobj
|
||||||
|
96 0 obj
|
||||||
|
[/ICCBased 124 0 R]
|
||||||
|
endobj
|
||||||
|
97 0 obj
|
||||||
|
[112 0 R/XYZ 72 666.89 0]
|
||||||
|
endobj
|
||||||
|
98 0 obj
|
||||||
|
[112 0 R/XYZ 72 469.325 0]
|
||||||
|
endobj
|
||||||
|
99 0 obj
|
||||||
|
[112 0 R/XYZ 72 422.4 0]
|
||||||
|
endobj
|
||||||
|
100 0 obj
|
||||||
|
[112 0 R/XYZ 72 345.781 0]
|
||||||
|
endobj
|
||||||
|
101 0 obj
|
||||||
|
[112 0 R/XYZ 72 712 0]
|
||||||
|
endobj
|
||||||
|
102 0 obj
|
||||||
|
<</ProcSet[/PDF/Text/ImageC/ImageB]/ColorSpace<</c0 96 0 R/c1 95 0 R>>/XObject<</x0 126 0 R>>/Font<</f0 103 0 R/f1 106 0 R/f2 109 0 R>>>>
|
||||||
|
endobj
|
||||||
|
103 0 obj
|
||||||
|
<</Type/Font/Subtype/Type0/BaseFont/MHIXIW+LibertinusSerif-Regular-Identity-H/Encoding/Identity-H/DescendantFonts[104 0 R]/ToUnicode 114 0 R>>
|
||||||
|
endobj
|
||||||
|
104 0 obj
|
||||||
|
<</Type/Font/Subtype/CIDFontType0/BaseFont/MHIXIW+LibertinusSerif-Regular/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>/FontDescriptor 105 0 R/DW 0/W[0 0 500 1 1 541 2 2 372 3 3 504 4 4 272 5 5 447 6 6 428 7 7 316 8 8 236 9 9 646 10 10 542 11 11 390 12 12 271 13 13 505.99997 14 14 500 15 15 250 16 16 465 17 17 381 18 18 538 19 19 512 20 20 220 21 22 465 23 23 519 24 24 264 25 25 457 26 26 493 27 27 747 28 28 465 29 29 490 30 30 220 31 32 465 33 33 485 34 34 531 35 35 485 36 36 839 37 37 515 38 38 951 39 39 701 40 40 528 41 41 497 42 42 310 43 43 587 44 44 503.00003 45 46 465 47 47 307 48 48 323 49 49 596 50 50 465 51 51 790 52 52 557]>>
|
||||||
|
endobj
|
||||||
|
105 0 obj
|
||||||
|
<</Type/FontDescriptor/FontName/MHIXIW+LibertinusSerif-Regular/Flags 131078/FontBBox[-48 -238 947 698]/ItalicAngle 0/Ascent 894/Descent -246/CapHeight 658/StemV 95.4/CIDSet 113 0 R/FontFile3 115 0 R>>
|
||||||
|
endobj
|
||||||
|
106 0 obj
|
||||||
|
<</Type/Font/Subtype/Type0/BaseFont/LXXYEH+LibertinusSerif-Bold-Identity-H/Encoding/Identity-H/DescendantFonts[107 0 R]/ToUnicode 117 0 R>>
|
||||||
|
endobj
|
||||||
|
107 0 obj
|
||||||
|
<</Type/Font/Subtype/CIDFontType0/BaseFont/LXXYEH+LibertinusSerif-Bold/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>/FontDescriptor 108 0 R/DW 0/W[0 0 500 1 1 614 2 2 322 3 3 616 4 4 489 5 5 456 6 6 428 7 7 427 8 8 358 9 9 250 10 10 716 11 11 561 12 12 504 13 13 325 14 14 505.99997 15 15 542 16 16 358 17 17 730 18 18 732 19 19 734 20 20 521 21 21 367 22 22 581 23 23 598 24 24 740 25 25 577 26 26 551 27 27 573 28 28 391 29 29 905 30 30 529 31 31 736]>>
|
||||||
|
endobj
|
||||||
|
108 0 obj
|
||||||
|
<</Type/FontDescriptor/FontName/LXXYEH+LibertinusSerif-Bold/Flags 131078/FontBBox[0 -238 893 700]/ItalicAngle 0/Ascent 894/Descent -246/CapHeight 645/StemV 168.6/CIDSet 116 0 R/FontFile3 118 0 R>>
|
||||||
|
endobj
|
||||||
|
109 0 obj
|
||||||
|
<</Type/Font/Subtype/Type0/BaseFont/LSWPYR+NewCMMath-Book-Identity-H/Encoding/Identity-H/DescendantFonts[110 0 R]/ToUnicode 120 0 R>>
|
||||||
|
endobj
|
||||||
|
110 0 obj
|
||||||
|
<</Type/Font/Subtype/CIDFontType0/BaseFont/LSWPYR+NewCMMath-Book/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>/FontDescriptor 111 0 R/DW 0/W[0 0 500 1 1 361 2 2 778 3 3 500 4 4 332 5 5 278 6 6 556 7 7 681 8 8 500 9 9 306 10 10 389 11 11 490 12 12 407 13 13 433 14 15 500 16 16 556 17 17 394 18 18 622 19 19 510.99997 20 20 444 21 21 579 22 22 500 23 23 539 24 24 389 25 26 500 27 27 389 28 28 500 29 29 828 30 30 784 31 31 429 32 32 716 33 33 278 34 34 778 35 35 500 36 36 278 37 37 643 38 38 750 39 39 339 40 40 392 41 41 444 42 42 528 43 43 359]>>
|
||||||
|
endobj
|
||||||
|
111 0 obj
|
||||||
|
<</Type/FontDescriptor/FontName/LSWPYR+NewCMMath-Book/Flags 131076/FontBBox[13 -250 804 750]/ItalicAngle 0/Ascent 806/Descent -194/CapHeight 683/StemV 95.4/CIDSet 119 0 R/FontFile3 121 0 R>>
|
||||||
|
endobj
|
||||||
|
112 0 obj
|
||||||
|
<</Type/Page/Resources 102 0 R/MediaBox[0 0 612 792]/StructParents 0/Parent 1 0 R/Contents 122 0 R>>
|
||||||
|
endobj
|
||||||
|
113 0 obj
|
||||||
|
<</Length 13/Filter/FlateDecode>>
|
||||||
|
stream
|
||||||
|
xœûÿ~ | ||||||