52d94303bc
ci / test (push) Has been cancelled
Public demo snapshot of an in-house pyRevit extension for precast concrete shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel handling solver (PCI Chapter 8). - No client, job, plant or product data: knowledge layers ship as structural templates only. - Ticket QC's model-read escalation targets a LAN model server endpoint (configurable), not a cloud provider. - Built from the private development repo via tools/make_dist.py.
437 lines
25 KiB
Markdown
437 lines
25 KiB
Markdown
# panellift — plan
|
||
|
||
PCI Chapter 8 handling checks for precast wall panels, fed from the Revit
|
||
model that already holds the geometry.
|
||
|
||
Scope is the "first tier" agreed on 2026-08-18: **stripping and yard handling,
|
||
flat**. Transportation (needs dunnage positions the model does not have) and
|
||
erection (the panel rotating up, load shedding onto the lowest pick point) are
|
||
explicitly out.
|
||
|
||
---
|
||
|
||
## Why the calculation does not run inside Revit
|
||
|
||
The extension runs on IronPython 2.7 on Revit's UI thread. A calculation whose
|
||
output goes to a plant has to be verifiable, and verifiable means a test suite
|
||
that runs in a second without a Revit licence, a model, or a Windows box.
|
||
|
||
So: **Revit writes a JSON file, a plain CPython package reads it.** Same
|
||
out-of-process pattern the rest of the tools use. The package has **no
|
||
dependencies** — every dependency is something that can be missing at the
|
||
moment somebody needs a number.
|
||
|
||
```
|
||
Revit ──liftexport──> <mark>.json ──panellift──> report.html
|
||
(IronPython 2.7) (CPython 3.9+)
|
||
```
|
||
|
||
A consequence worth keeping: a panel can be checked by hand-writing the JSON,
|
||
with no model at all.
|
||
|
||
## Decisions taken
|
||
|
||
| Question | Decision |
|
||
|---|---|
|
||
| Lifter capacity vs handling multiplier | **Not** stacked. PCI Table 8.3.1 note (a): those factors are for flexural design and are not to be applied to lifting-device safety factors. Inserts are checked against unfactored reactions and against **the published rating of the part itself** — safety factors are per product in `knowledge/catalogue.yaml` and an insert with none published is reported unjudged. *This row used to end "with the manufacturer's 4:1 tension / 2.67:1 shear". No manufacturer published that. The sentence was written the day after the constant it justified and it cited nothing; the one datasheet in this repository for the parts it reached (ALP catalog p53, 02/2024) heads its capacity table "Capacity in Concrete 4:1 SWL" across both load columns — 4:1 in shear as well. Corrected 2026-08-31, and recorded rather than quietly deleted, because an invented pedigree on a safety factor is the kind of thing that gets re-derived from the same document twice.* |
|
||
| Sling angle | Engineer's config file, in degrees. Not inferred from the model. |
|
||
| Rolling block vs static lift | Engineer's config file. **Two different mechanics**, not a coefficient — see below. |
|
||
| Load cases | Stripping + yard handling. |
|
||
| Flexural capacity | Shipped 2026-08-31 as ACI 318-19 approximate `fps` (Eq. 20.3.2.3.1), strand only, in `capacity.py`. **The columns were informational for one day.** Owner's ruling 2026-09-01 — *"M/pMn 对我而言>1.0是可以的,我能盖章。你不行,大于1.0你就报fail,我做最后决策。"* — so `M/φMn > 1.0` is a `FLEX-CAP` **FAIL** and `V/φVn > 1.0` a `SHEAR-CAP` FAIL, checked at every station of every stage, with **no development-length exclusion**: the tool may not bless a ratio over 1.0 and the engineer stamps past it. The stress check keeps its own verdict; a station that cannot be judged stays a dash and is neither. Full strain compatibility remains the upgrade: it needs the strand stress-strain curve and mild-steel/WWF input paths that no layer holds yet. `fy` defaults to 60 ksi, `fyt` from the design summary; both overridable in config. |
|
||
|
||
### Rolling block vs static lift
|
||
|
||
- **rolling** — equalising sheaves, every line in a cable group carries the
|
||
same force. Two groups, two equations of statics, determinate. Reactions do
|
||
not depend on stiffness, so a blockout does not move them.
|
||
- **static** — fixed-length slings. The panel is a continuous beam on rigid
|
||
supports; three or more support lines are indeterminate and the reactions
|
||
follow flexural stiffness. A blockout beside a support really does shed load
|
||
off it.
|
||
|
||
Measured on the ASPIRE panel with three support lines at 4 / 16 / 28 ft:
|
||
|
||
```
|
||
rolling 15,167 15,167 15,167 lb
|
||
static 13,508 18,484 13,508 lb middle line +22%
|
||
```
|
||
|
||
Picking the wrong one is a 22% error on an insert. Hence: the engineer says.
|
||
|
||
## The design decision that matters most
|
||
|
||
**PCI Figure 8.3.2's coefficients are a test, not the engine.**
|
||
|
||
Those coefficients only hold when the picks sit where positive and negative
|
||
moment come out equal. The entire reason to check a real ticket is that the
|
||
detailer may not have put them there — so using the closed form as the engine
|
||
would assume the answer.
|
||
|
||
Instead the moment comes from a real beam solve at the *actual* insert
|
||
positions, and Figure 8.3.2 becomes the regression test: put the supports at
|
||
`(sqrt(2)-1)/2` of the span and the solver must return `0.0214 w a b²`.
|
||
|
||
It does. That identity is also *derived* in `pci.py` rather than quoted —
|
||
solving `w(aL)²/2 = wL²(1-2a)²/8 - w(aL)²/2` gives `4a² + 4a - 1 = 0`, hence
|
||
`a = 0.20711` and a coefficient of `0.02145`, which is PCI's 0.0214.
|
||
|
||
## Known idealisation
|
||
|
||
The panel is treated as a beam spanning its long direction. This *is* PCI's
|
||
own method for `My`, and it is what the ASPIRE examples do. It is not valid
|
||
for a near-square panel, where two-way plate action governs — that is
|
||
finite-element territory (PanelsPlus). **The code must refuse rather than
|
||
answer** when the aspect ratio gets close to 1. Not yet implemented.
|
||
|
||
The transverse direction `Mx` will use PCI's closed form with the "resisted by
|
||
15t or b/2" rule, as a secondary check.
|
||
|
||
## Status
|
||
|
||
### Done and tested — 34 tests, all passing
|
||
|
||
| Module | |
|
||
|---|---|
|
||
| `model.py` | `Panel`, `Blockout`, `Lifter`, `Rigging`, `Reinforcement`, `Stage`, `Material` |
|
||
| `section.py` | `b_eff(x)`, `S(x)`, `I(x)`, `w(x)`, blockout merging, weight reconciliation |
|
||
| `pci.py` | Table 8.3.1, `5√f'c`, Figure 8.3.2 closed forms, optimal support fraction |
|
||
| `linalg.py` | dense solver, so there are no dependencies |
|
||
| `statics.py` | insert clustering → support lines → cable groups; rolling and static solvers; V(x), M(x); sling forces |
|
||
|
||
Reproduced from ASPIRE Winter 2019 (Brown), which reprints PCI Table 8.3.1 and
|
||
Figure 8.3.2:
|
||
|
||
| | ours | published |
|
||
|---|---|---|
|
||
| unit weight | 109.4 psf | 109.4 |
|
||
| S solid | 1531 in³ | 1531 |
|
||
| S, five 6"×24" pockets | 1148 in³ | 1148 |
|
||
| unfactored moment | 24,020 lb-ft | 23,973 |
|
||
| stripping stress, solid | 245 psi | 244 — OK vs 316 |
|
||
| transport stress, solid | 282 psi | 282 — OK vs 354 |
|
||
| stripping stress, pocketed | 326 psi | 326 — **no good** vs 316 |
|
||
| transport stress, pocketed | 377 psi | 376 — **no good** vs 354 |
|
||
|
||
The moment differs by 0.2% because the article rounds the coefficient to
|
||
0.0214; we carry `(√2−1)²/8`.
|
||
|
||
### Done since — 76 tests
|
||
|
||
| | |
|
||
|---|---|
|
||
| `checks.py` | cracking, insert capacity with sling amplification, edge-anchor shear and plumb-hang tension, per-stage derating, aspect-ratio refusal, unrated inserts as a warning |
|
||
| `report.py` | self-contained HTML; `build_many` puts every stage of one piece in one file behind a verdict table |
|
||
| `layout.py` | recommended insert positions and the delta from the model |
|
||
| `cli.py` | `panellift job.json -o report.html [--json]` |
|
||
| `vibe/liftexport.py` | the Revit side of the job file, plus the subprocess |
|
||
| `Panel Lift Check` | the button: select an assembly, confirm the picks, choose where the HTML goes |
|
||
|
||
### Next
|
||
|
||
1. Strain-compatibility `φMn` (upgrade of `capacity.py`'s approximate-`fps`
|
||
columns, shipped 2026-08-31): strand power curve, mild steel and WWF layers
|
||
— the input paths for both are still owed before this can be honest.
|
||
2. Transverse equilibrium. `layout.py` reports how far a row's centroid sits
|
||
off the width centreline, but a beam model cannot say what that costs. On a
|
||
panel with large openings the recommended rows can be forced entirely to
|
||
one side, and whether the piece twists is currently the engineer's call
|
||
with no number attached.
|
||
3. Per-piece config overrides (sling angle, lift mode) without editing the
|
||
plant knowledge file.
|
||
|
||
### Answered against the live model (sample_job, 2026-08-18)
|
||
|
||
Probed on `PNL204` — a `PANEL_VERTICAL::08"`, design PNL1.0,
|
||
13'-10" x 4'-0" x 8", 5,533 lb, 51 members.
|
||
|
||
**Panel-local frame.** `x` is the width, `y` is the thickness, `z` is the
|
||
length. On PNL204: `x ∈ [-2.000, 2.000]`, `y ∈ [-0.667, 0.000]`,
|
||
`z ∈ [0, 13.917]` ft. So the form face is `y = -0.667` and the exposed face
|
||
`y = 0`.
|
||
|
||
**1. Blockouts are family instances, not `Opening` elements.** The document
|
||
contains **zero** `Opening` instances. Voids are a family group under Generic
|
||
Models:
|
||
|
||
| family | count |
|
||
| --- | --- |
|
||
| `VOID_RECTANGULAR` | 622 |
|
||
| `VOID_RECESS_3_SIDES` | 234 |
|
||
| `VOID_POUR_STRIP` | 102 |
|
||
| `VOID_DT_DAP` | 54 |
|
||
| `VOID_ROUND` | 46 |
|
||
| `V-PLATE_VOID_RECTANGULAR` | 22 |
|
||
| `VOID_TAPER_ROUND` | 17 |
|
||
|
||
`STEM_BLOCKOUT` (2694) is under Specialty Equipment and belongs to double-tee
|
||
stems, not wall panels. So `b_eff(x)` gets populated the same way the lifters
|
||
do: instance position transformed to panel-local, size from instance
|
||
parameters. Whether these families actually cut the solid, or only mark the
|
||
void for the detailer, still needs checking — it decides whether the geometric
|
||
weight reconciliation will agree.
|
||
|
||
**2. Insert role separates cleanly by geometry.** On PNL204:
|
||
|
||
| family | count | x | y | z |
|
||
| --- | --- | --- | --- | --- |
|
||
| `ANCHOR_FACE_4T` | 4 | ±1.000 | **-0.667** (form face) | 2.583, 11.417 |
|
||
| `LIFT_EDGE_4T` | 2 | ±1.000 | **-0.333** (mid-thickness) | **13.917** (top end) |
|
||
|
||
Face-mounted and inboard → **stripping**. Mid-thickness and at an end →
|
||
**erection**, exactly as suspected of the `LIFT_EDGE_8T` seen at 0'-0".
|
||
Two independent signals agree, family prefix and position, so the rule can
|
||
check itself rather than trusting a name list.
|
||
|
||
The real pick is 4 points on 2 support lines at `z/L` = 0.186 and 0.180,
|
||
against the PCI optimum of 0.207 — a good first live test case.
|
||
|
||
**Capacity is encoded in the family name**: `ANCHOR_FACE_4T` → 4T, `LIFT_EDGE_4T`
|
||
→ 4T, `LIFT_EDGE_8T` → 8T. That makes `knowledge.yaml`'s `capacity_lb`
|
||
table largely derivable. **Open question for the engineer: is `4T` the working
|
||
load limit or the ultimate?** It is a factor of four on every insert check.
|
||
|
||
**3. Strand positions are recoverable, through-thickness included.** Four
|
||
`1-2_STRAND` at `y = -0.500` and `y = -0.167` — two layers, 2 in.
|
||
from each face of the 8 in. panel. That `y` is precisely the depth strain
|
||
compatibility needs. `WWF_MESH_A` sits in the same two layers, confirming
|
||
`per_face` in the spec schema.
|
||
|
||
Unresolved: the four strands sit at only two `x` stations (-1.500 and 0.000)
|
||
on a panel spanning -2.000 to 2.000, which is not symmetric. Either the family
|
||
insertion point is off-centre or `_point_of` is returning a curve start.
|
||
Worth pinning before trusting a lever arm.
|
||
|
||
**4. Panels are boxes, and there are only two thicknesses.** PNL204's local
|
||
bounding box is a clean 4.000 x 0.667 x 13.917. The whole model uses only
|
||
`PANEL_VERTICAL` and `PANEL_HORIZONTAL`, each in `08"` and `12"`.
|
||
|
||
One discrepancy to carry: the bounding box gives 13.917 ft = 13'-11" while
|
||
the panel's stated length parameter says 13'-10". Use the stated dimensions
|
||
for the calculation and report the delta, the same way a ticket's printed
|
||
length runs over the modelled one on stemmed pieces.
|
||
|
||
### Smaller known gaps
|
||
|
||
- Three support lines with equal gaps on both sides make the cable grouping
|
||
ambiguous; currently resolved to the first. Should warn.
|
||
- Partial-depth blockouts are carried but do not reduce `S`.
|
||
- Inclined slings: the horizontal component is reported, but the secondary
|
||
moment it induces is not analysed. LECWall does not either, and says so.
|
||
|
||
---
|
||
|
||
## Insulated (sandwich) panels
|
||
|
||
Added 2026-08-31. Read this before touching `sandwich.py` or the refusal in
|
||
`cli._refuse_sandwich`.
|
||
|
||
### Why it is a refusal first and mechanics second
|
||
|
||
Every section property in this package is built on **one prism** of concrete
|
||
`panel.thickness_in` deep — `section.NetSection`, `section_modulus_in3`, the
|
||
four-corner stress. An insulated body is two concrete wythes with insulation
|
||
between them, and idealising it as solid concrete at the envelope depth
|
||
**overstates the section modulus and understates the tensile stress**. The
|
||
owner, 2026-08-31:
|
||
|
||
> "把夹芯板当实心算并不 conservative。因为 insulation 并不会 100% composite,
|
||
> 你忽视了中间的 composite ratio 的计算和可能出现的问题。这是一个 insulated
|
||
> panel design 的时候非常大的一个问题。"
|
||
|
||
On the dimensions of a real 16 in. piece (4 in. architectural wythe, 4 in.
|
||
EPS, 8 in. structural wythe, 64 in. of clear width) the arithmetic is:
|
||
|
||
| | I, in⁴ | governing S, in³ |
|
||
| --- | --- | --- |
|
||
| solid at the 16 in. envelope | 21,845 | 2,731 |
|
||
| fully composite | 20,139 | 2,324 |
|
||
| non-composite | 3,072 | 768 |
|
||
| structural wythe alone | 2,731 | 683 |
|
||
|
||
So the solid idealisation overstates the governing section modulus by
|
||
**3.56× against the non-composite bound and still 1.18× against the fully
|
||
composite one**. Both above 1.0: there is **no reading of a sandwich under
|
||
which treating it as solid is the safe direction**, because a sandwich has a
|
||
hole in the middle of its depth and is weaker than a solid piece of the same
|
||
envelope however well the connectors work. `test_sandwich.py` keeps every one
|
||
of those numbers with the arithmetic in a comment.
|
||
|
||
**Until 2026-08-31 this never fired.** The weight gate refused every insulated
|
||
piece for an unrelated reason — the solid sum was one wythe — and that refusal
|
||
*accidentally* protected against this. The weight is right now, so the gate
|
||
opens, and it opens onto a wrong section. `cli._refuse_sandwich` is what
|
||
closes that window, and it is deliberately **upstream-independent**: it does
|
||
not care whether the weight gate let the piece through. If a sandwich job file
|
||
reaches this package at all, it fires.
|
||
|
||
### The detection signal travels in the job file
|
||
|
||
`source.solid_parts` — the per-solid breakdown the Revit side has written
|
||
since 2026-08-31. Nothing on this side re-derives it: the file is the
|
||
boundary, and a second opinion about a model this process cannot see would be
|
||
a guess with a different accent. The ladder is in `sandwich.detect` and the
|
||
only rung that may return `False` is *one concrete solid and nothing left
|
||
out*. No signal at all returns **None**, never `False`.
|
||
|
||
### The conventions chosen, and why each is the conservative one
|
||
|
||
Every one of these is stated on the number it touches, in the report and in
|
||
`sandwich.py`, and every one is on the owner-question list below.
|
||
|
||
| decision | what was chosen | why it is the conservative reading |
|
||
| --- | --- | --- |
|
||
| **wythe load sharing, non-composite bound** | the wythes are tied by the connectors so they deflect together; each takes the moment in proportion to its own stiffness, `M_i = M·I_i/ΣI`, so `f = M·(t_max/2)/ΣI` | it is the compatible answer, and the extreme fibre lands on the **thickest** wythe rather than being averaged over both. The alternative — sharing by mass — puts less moment on the stiff wythe than compatibility does |
|
||
| **is the face wythe structural** | reported **both ways**. The non-composite bound assumes both wythes resist; `structural_wythe_alone` assumes the thickest carries the whole piece and the thinner is dead load | the second is the smallest section modulus of the three (683 in³ against 768), so printing it is the conservative half and printing only it would hide the bound the owner may actually want |
|
||
| **which wythe is structural** | the **thickest**, by trade convention | not measured, and it is named as not measured every time it is used. There is no conservative direction available here, only a disclosed one |
|
||
| **openings** | assumed to run through **both** wythes, so `b_eff(x)` is shared | the measured plan areas of the two wythes on the real piece differ by ~1%, which is inside the rounding on the volumes. If they diverge on some piece, each wythe needs its own opening set |
|
||
| **insulation** | contributes **nothing** to the section | its modulus is three orders below concrete's. This is the standard treatment, not an approximation invented here |
|
||
| **modular ratio** | 1, no transformation | both wythes are the same mix on every piece measured. Where the densities differ the report says so rather than quietly scaling one |
|
||
| **connector demand distribution** | the mean over a row, at the fully-composite `VQ/I`, **named as a mean and not as a bound** | it is honest about being an assumption. It is *not* conservative — see below — and saying so is the only available treatment until `k` exists |
|
||
|
||
### The bounds are scaffolding, not the product
|
||
|
||
The owner's instruction, and it reshapes what this is for. The two-bound
|
||
envelope is what can be computed **while the connector data is absent**. The
|
||
partial-composite middle is the thing insulated-panel design is actually
|
||
about, and the capacities and stiffnesses being hunted are the *inputs to
|
||
computing it*, not garnish. The report presents the bounds as an interim
|
||
envelope with the missing computation named.
|
||
|
||
### 可能出现的问题 — what the two bounds do NOT bracket
|
||
|
||
`sandwich.concerns()` is the enumeration, and reading it is the point.
|
||
**Three quantities are bracketed. Everything else about a sandwich panel is
|
||
not a bounds question at all**, and a reader who takes a two-bound report as
|
||
an envelope over all of it is the failure the list exists to prevent.
|
||
|
||
Bracketed:
|
||
|
||
- **wythe flexural tension** — maximal non-composite, monotone in between.
|
||
With one trap: the **inner face of a wythe changes sign** between the
|
||
bounds (compression non-composite, tension fully composite), so both faces
|
||
of both wythes have to be evaluated, not only the envelope faces.
|
||
- **interface shear flow** — maximal fully composite, `q = VQ/I`, zero
|
||
non-composite. A true upper bound on the **total** interface force, which
|
||
is why it is computed even where the plant designs the panel non-composite.
|
||
- **deflection** — maximal non-composite. Differential bowing from
|
||
temperature and shrinkage is a service problem, not a handling one, and is
|
||
**declared out of scope** rather than silently omitted.
|
||
|
||
Not bracketed:
|
||
|
||
- **connector force distribution along the span.** Both bounds are
|
||
distribution-free — one puts zero on every connector, the other spreads
|
||
demand smoothly as `V(x)`. Real partial interaction concentrates slip, and
|
||
therefore connector force, **at the ends of each shear span**, decaying
|
||
over a length set by the connector stiffness `k` against the wythes' axial
|
||
stiffness `EA`. A uniform-demand assumption **understates the end
|
||
connectors**. When `k` arrives, the standard partial-interaction treatment
|
||
is what goes in here; until then the mean is reported and named as a mean.
|
||
- **connector force at discontinuities** — an opening edge or a pick point
|
||
restarts the slip profile and the connectors beside it see a local peak
|
||
neither bound shows.
|
||
- **connector NORMAL (tension) demand.** A flat pick hangs one wythe off the
|
||
other through the connectors in direct tension; an erection pick peels the
|
||
face wythe as the piece rotates. Not an interface-shear question at all, so
|
||
**neither bound speaks to it** — and where the panel is designed
|
||
non-composite it is likely the *governing* connector demand. This is why a
|
||
capacity is needed **per failure mode**, not as one number.
|
||
- **local flexure of the face wythe between connectors** — a slab strip on
|
||
discrete supports at the connector spacing. A different mechanism from
|
||
anything in this package.
|
||
- **percent composite is not one number.** The **stiffness** ratio
|
||
(`κ_I = (I_eff − I_nc)/(I_full − I_nc)`, what governs deflection and
|
||
bowing) and the **strength** ratio (`η`, the degree of shear connection,
|
||
what governs capacity) are different quantities in the trade's own methods.
|
||
Any percentage this report prints says which one it is.
|
||
|
||
### What stands down, and what unblocks it
|
||
|
||
| stood down | why | unblocked by |
|
||
| --- | --- | --- |
|
||
| **composite ratio** (both of them) | a computed ratio without connector data is not hard, it is *impossible*. The width of the band is printed; the position in it is not | connector slip stiffness `k`, lb/in per connector (stiffness ratio); connector capacity per failure mode (strength ratio) |
|
||
| **connector sufficiency** | no capacity is published in any layer. `verdict` stays `None` — never OK, never a pass | capacity **per failure mode**: interface shear **and** normal/tension, lb per connector |
|
||
| **connector recognition** on the gather side | nothing reads the connector instances off the piece. `source.connectors` is written with every field null and the reason beside it, because a *missing* key reads downstream as "this piece has no connectors" | a recognition rule. Measured by the bridge holder, and it is plant knowledge so the parameter names live in the plant layer: identity is a **type-level** parameter, **not** the family name, and a second type-level parameter is the gate. Family-name matching silently finds nothing here — a decoy family with no instances carries the same identity string |
|
||
| **the layup on an old job file** | `solid_parts` without `z0_in`/`z1_in` says a sandwich exists and cannot say what its section is. `Layup.measured` is False and `section_bounds` refuses rather than inventing a layup | re-export the piece |
|
||
|
||
### The plant calls its pins non-composite, and that settles nothing
|
||
|
||
The insulation family on these pieces carries the description *"EPS with Holes
|
||
for Non-composite pins"*. That is **evidence about design intent, carried
|
||
verbatim in `source.connectors.declared_action` and quoted in the report**.
|
||
It is not a structural ruling, and nothing in the mechanics branches on it:
|
||
partial composite action exists physically whatever the label says, and the
|
||
connector product has published stiffness data. What the owner's confirmation
|
||
would settle is **which bound the pass/fail line is drawn against** — the
|
||
report carries both either way.
|
||
|
||
### Which end is up, and why a disclosed fallback is allowed
|
||
|
||
`knowledge.up_end_from_origin` is this project's flagship declared-coverage
|
||
refusal — three measured conditions, and it never returns `False`. When it
|
||
declines, `liftexport.erected_orientation` reads global elevation and answers
|
||
anyway, writing the refusal's own reason into `source.up_end_basis_why` —
|
||
and **nothing read that key**, partly because `read_panel` looked for
|
||
`source` inside `panel` while the exporter writes it at job level, so
|
||
`panel.source` came out empty on every real job file.
|
||
|
||
Both halves are fixed. The job-level `source` is merged onto the panel, and
|
||
`report.up_end_disclosure` renders the fallback on the page, in the CLI's
|
||
stdout and in the summary's `basis` key — written **only** when the ruled
|
||
mechanism did not answer, the same "only when it is not the ordinary case"
|
||
rule `searched` and `enabled` follow.
|
||
|
||
The deliberate decision, because "never fall back" is not the answer either:
|
||
|
||
- The rule declines on a **vertical wall panel** because it is only confirmed
|
||
for horizontal ones. Global elevation is the mechanism that is *sound* for
|
||
a piece erected on end — its two ends really are at different heights — so
|
||
on that decline the fallback is the right tool for the case the rule handed
|
||
over, not a guess.
|
||
- It is meaningless for a piece erected long-ways, and that case never
|
||
reaches here: `MIN_VERTICAL_SHARE` refuses any piece with no axis
|
||
convincingly up, and the aspect-ratio gate refuses a near-square one.
|
||
- **It reaches one stage.** Stripping and transportation are analysed flat
|
||
and are invariant under mirroring `x` — reverse the piece end for end and
|
||
the beam problem is its own mirror image. Only erection depends on it:
|
||
which row `exclude_top_rows` drops, and which edge the anchors are on.
|
||
|
||
So the fallback answers, and it is stated on every report it touched, naming
|
||
the rule's own reason for standing down. `default_stages` gets the same
|
||
treatment one step further on: an absent `source.erected_vertical_axis` used
|
||
to be read as `or 'length'`, and now says so in `problems`.
|
||
|
||
### Owner questions
|
||
|
||
Nothing below is inferred and nothing below is guessed at. Numbered so they
|
||
can be answered out of order.
|
||
|
||
1. **Are the insulated panels designed, and to be checked, as
|
||
non-composite?** The plant's own family description says the pins are
|
||
non-composite. Confirming it picks which bound the pass/fail line is drawn
|
||
against; the report carries both either way.
|
||
2. **Which wythe is structural?** The thickest is assumed, by trade
|
||
convention, and named as an assumption every time. A ruling makes it a
|
||
fact.
|
||
3. **Is the architectural wythe to be relied on in flexure at all**, or
|
||
carried by the connectors and resisting nothing? The two readings differ
|
||
by 768 in³ against 683 in³ on the reference piece.
|
||
4. **What depth should the beam use once the refusal is lifted** — the
|
||
envelope, one wythe, or a bounded pair? This is the question
|
||
`docs/lift-check.md` has been carrying as open since 2026-08-31, and the
|
||
bounds are now computed for all three so it can be answered against
|
||
numbers.
|
||
5. **Connector capacity per failure mode** — interface shear, and
|
||
normal/tension — in lb per connector, and the concrete strength the
|
||
ratings were derived at.
|
||
6. **Connector slip stiffness `k`**, lb/in per connector. Without it there is
|
||
no ratio and no end-concentration factor, and both stay stood down.
|
||
7. **Do the openings run through both wythes on every piece?** Assumed yes;
|
||
the measured plan areas on the reference piece agree to ~1%, which is
|
||
inside the rounding.
|
||
8. **Is the local thickness axis' minimum face the form face?** The layup is
|
||
measured from the lowest face of any solid the body draws, which is the
|
||
same convention the strand and insert `z_in` already use, and which face
|
||
that is has never been established. A symmetric layup is the same either
|
||
way; an asymmetric one may be the mirror of what was cast.
|