Collection of engineering calculation projects (Python + Typst), each with input, calc script, tests, results, and generated PDF where available.
10 KiB
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 viatypst compile --root ..calcs/concentric-footing/test_concentric_footing.py— append two Typst query tests (do not rewrite existing tests); creategenerated/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/linedrawing (no external image needed). Example: centered square footing with column on top, labelBfandDf. 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 ascalc-linebelow. -
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-linerows: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) psffor context but do not recompute Af.
-
Section
== Geometry and Materials:calc-linefor 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 showquas 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)— runscalc.pywithsubprocess.run([sys.executable, str(HERE/"calc.py")], check=True, cwd=HERE.parents[1]), thentypst compile ...check pdf exists, thentypst queryfor<concentric-footing-results>viatypst eval "query(<concentric-footing-results>)"? Actually usetypst evalortypst querycommand as in wood-joist:subprocess.run(["typst","query", ...])? Wood-joist usestypst compileandtypst 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 assertmeta["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 assertsderived Ps_kip approx values["Ps_kip"]within 0.1 kip (allow rounding of column weight 18.36 vs 18.4) and same forPu_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
footing.typimports onlylib/sheet.typandresults.json; no arithmetic beyond load derivation and rounding.- Every
checkbox reflectschecks.*.okfrom JSON; D/C printed as demand/capacity. typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdfsucceeds and PDF exists.- Two appended pytest tests pass, reconciling Typst-derived Ps/Pu to Python-checked Ps/Pu and Typst-presented utils to Python values.
- All prior 10 tests still pass (total 12).
- 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.