WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s
ci / test (push) Successful in 6m18s
History-free snapshot of the private development repository at cbe45c5 (2026-09-09). Code, docs and public catalogue only; no plant or job knowledge layers, no client or job names (tools/scrub.py clean). The intro deck is left out of this snapshot because its slide images could not be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,944 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""The Design Criteria section of the Panel Analysis input form.
|
||||
|
||||
WHY THIS FILE EXISTS: the owner rejected the old Panel Analysis form three
|
||||
times running -- "太丑了" (too ugly), "你这么输入用户上哪儿能看懂" (input like
|
||||
this, where would a user even understand it), "现在这个panel analysis做的也太丑
|
||||
了" (even now it is still too ugly) -- and then gave the fix in one sentence:
|
||||
"全部给我做成普通人能看懂的样子", make every part of it something an ordinary
|
||||
person can understand. The form is being rebuilt to look like Eriksson Wall's
|
||||
own printed pages, split one Eriksson section per file so eight agents can
|
||||
build it at once without stepping on each other. This file owns exactly one
|
||||
of those sections: **Design Criteria**, which on Eriksson's own printout is
|
||||
four blocks stacked on one page -- Project Information, Material Properties,
|
||||
Load Combinations, Settings -- not four separate sections. `page()` below
|
||||
returns all four, in that order, as one flat block list.
|
||||
|
||||
`page()` NEVER RAISES. A form button that throws mid-render leaves the owner
|
||||
looking at a stack trace instead of a form, which is worse than an ugly form
|
||||
-- so every lookup here is defensive, and anything this module could not lay
|
||||
out is named in the `problems` list it returns instead.
|
||||
|
||||
THE SHAPE OF THE OUTPUT is the block vocabulary the caller's generic WPF
|
||||
renderer understands (`heading`, `columns`, `field`, `kv`, `sentence`,
|
||||
`table`, `lc_table`, `rule`, `space`) and nothing else -- an unknown `kind` renders as
|
||||
nothing, so a typo here is a silently missing block, not an error. There is
|
||||
no test for "the renderer draws this correctly" (there is no renderer to run
|
||||
against on host CPython, and Revit is not running for this module to check
|
||||
against) -- only for "every block this function returns is a well-formed
|
||||
member of that vocabulary."
|
||||
|
||||
WHERE THE FIELD TEXT COMES FROM. This file authors its own `label` / `symbol`
|
||||
/ `hint` for every field, rather than importing them from
|
||||
`vibe.panelanalysisform`. Two reasons, both load-bearing:
|
||||
|
||||
1. Another agent is restructuring `panelanalysisform.FIELDS` while this file
|
||||
is being written, so importing text from it ties this module's stability
|
||||
to a moving target -- exactly what "the block list is stable" (tested
|
||||
below) is supposed to rule out.
|
||||
2. Roughly half of what Eriksson's own Design Criteria page prints has NO
|
||||
key in `panelanalysisform.FIELDS` at all yet (project identity, concrete
|
||||
elastic modulus, deflection-limit checkboxes, stress-limit checkboxes,
|
||||
resistance-factor splits, development multipliers by strand type, ...).
|
||||
Those need a label and a hint regardless of whether the backing field
|
||||
exists, so authoring locally is the only option that covers both cases the
|
||||
same way.
|
||||
|
||||
`panelanalysisform` IS still consulted, but only for one yes/no fact per
|
||||
field: does this key already exist in `FIELDS`. That answer decides EXISTS
|
||||
vs. MISSING for the `problems` list, per the owner's instruction that a field
|
||||
whose key does not exist yet degrades to an empty editor plus a named
|
||||
problem, never to a vanished block. The import is wrapped in `try/except
|
||||
Exception` (not just `ImportError`) because a sibling agent editing that file
|
||||
right now can leave it syntactically broken for a moment, and this module
|
||||
must keep rendering through that -- "could not judge whether the key exists"
|
||||
degrades to "treat it as missing", the same discipline CLAUDE.md's
|
||||
`same_face()` example applies to an unrecognised abbreviation.
|
||||
|
||||
WHY THE 3-COLUMN CONCRETE GRID AND THE REBAR/MESH/STRAND BLOCK ARE BUILT WITH
|
||||
`columns`, NOT `table`. Eriksson prints both as grids, and the block
|
||||
vocabulary's `table` kind was the first thing tried -- but a `table` cell is
|
||||
only ever a plain string or a `{'editor': 'check', ...}` tick box; there is no
|
||||
way to put a live numeric/text editor inside one. Every value in those two
|
||||
grids (f′ci, f′c, E′ci, E′c, unit weight, concrete type, fy, Es, fpu, Ep) is
|
||||
something the user can actually type, so they are laid out as `columns` of
|
||||
`field` blocks that mirror Eriksson's rows and column groupings instead. The
|
||||
Load Combinations tables used `table` for exactly that reason, back when the
|
||||
only editable thing per row was the Include tick. They no longer do: the owner
|
||||
ruled (2026-09-03) that the ACI factors themselves are editable and that a user
|
||||
can add a combination of their own, so they emit `lc_table` -- the same grid,
|
||||
but every factor cell carries a live numeric editor and the block names its
|
||||
factor columns so the renderer can build an added row. The DEFAULT factors are
|
||||
public ACI 318 code text (not this job's data) and now live in
|
||||
`panelanalysisform.LOAD_COMBO_ROWS`, beside the keys they are stored under,
|
||||
rather than in this file.
|
||||
|
||||
UPDATE 2026-09-04, RESISTANCE FACTORS IS THE ONE EXCEPTION TO ALL OF THAT. A
|
||||
headless render of this page (WPA655) showed every phi editor squeezed to zero
|
||||
width: that block was a `columns` of two sub-columns ("Developed" |
|
||||
"Transferring"), each holding `field` blocks, nested inside `col_c` of the
|
||||
Settings section's own three-way split -- so each phi row's `_two_col_row` (the
|
||||
renderer's fixed-pixel `label* | symbol(66) | editor(138) | annot(150)` grid,
|
||||
see `script.py`) needed >450px it never had once two levels of column-halving
|
||||
were done to it. `_render_table`'s cell handling was generalised for `lc_table`
|
||||
(any editor kind, not just a tick, keyed per cell) and that generalisation
|
||||
reads for a plain `table` too -- its grid columns are ordinary (unconstrained)
|
||||
`ColumnDefinition`s, not `_two_col_row`'s fixed pixels, so a cell's editor gets
|
||||
real room regardless of how many columns the table has. Resistance Factors is
|
||||
now a `table`: rows Bending/Compression/Shear, columns Developed/Transferring,
|
||||
built by `_cell(F(key))` so the existing missing-field `problems` bookkeeping
|
||||
in `_field()` still runs even though the field block itself is only used for
|
||||
its `editor`/`key`/`choices`, never rendered directly -- which does mean these
|
||||
five phi keys carry no Notes-panel hint any more (`_render_field` is what
|
||||
files a field's hint into the notes; a bare table cell dict has no hook for
|
||||
that). The Concrete / Rebar-Mesh-Strand grids above stay on `columns`
|
||||
deliberately -- they are not being squeezed the way Resistance Factors was.
|
||||
|
||||
PSI VS. KSI. `panelanalysisform.FIELDS` keeps concrete/rebar/strand strength
|
||||
keys in **psi** (`fc_psi`, `fci_psi`, `rebar_fy_ksi` is the one already in
|
||||
ksi, `ps_fpu_ksi` likewise, `strand_ep_ksi` likewise -- the module is not
|
||||
perfectly consistent about it, and this file does not try to fix that, only
|
||||
to read it correctly field by field). Eriksson prints everything in **ksi**.
|
||||
Per the assignment: keep the psi keys as the source of truth and put the ksi
|
||||
reading in the field's `symbol` or `hint` instead of introducing a second,
|
||||
parallel set of ksi keys nobody would keep in sync. Concretely: `fc_psi` and
|
||||
`fci_psi` keep their psi keys and their `hint` says "Eriksson prints this
|
||||
divided by 1000, in ksi" so the two reconcile by eye against a printed sheet;
|
||||
the keys that are already ksi (`rebar_fy_ksi`, `rebar_es_ksi`, `ps_fpu_ksi`,
|
||||
`strand_ep_ksi`) need no such note.
|
||||
|
||||
WHY EXTERIOR SOIL / INTERIOR SOIL ARE `sentence` BLOCKS, NOT `field` BLOCKS.
|
||||
On Eriksson's own page these are not something the user types on THIS page at
|
||||
all -- they are a computed caption ("Design contains no exterior soil loads")
|
||||
that reads back whatever soil-load rows exist elsewhere in the model. Nothing
|
||||
in this codebase collects soil-load rows anywhere yet, so there is no field
|
||||
for this section to degrade to an "empty editor" for -- there is no editor to
|
||||
have. Rendering it as a static `sentence` instead of inventing a text field
|
||||
that would silently claim to collect something it cannot is the same
|
||||
discipline as `knowledge.same_face()` returning `None` rather than `False`:
|
||||
"cannot judge, and does not pretend to." One `problems` entry names this once
|
||||
for both columns, so the gap is visible without repeating the same sentence
|
||||
twice.
|
||||
|
||||
THE R-VALUE / MINIMUM-REINFORCEMENT PAIRS mirror Eriksson's own pattern: a
|
||||
checkbox that turns a calculation on, printed beside the sentence that
|
||||
applies when it is off ("R-Value not being calculated"). Both checkbox keys
|
||||
are new (`design_criteria.rvalue_calc_enabled`,
|
||||
`design_criteria.min_reinf_checked` -- see the naming note below) and both
|
||||
degrade the same way every other missing key does: an empty `check` editor
|
||||
plus a named `problems` entry, with the off-state sentence always shown
|
||||
underneath since "unchecked" is this page's honest default until the field
|
||||
exists to say otherwise.
|
||||
|
||||
NAMING STYLE FOR NEW KEYS. `panelanalysisform` already has a dotted
|
||||
namespacing convention for one composite input (`stage.<stage_slug>.<opt>`,
|
||||
see `stage_field_key`). New keys here that do not fit a single flat name
|
||||
follow the same shape: `design_criteria.<thing>` for one-off Settings/Project
|
||||
fields (e.g. `design_criteria.analysis_beta_d`), and
|
||||
`load_combo.<strength|service>.<n>.include` /
|
||||
`load_combo.<strength|service>.min_earth_factor` for the Load Combinations
|
||||
grid, mirroring `stage_field_key`'s `<group>.<slug>.<opt>` shape exactly.
|
||||
|
||||
IRONPYTHON 2.7: no f-strings, no walrus, no typing syntax; string formatting
|
||||
is `'{0}'.format(...)`; every literal that carries the prime character or a
|
||||
non-ASCII letter is a `u'...'` literal with the coding cookie above declaring
|
||||
the file UTF-8, the same convention `panelanalysisform.py` uses throughout.
|
||||
Imports NOTHING beyond the standard library and, defensively,
|
||||
`vibe.panelanalysisform` for key-existence checks only -- no WPF, no Revit,
|
||||
no `panelsketch`, so this loads and runs on plain CPython the way
|
||||
`tests/conftest.py` expects.
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------- key existence
|
||||
|
||||
def _existing_keys():
|
||||
"""The set of keys `panelanalysisform.FIELDS` already declares, or None.
|
||||
|
||||
None means "could not judge" -- the import failed, or the module's shape
|
||||
changed out from under us mid-refactor -- and every caller below treats
|
||||
None the same as "key not found": every field on this page degrades to
|
||||
an empty editor plus a `problems` entry rather than silently assuming
|
||||
the other agent's in-flight edit added the key. Broad `except Exception`
|
||||
on purpose: a sibling agent saving a half-written `panelanalysisform.py`
|
||||
can raise a SyntaxError, not just ImportError, and this page must keep
|
||||
rendering through that.
|
||||
"""
|
||||
try:
|
||||
from vibe import panelanalysisform as pf
|
||||
return set(f['key'] for f in pf.FIELDS)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- fields
|
||||
#
|
||||
# One entry per field this page can lay out: key -> (label, symbol, hint,
|
||||
# required, editor, choices). Authored locally -- see the module docstring's
|
||||
# "WHERE THE FIELD TEXT COMES FROM" for why. `required` is True only for the
|
||||
# two keys `panelanalysisform` itself already gates the Run button on
|
||||
# (`fc_psi`, `unit_weight_pcf`); every other field here is optional, matching
|
||||
# the rest of Design Criteria in that module.
|
||||
_F = {
|
||||
# --------------------------------------------------- Project Information
|
||||
'project_name': (
|
||||
'Project name', '',
|
||||
'The name of the project this panel belongs to, for the record on '
|
||||
'this printout. Leave blank if the project has no name yet.',
|
||||
False, 'text', None),
|
||||
'client_name': (
|
||||
'Client name', '',
|
||||
"The client this panel's project is for. Leave blank if unknown.",
|
||||
False, 'text', None),
|
||||
'project_description': (
|
||||
'Project description', '',
|
||||
'A short free-text description of the project. Leave blank if '
|
||||
'there is nothing to add.',
|
||||
False, 'text', None),
|
||||
'project_location': (
|
||||
'Project location', '',
|
||||
'Where the project is built -- city, state, or site name. Leave '
|
||||
'blank if unknown.',
|
||||
False, 'text', None),
|
||||
'engineer_initials': (
|
||||
'Engineer of record', '',
|
||||
'Who is responsible for this design. Leave blank if not yet '
|
||||
'assigned.',
|
||||
False, 'text', None),
|
||||
'project_date': (
|
||||
'Design date', '',
|
||||
'The date this design applies to. Leave blank to leave it unset.',
|
||||
False, 'text', None),
|
||||
'job_number': (
|
||||
'Job number', '',
|
||||
"This job's internal tracking number, if it has one. Leave blank "
|
||||
'if none has been assigned yet.',
|
||||
False, 'text', None),
|
||||
'design_spec': (
|
||||
'Governing design code', '',
|
||||
'Which code edition this design is checked against. Leave blank '
|
||||
'to leave it unstated.',
|
||||
False, 'choice', ('ACI 318-19', 'ACI 318-14', 'ACI 318-11')),
|
||||
# ------------------------------------------- Material Properties/Concrete
|
||||
'fci_psi': (
|
||||
'Concrete strength at release', u"f′ci (psi)",
|
||||
u"How strong the concrete must be when the strand is cut or the "
|
||||
u"panel is stripped from the bed -- usually lower than the 28-day "
|
||||
u"strength. Eriksson prints this divided by 1000, in ksi. Leave "
|
||||
u"blank if this panel is not prestressed.",
|
||||
False, 'num', None),
|
||||
'fc_psi': (
|
||||
'Concrete strength at 28 days', u"f′c (psi)",
|
||||
u"The concrete's design strength once fully cured; every stress "
|
||||
u"limit on this page is a multiple of this number. Eriksson prints "
|
||||
u"this divided by 1000, in ksi. Required -- the solve cannot run "
|
||||
u"without it.",
|
||||
True, 'num', None),
|
||||
# Both moduli are DERIVED (`panelanalysisform.DERIVED_FIELDS`, ACI 318
|
||||
# 19.2.2, Ec = 33 wc^1.5 sqrt(f'c)) and the page keeps them live as
|
||||
# f'c / f'ci / unit weight are edited until the user types into one
|
||||
# (issue #22, item 1). The old text here said a rule was still to be
|
||||
# agreed; it had been in the field model since 2026-09-03. The solve
|
||||
# still does not read either -- `panellift` computes its own -- and the
|
||||
# hint says so, so the page mutes them honestly.
|
||||
'conc_eci_psi': (
|
||||
'Concrete elastic modulus at release', u"E′ci (psi)",
|
||||
u"How stiff the concrete is at release strength. Computed by ACI "
|
||||
u"318 from f′ci and the unit weight (Ec = 33 wc^1.5 sqrt(f′ci)) and "
|
||||
u"kept up to date as you edit those; type a value to override it. "
|
||||
u"Not yet used by the solve.",
|
||||
False, 'num', None),
|
||||
'conc_ec_psi': (
|
||||
'Concrete elastic modulus, in-place', u"E′c (psi)",
|
||||
u"How stiff the concrete is once fully cured. Computed by ACI 318 "
|
||||
u"from f′c and the unit weight (Ec = 33 wc^1.5 sqrt(f′c)) and kept "
|
||||
u"up to date as you edit those; type a value to override it. Not "
|
||||
u"yet used by the solve.",
|
||||
False, 'num', None),
|
||||
'unit_weight_pcf': (
|
||||
'Concrete unit weight', 'pcf',
|
||||
"How much the concrete weighs per cubic foot; the panel's own "
|
||||
'weight is computed from this. Required.',
|
||||
True, 'num', None),
|
||||
'concrete_type': (
|
||||
'Concrete type', '',
|
||||
'What kind of concrete this panel is cast from (normal weight, '
|
||||
'lightweight, ...). Reference only -- nothing computes from it '
|
||||
'yet.',
|
||||
False, 'choice', ('Normalweight', 'Lightweight', 'Sand-Lightweight')),
|
||||
# ----------------------------------------------- Material Properties/Bar
|
||||
'rebar_fy_ksi': (
|
||||
'Rebar yield strength', 'fy (ksi)',
|
||||
'The stress at which ordinary (non-prestressed) reinforcing bar '
|
||||
'starts to yield. Shown for the record; not yet used by the '
|
||||
'solve.',
|
||||
False, 'num', None),
|
||||
'rebar_es_ksi': (
|
||||
'Rebar elastic modulus', 'Es (ksi)',
|
||||
'How stiff ordinary reinforcing steel is. Shown for the record; '
|
||||
'not yet used by the solve.',
|
||||
False, 'num', None),
|
||||
'ps_fpu_ksi': (
|
||||
u'Strand ultimate strength', u'fₚᵤ (ksi)',
|
||||
'The breaking strength of the prestressing strand. Leave blank if '
|
||||
'this panel is not prestressed.',
|
||||
False, 'num', None),
|
||||
'strand_ep_ksi': (
|
||||
'Strand elastic modulus', 'Ep (ksi)',
|
||||
'How stiff the prestressing strand is. Shown for the record; not '
|
||||
'yet used by the solve.',
|
||||
False, 'num', None),
|
||||
# ----------------------------------------------- Material Properties/Mesh
|
||||
'mesh_fy_ksi': (
|
||||
'Mesh yield strength', 'fy (ksi)',
|
||||
'The yield strength of the welded wire mesh. Defaults to the '
|
||||
'common 80 ksi. Shown for the record; not yet used by the solve.',
|
||||
False, 'num', None),
|
||||
'mesh_es_ksi': (
|
||||
'Mesh elastic modulus', 'Es (ksi)',
|
||||
'How stiff the welded wire mesh is. Defaults to 29000 ksi. Shown '
|
||||
'for the record; not yet used by the solve.',
|
||||
False, 'num', None),
|
||||
# -------------------------------------------------------- Load combos
|
||||
'design_criteria.min_earth_factor_strength': (
|
||||
'Minimum earth factor, strength combinations', '',
|
||||
'The smallest lateral-earth-pressure factor applied across the '
|
||||
'strength combinations below. Leave blank to use none.',
|
||||
False, 'num', None),
|
||||
'design_criteria.min_earth_factor_service': (
|
||||
'Minimum earth factor, service combinations', '',
|
||||
'The smallest lateral-earth-pressure factor applied across the '
|
||||
'service combinations below. Leave blank to use none.',
|
||||
False, 'num', None),
|
||||
# --------------------------------------------------- Settings/Deflection
|
||||
'deflection_limit': (
|
||||
'Typical deflection limit', 'L / n',
|
||||
'How much the panel is allowed to bow or sag, as a fraction of its '
|
||||
'span, e.g. "150" for L/150. Read into the in-place service check; '
|
||||
'blank leaves that check NOT JUDGED.',
|
||||
False, 'text', None),
|
||||
'design_criteria.deflection_add_bow': (
|
||||
'Add an initial bow to reported deflections?', '',
|
||||
'Whether the panel is assumed to already be slightly bowed before '
|
||||
'any load is applied, and that starting bow is added to every '
|
||||
'reported deflection. Leave unchecked to assume none.',
|
||||
False, 'check', None),
|
||||
'design_criteria.deflection_additional_bow_in': (
|
||||
'Additional bow assumed', 'in',
|
||||
'How much starting bow to add, if the box above is checked. Leave '
|
||||
'blank for none.',
|
||||
False, 'num', None),
|
||||
'design_criteria.deflection_check_cantilever': (
|
||||
'Check deflections on cantilevers?', '',
|
||||
'Whether cantilevered portions of the panel get their own '
|
||||
'deflection check, separate from the typical limit above. Leave '
|
||||
'unchecked to skip it.',
|
||||
False, 'check', None),
|
||||
'design_criteria.deflection_cantilever_limit': (
|
||||
'Cantilever deflection limit', 'L / n',
|
||||
'The separate deflection limit for cantilevered portions, if the '
|
||||
'box above is checked.',
|
||||
False, 'choice', ('L/75', 'L/120', 'L/150')),
|
||||
# ---------------------------------------------- Settings/Structural Anal.
|
||||
'second_order': (
|
||||
'2nd-order effects considered?', '',
|
||||
'Whether P-delta / second-order bending is considered for this '
|
||||
'panel. Not yet used by the solve.',
|
||||
False, 'text', None),
|
||||
'cracking_assumption': (
|
||||
'Cracked or uncracked section assumed', '',
|
||||
'Whether the stress check assumes the concrete section is cracked '
|
||||
'or uncracked. Read into the in-place service check as the '
|
||||
'cracking class.',
|
||||
False, 'choice', ('Cracked', 'Uncracked')),
|
||||
'design_criteria.analysis_beta_d': (
|
||||
u'Sway condition for 2nd-order analysis', u'βd',
|
||||
'Whether the 2nd-order analysis above treats this panel as braced '
|
||||
'(non-sway) or unbraced (sway). Leave blank if not applicable.',
|
||||
False, 'choice', ('Non-Sway', 'Sway')),
|
||||
'design_criteria.cracking_method': (
|
||||
'Cracking method', '',
|
||||
'Which published method decides whether the section is treated as '
|
||||
'cracked. Leave blank if not applicable.',
|
||||
False, 'choice', ('Mast Method', 'Uncracked', 'Cracked')),
|
||||
'design_criteria.cracking_limit_sqrt_fc': (
|
||||
u'Cracking stress limit', u"n·√f′c",
|
||||
u"The stress, as a multiple of √f′c, at which the "
|
||||
u"section is assumed to crack. Leave blank to use the built-in "
|
||||
u"default.",
|
||||
False, 'num', None),
|
||||
'design_criteria.rvalue_calc_enabled': (
|
||||
'Calculate an R-value for this panel?', '',
|
||||
'Whether an insulation R-value is calculated for this panel. Leave '
|
||||
'unchecked if it is not being calculated -- that is the default.',
|
||||
False, 'check', None),
|
||||
'design_criteria.min_reinf_checked': (
|
||||
'Check minimum reinforcement?', '',
|
||||
'Whether the ACI minimum-steel requirement is checked for this '
|
||||
'panel. Leave unchecked if it is not being checked -- that is the '
|
||||
'default.',
|
||||
False, 'check', None),
|
||||
# -------------------------------------------------- Settings/Stress opts
|
||||
'design_criteria.stress_failure_if_cracked': (
|
||||
'Fail the panel if it cracks?', '',
|
||||
'Whether a cracked section is treated as an outright failure '
|
||||
'rather than just a flag. Leave unchecked to only flag it.',
|
||||
False, 'check', None),
|
||||
'design_criteria.stress_ignore_lateral_ecc': (
|
||||
'Ignore lateral strand eccentricity?', '',
|
||||
'Whether side-to-side strand placement is ignored when computing '
|
||||
'stress. Leave unchecked to include it.',
|
||||
False, 'check', None),
|
||||
'design_criteria.stress_use_principal_axis': (
|
||||
'Use the principal axis for stress?', '',
|
||||
'Whether stress is computed about the section\'s principal axis '
|
||||
'rather than its geometric one. Leave unchecked to use the '
|
||||
'geometric axis.',
|
||||
False, 'check', None),
|
||||
# Eriksson prints these as three rows under one "In-Place Stress Limits"
|
||||
# heading -- Tension (uncracked), Tension (transition / modulus of
|
||||
# rupture), Compression -- and the review on 2026-09-04 (WPA655) found
|
||||
# this page missing the first of the three (`inplace_tension_sqrt` is a
|
||||
# real `panelanalysisform.FIELDS` key, just never surfaced here) and
|
||||
# labelling the other two by what they compute rather than by Eriksson's
|
||||
# own row names. All three keep the naming scheme now.
|
||||
'inplace_tension_sqrt': (
|
||||
u'Tension, uncracked', u"n·√f′c",
|
||||
u"The concrete's tensile stress limit once erected, while the "
|
||||
u"section is still assumed uncracked, as a multiple of √f′c. "
|
||||
u"Leave blank to use the built-in default.",
|
||||
False, 'num', None),
|
||||
'design_criteria.inplace_modulus_rupture_sqrt_fc': (
|
||||
u'Tension, transition (modulus of rupture)', u"n·√f′c",
|
||||
u"The concrete's tensile cracking stress once erected, as a "
|
||||
u"multiple of √f′c. Leave blank to use the built-in "
|
||||
u"default.",
|
||||
False, 'num', None),
|
||||
'inplace_comp_ratio': (
|
||||
u'Compression', u"n·f′c",
|
||||
u"The compression limit for the panel once it is standing in "
|
||||
u"place, as a fraction of f′c. Not yet used by the solve.",
|
||||
False, 'num', None),
|
||||
'handling_tension_sqrt': (
|
||||
'Tension allowed while handling', u"n·√f′ci",
|
||||
u"How much the concrete may be pulled apart during stripping / "
|
||||
u"handling, as a multiple of √f′ci. Blank uses the "
|
||||
u"built-in default.",
|
||||
False, 'num', None),
|
||||
'handling_comp_ratio': (
|
||||
'Compression allowed while handling', u"n·f′ci",
|
||||
u"How much the concrete may be squeezed during handling, as a "
|
||||
u"fraction of f′ci. Blank uses the built-in default.",
|
||||
False, 'num', None),
|
||||
'design_criteria.flexure_method': (
|
||||
'Flexure calculation method', '',
|
||||
'Which method computes bending capacity. Leave blank to use the '
|
||||
'built-in default.',
|
||||
False, 'choice', ('Whitney Stress Block', 'Strain Compatibility')),
|
||||
'design_criteria.flexure_ignored_end_distance_in': (
|
||||
'Ignored end distance', 'in',
|
||||
'A length at each end of the panel excluded from the flexure '
|
||||
'check (strand not yet developed there). Leave blank for none.',
|
||||
False, 'num', None),
|
||||
'design_criteria.flexure_cracking_check': (
|
||||
'Flexural cracking check', '',
|
||||
'Which cracking check applies to the flexure calculation, if any. '
|
||||
'Leave blank for none.',
|
||||
False, 'choice', ('None', '1.2 Mcr', '1.33 Mu')),
|
||||
# ------------------------------------------------ Settings/Resist. Fact.
|
||||
'phi_flexure': (
|
||||
'Bending strength reduction factor, developed', u'φ, tension',
|
||||
"The ACI safety factor applied to the panel's bending strength "
|
||||
'once the strand is fully developed. Blank uses the built-in '
|
||||
'default (0.90).',
|
||||
False, 'num', None),
|
||||
'design_criteria.phi_compression': (
|
||||
'Compression strength reduction factor, developed',
|
||||
u'φ, compression',
|
||||
"The ACI safety factor applied to the panel's compression "
|
||||
'strength once the strand is fully developed. Leave blank to use '
|
||||
'the built-in default.',
|
||||
False, 'num', None),
|
||||
'phi_shear': (
|
||||
'Shear strength reduction factor, developed', u'φ, shear',
|
||||
"The ACI safety factor applied to the panel's shear strength once "
|
||||
'the strand is fully developed. Blank uses the built-in default '
|
||||
'(0.75).',
|
||||
False, 'num', None),
|
||||
'design_criteria.phi_tension_transfer': (
|
||||
'Bending strength reduction factor, transferring',
|
||||
u'φ, tension',
|
||||
'The same safety factor as above, but while the strand is still '
|
||||
'transferring its force into the concrete. Leave blank if not '
|
||||
'applicable.',
|
||||
False, 'num', None),
|
||||
'design_criteria.phi_shear_transfer': (
|
||||
'Shear strength reduction factor, transferring', u'φ, shear',
|
||||
'The same safety factor as above, but while the strand is still '
|
||||
'transferring its force into the concrete. Leave blank if not '
|
||||
'applicable.',
|
||||
False, 'num', None),
|
||||
'dev_mult': (
|
||||
'Strand development multiplier, normal', '',
|
||||
'A multiplier on the strand development length for ordinary '
|
||||
'(non-debonded, uncut) strand. Blank uses the built-in default '
|
||||
'(1.0).',
|
||||
False, 'num', None),
|
||||
'design_criteria.dev_mult_debonded': (
|
||||
'Strand development multiplier, debonded', '',
|
||||
'The same multiplier, for strand that is deliberately debonded '
|
||||
'over part of its length. Leave blank to use the built-in '
|
||||
'default.',
|
||||
False, 'num', None),
|
||||
'design_criteria.dev_mult_cut': (
|
||||
'Strand development multiplier, cut', '',
|
||||
'The same multiplier, for strand that is field-cut rather than '
|
||||
'released normally. Leave blank to use the built-in default.',
|
||||
False, 'num', None),
|
||||
'design_criteria.dev_mult_mild': (
|
||||
'Development multiplier, mild reinforcement', '',
|
||||
'The same idea applied to ordinary (non-prestressed) reinforcing '
|
||||
'bar. Leave blank to use the built-in default.',
|
||||
False, 'num', None),
|
||||
'design_criteria.shear_treat_as_column': (
|
||||
'Treat this panel as a column for shear?', '',
|
||||
'Whether the shear check uses column rules rather than wall '
|
||||
'rules. Leave unchecked to use wall rules.',
|
||||
False, 'check', None),
|
||||
'design_criteria.axial_ignore_tensile_failures': (
|
||||
'Ignore axial tensile-strength failures?', '',
|
||||
'Whether the axial check is allowed to pass even when the '
|
||||
"concrete's tensile strength alone is exceeded. Leave unchecked "
|
||||
'to flag them.',
|
||||
False, 'check', None),
|
||||
'design_criteria.axial_ignore_buckling_failures': (
|
||||
'Ignore buckling failures?', '',
|
||||
'Whether the axial check is allowed to pass even when the panel '
|
||||
'would buckle. Leave unchecked to flag them.',
|
||||
False, 'check', None),
|
||||
}
|
||||
|
||||
# Prestress-loss group: shown as a sentence when every one of these is blank
|
||||
# (see `_group_or_sentence`), matching Eriksson's own "nothing entered here"
|
||||
# style for Exterior/Interior Soil, applied to the one other Design Criteria
|
||||
# group that is entirely optional and entirely empty on a fresh panel.
|
||||
_PRESTRESS_LOSS_FIELDS = (
|
||||
'ps_loss_method', 'ps_loss_humidity_pct',
|
||||
'ps_loss_release_hours', 'ps_loss_erection_days',
|
||||
)
|
||||
_F['ps_loss_method'] = (
|
||||
'Prestress loss method', '',
|
||||
'Which published method (PCI, AASHTO, ...) the assumed loss below '
|
||||
'comes from. Reference only.',
|
||||
False, 'text', None)
|
||||
_F['ps_loss_humidity_pct'] = (
|
||||
'Ambient humidity assumed', '%',
|
||||
'The relative humidity the loss method uses -- losses are larger in '
|
||||
'drier air. Not yet used by the solve.',
|
||||
False, 'num', None)
|
||||
_F['ps_loss_release_hours'] = (
|
||||
'Hours from casting to release', '',
|
||||
'How many hours after casting the strand is cut / the panel is '
|
||||
'stripped. Not yet used by the solve.',
|
||||
False, 'num', None)
|
||||
_F['ps_loss_erection_days'] = (
|
||||
'Days from casting to erection', '',
|
||||
'How many days after casting the panel is erected in place. Not yet '
|
||||
'used by the solve.',
|
||||
False, 'num', None)
|
||||
|
||||
|
||||
def _field(key, problems, seen, existing):
|
||||
"""One `field` block for `key`, plus a `problems` entry if it is missing.
|
||||
|
||||
`seen` de-dupes: several groups below reuse a couple of keys (none do
|
||||
today, but the guard costs nothing and keeps a future edit from quietly
|
||||
filing the same problem twice). `existing` is `_existing_keys()`'s
|
||||
result, threaded through rather than re-read per field so one broken
|
||||
import during a render is reported once, not dozens of times.
|
||||
"""
|
||||
spec = _F.get(key)
|
||||
if spec is None:
|
||||
# Programming error inside this file, not a missing form key --
|
||||
# still degrade rather than raise, and say so plainly.
|
||||
problems.append(
|
||||
"Design Criteria has no text authored for the key '{0}' -- "
|
||||
'this is a bug in pa_criteria.py, not a missing form field.'
|
||||
.format(key))
|
||||
return {'kind': 'field', 'key': key, 'label': key, 'symbol': None,
|
||||
'hint': None, 'required': False, 'editor': 'text',
|
||||
'choices': None}
|
||||
label, symbol, hint, required, editor, choices = spec
|
||||
if existing is None or key not in existing:
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
problems.append(
|
||||
"'{0}' ({1}) has no backing field in panelanalysisform.FIELDS "
|
||||
'yet -- shown as an empty editor until it is added.'
|
||||
.format(label, key))
|
||||
return {'kind': 'field', 'key': key, 'label': label,
|
||||
'symbol': symbol or None, 'hint': hint, 'required': required,
|
||||
'editor': editor, 'choices': list(choices) if choices else None}
|
||||
|
||||
|
||||
def _heading(text, level):
|
||||
return {'kind': 'heading', 'text': text, 'level': level}
|
||||
|
||||
|
||||
def _cols(columns):
|
||||
return {'kind': 'columns', 'columns': columns}
|
||||
|
||||
|
||||
def _sentence(text):
|
||||
return {'kind': 'sentence', 'text': text}
|
||||
|
||||
|
||||
def _rule():
|
||||
return {'kind': 'rule'}
|
||||
|
||||
|
||||
def _space(h=1):
|
||||
return {'kind': 'space', 'h': h}
|
||||
|
||||
|
||||
def _table(headers, rows, header_groups=None):
|
||||
return {'kind': 'table', 'headers': list(headers),
|
||||
'header_groups': header_groups, 'rows': [list(r) for r in rows]}
|
||||
|
||||
|
||||
def _cell(field_block):
|
||||
"""A `table` cell dict for one `_field()` result -- the Resistance
|
||||
Factors table (see the module docstring's 2026-09-04 update) reuses
|
||||
`_field()` only for its `editor` / `key` / `choices` and the
|
||||
missing-backing-field `problems` entry it already files, never for the
|
||||
`field` block itself (a bare table cell has nowhere to put a label or a
|
||||
hint, so those are simply not repeated here)."""
|
||||
return {'editor': field_block['editor'], 'key': field_block['key'],
|
||||
'choices': field_block['choices']}
|
||||
|
||||
|
||||
def _group_or_sentence(values, keys, empty_text, build_filled):
|
||||
"""[blocks] -- a `sentence` when every key in `keys` is blank, else
|
||||
whatever `build_filled()` returns.
|
||||
|
||||
This is the "nothing here vs. never asked" split applied at group
|
||||
granularity: `keys` all being blank in `values` is a real, filled-in
|
||||
answer ("nothing entered"), not the absence of a form -- so it is shown
|
||||
as a plain sentence instead of a wall of empty boxes, the same way
|
||||
Eriksson's own printout shows a caption instead of blank inputs when
|
||||
there is nothing to report. `values` is read with `.get(...)`, never
|
||||
indexed, and never mutated.
|
||||
|
||||
`build_filled` is a zero-argument CALLABLE, not a plain list -- the
|
||||
filled-in branch typically calls `F(key)`, which files a `problems`
|
||||
entry for any key still missing from `panelanalysisform.FIELDS`, and
|
||||
that side effect must only happen for fields actually being shown. A
|
||||
plain list argument would be built eagerly by the caller before this
|
||||
function ever looks at `keys`, filing a phantom "missing field" problem
|
||||
for a field the empty-sentence branch never renders.
|
||||
"""
|
||||
for k in keys:
|
||||
v = values.get(k)
|
||||
if v is not None and str(v).strip():
|
||||
return build_filled()
|
||||
return [_sentence(empty_text)]
|
||||
|
||||
|
||||
# --------------------------------------------------------- load combinations
|
||||
#
|
||||
# Standard ACI 318-14 basic strength/service combinations -- public code
|
||||
# text, the same eight/ten rows any ACI-compliant tool would print, not this
|
||||
# job's data.
|
||||
#
|
||||
# THESE ARE NOW A FALLBACK, not the source. The live copy is
|
||||
# `panelanalysisform.LOAD_COMBO_ROWS`, because the factors became editable form
|
||||
# VALUES and a value needs a key, which is that module's business. These stay
|
||||
# because this file's whole discipline is that a sibling module mid-edit must
|
||||
# not take the page down: if the import fails, the table still prints the ACI
|
||||
# defaults (as plain text, with a named problem saying it cannot be edited)
|
||||
# instead of vanishing. Keep the two in step -- a host test compares them.
|
||||
_STRENGTH_COMBOS = (
|
||||
('1', '1.4', '0', '0', '0', '0', '1', '1.6'),
|
||||
('2', '1.2', '1.6', '0.5', '0', '0', '1', '1.6'),
|
||||
('3', '1.2', '1', '1.6', '0', '0', '1', '1.6'),
|
||||
('4', '1.2', '0', '1.6', '0.5', '0', '1', '1.6'),
|
||||
('5', '1.2', '1', '0.5', '1', '0', '1', '1.6'),
|
||||
('6', '1.2', '1', '0.2', '0', '1', '1', '1.6'),
|
||||
('7', '0.9', '0', '0', '1', '0', '1', '1.6'),
|
||||
('8', '0.9', '0', '0', '0', '1', '1', '1.6'),
|
||||
)
|
||||
_SERVICE_COMBOS = (
|
||||
('1', '1', '0', '0', '0', '0', '0.75', '1'),
|
||||
('2', '1', '1', '0', '0', '0', '0.75', '1'),
|
||||
('3', '1', '0', '1', '0', '0', '0.75', '1'),
|
||||
('4', '1', '0.75', '0.75', '0', '0', '0.75', '1'),
|
||||
('5', '1', '0', '0', '0.6', '0', '0.75', '1'),
|
||||
('6', '1', '0', '0', '0', '0.7', '0.75', '1'),
|
||||
('7', '1', '0.75', '0.75', '0.45', '0', '0.75', '1'),
|
||||
('8', '1', '0.75', '0.75', '0', '0.525', '0.75', '1'),
|
||||
('9', '0.6', '0', '0', '0.6', '0', '0.75', '1'),
|
||||
('10', '0.6', '0', '0', '0', '0.7', '0.75', '1'),
|
||||
)
|
||||
_COMBO_HEADERS = ['#', 'D', 'L', 'R', 'W', 'E', 'T', 'H', 'Include']
|
||||
|
||||
|
||||
def _combo_table(name, values, problems):
|
||||
"""One EDITABLE load-combination table, as an `lc_table` block.
|
||||
|
||||
Not a plain `table` any more. The owner ruled (2026-09-03) that the load
|
||||
factors are the user's to change and that a row can be added, and a `table`
|
||||
cell is only ever a string or a tick box -- so this emits `lc_table`, whose
|
||||
factor cells each carry an `{'editor': 'num', 'key': ...}` the renderer
|
||||
turns into a live box, plus the `factors` list the renderer's "Add LC"
|
||||
button needs to build a new row's cells.
|
||||
|
||||
The FACTORS themselves come from `panelanalysisform.LOAD_COMBO_ROWS`, which
|
||||
is the one place that knows both the ACI default and the key it is stored
|
||||
under. The local tuples stay as a fallback for the case this file already
|
||||
handles everywhere else -- the sibling module mid-edit and unimportable --
|
||||
so a broken import degrades this page to the code defaults with no live
|
||||
keys rather than to no table at all.
|
||||
|
||||
`values` is accepted (and read only for the user's own added rows) rather
|
||||
than the row tuples the old signature took: the rows are no longer this
|
||||
file's to decide.
|
||||
"""
|
||||
values = values if isinstance(values, dict) else {}
|
||||
fallback = {'strength': _STRENGTH_COMBOS, 'service': _SERVICE_COMBOS}
|
||||
factors = list(_COMBO_HEADERS[1:-1])
|
||||
try:
|
||||
from vibe import panelanalysisform as paf
|
||||
factors = list(paf.LOAD_COMBO_FACTORS)
|
||||
row_defaults = dict((r[0], r) for r in paf.LOAD_COMBO_ROWS.get(name, ()))
|
||||
row_ids = paf.load_combo_row_ids(name, values)
|
||||
factor_key = paf.load_combo_factor_key
|
||||
include_key = paf.load_combo_include_key
|
||||
except Exception:
|
||||
problems.append(
|
||||
'could not read panelanalysisform for the {0} load-combination '
|
||||
'factors; the table below shows the ACI defaults as plain text '
|
||||
'and cannot be edited until that module loads.'.format(name))
|
||||
row_defaults = dict((r[0], r) for r in fallback.get(name, ()))
|
||||
row_ids = [r[0] for r in fallback.get(name, ())]
|
||||
|
||||
def factor_key(nm, rid, col):
|
||||
return 'load_combo.{0}.{1}.{2}'.format(nm, rid, col)
|
||||
|
||||
def include_key(nm, rid):
|
||||
return 'load_combo.{0}.{1}.include'.format(nm, rid)
|
||||
|
||||
table_rows = []
|
||||
for rid in row_ids:
|
||||
cells = [str(rid)]
|
||||
default = row_defaults.get(rid)
|
||||
for i, col in enumerate(factors):
|
||||
cells.append({'editor': 'num', 'width': 44,
|
||||
'key': factor_key(name, rid, col),
|
||||
'default': (default[i + 1] if default and
|
||||
i + 1 < len(default) else None)})
|
||||
cells.append({'editor': 'check', 'key': include_key(name, rid)})
|
||||
table_rows.append(cells)
|
||||
return {'kind': 'lc_table', 'name': name,
|
||||
'headers': list(_COMBO_HEADERS), 'header_groups': None,
|
||||
'rows': table_rows, 'factors': factors, 'row_key': None}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- page
|
||||
|
||||
def page(values, reads=None):
|
||||
"""(blocks, problems) for the Design Criteria section.
|
||||
|
||||
`values` is the form's current string-valued dict, read defensively --
|
||||
every lookup goes through `.get(...)` and a key this module expects but
|
||||
does not find is treated as blank, never as an error, because another
|
||||
agent is restructuring `panelanalysisform.FIELDS` at the same time this
|
||||
is being written. `values` is never mutated. `reads` is optional and, on
|
||||
this section, only used for the one-line "Panel <mark>" context caption
|
||||
at the top -- everything else on Design Criteria is job-level or
|
||||
material data, not something read off the framing element.
|
||||
|
||||
Never raises: every lookup that could fail (the `panelanalysisform`
|
||||
import, a missing dict key) degrades to a `problems` entry instead.
|
||||
"""
|
||||
values = values if isinstance(values, dict) else {}
|
||||
reads = reads if isinstance(reads, dict) else {}
|
||||
problems = []
|
||||
seen = set()
|
||||
existing = _existing_keys()
|
||||
if existing is None:
|
||||
problems.append(
|
||||
'could not read panelanalysisform.FIELDS to check which keys '
|
||||
'already exist (the module may be mid-edit); every field on '
|
||||
'this page is shown, and is reported as missing until this is '
|
||||
'confirmed.')
|
||||
|
||||
def F(key):
|
||||
return _field(key, problems, seen, existing)
|
||||
|
||||
blocks = []
|
||||
blocks.append(_heading('Design Criteria', 1))
|
||||
|
||||
mark = (reads.get('mark') or '').strip()
|
||||
design_number = (reads.get('design_number') or '').strip()
|
||||
if mark or design_number:
|
||||
blocks.append({'kind': 'kv', 'rows': [
|
||||
('Panel', mark or '(mark not read)'),
|
||||
('Design number', design_number or '(not read)'),
|
||||
]})
|
||||
|
||||
# ----------------------------------------------------- Project Information
|
||||
blocks.append(_heading('Project Information', 2))
|
||||
blocks.append(_cols([
|
||||
[F('project_name'), F('client_name'), F('project_description')],
|
||||
[F('project_location'), F('engineer_initials')],
|
||||
[F('project_date'), F('job_number'), F('design_spec')],
|
||||
]))
|
||||
blocks.append(_rule())
|
||||
|
||||
# ------------------------------------------------------ Material Properties
|
||||
blocks.append(_heading('Material Properties', 2))
|
||||
blocks.append(_heading('Concrete', 3))
|
||||
blocks.append(_cols([
|
||||
[F('fci_psi'), F('fc_psi')],
|
||||
[F('conc_eci_psi'), F('conc_ec_psi')],
|
||||
[F('unit_weight_pcf'), F('concrete_type')],
|
||||
]))
|
||||
blocks.append(_space(1))
|
||||
blocks.append(_cols([
|
||||
[_heading('Rebar', 3), F('rebar_fy_ksi'), F('rebar_es_ksi')],
|
||||
[_heading('Mesh', 3), F('mesh_fy_ksi'), F('mesh_es_ksi')],
|
||||
[_heading('Strand', 3), F('ps_fpu_ksi'), F('strand_ep_ksi')],
|
||||
]))
|
||||
blocks.append(_rule())
|
||||
|
||||
# ---------------------------------------------------------- Prestress Loss
|
||||
#
|
||||
# Two columns, not the one wide column this used to be laid out in: a
|
||||
# single-column `_cols` hands `_two_col_row`'s star label a full page
|
||||
# width to stretch into, which is what the 2026-09-04 review (WPA655)
|
||||
# reported as the '%' symbol on `ps_loss_humidity_pct` "floating mid-row
|
||||
# far from its editor" -- the symbol and editor are still adjacent (see
|
||||
# `script.py`'s `_two_col_row`), but a page-wide row puts that pair a
|
||||
# long way from the label a reader's eye is anchored to. Splitting the
|
||||
# four fields into two narrower columns, the same way every other group
|
||||
# on this page already does, keeps the whole row -- label, symbol, and
|
||||
# editor together -- inside one eye span.
|
||||
blocks.append(_heading('Prestress Losses', 2))
|
||||
blocks.extend(_group_or_sentence(
|
||||
values, _PRESTRESS_LOSS_FIELDS,
|
||||
'No prestress loss assumptions entered for this panel.',
|
||||
lambda: [_cols([
|
||||
[F('ps_loss_method'), F('ps_loss_humidity_pct')],
|
||||
[F('ps_loss_release_hours'), F('ps_loss_erection_days')],
|
||||
])]))
|
||||
blocks.append(_rule())
|
||||
|
||||
# -------------------------------------------------------------- Soil loads
|
||||
#
|
||||
# `sentence`, not `field` -- see the module docstring's "WHY EXTERIOR
|
||||
# SOIL / INTERIOR SOIL ARE `sentence` BLOCKS" for why there is nothing
|
||||
# to degrade to an editor for here.
|
||||
problems.append(
|
||||
'soil loads are not collected anywhere in this form yet; Exterior '
|
||||
'Soil and Interior Soil always read as "no loads" until a later '
|
||||
'stage adds that input.')
|
||||
blocks.append(_cols([
|
||||
[_heading('Exterior Soil', 2),
|
||||
_sentence('Design contains no exterior soil loads.')],
|
||||
[_heading('Interior Soil', 2),
|
||||
_sentence('Design contains no interior soil loads.')],
|
||||
]))
|
||||
blocks.append(_rule())
|
||||
|
||||
# ----------------------------------------------------------- Load Combos
|
||||
blocks.append(_heading('Load Combinations', 1))
|
||||
blocks.append(_cols([
|
||||
[_heading('Strength Combinations', 2),
|
||||
_combo_table('strength', values, problems),
|
||||
F('design_criteria.min_earth_factor_strength')],
|
||||
[_heading('Service Combinations', 2),
|
||||
_combo_table('service', values, problems),
|
||||
F('design_criteria.min_earth_factor_service')],
|
||||
]))
|
||||
blocks.append(_rule())
|
||||
|
||||
# --------------------------------------------------------------- Settings
|
||||
blocks.append(_heading('Settings', 1))
|
||||
col_a = [
|
||||
_heading('Deflection Limits', 2),
|
||||
F('design_criteria.deflection_add_bow'),
|
||||
F('design_criteria.deflection_additional_bow_in'),
|
||||
F('deflection_limit'),
|
||||
F('design_criteria.deflection_check_cantilever'),
|
||||
F('design_criteria.deflection_cantilever_limit'),
|
||||
_heading('Structural Analysis', 2),
|
||||
F('second_order'),
|
||||
F('cracking_assumption'),
|
||||
F('design_criteria.analysis_beta_d'),
|
||||
F('design_criteria.cracking_method'),
|
||||
F('design_criteria.cracking_limit_sqrt_fc'),
|
||||
F('design_criteria.rvalue_calc_enabled'),
|
||||
] + _group_or_sentence(
|
||||
values, ('design_criteria.rvalue_calc_enabled',),
|
||||
'R-value is not being calculated for this panel.', lambda: []) + [
|
||||
F('design_criteria.min_reinf_checked'),
|
||||
] + _group_or_sentence(
|
||||
values, ('design_criteria.min_reinf_checked',),
|
||||
'Minimum steel is not being checked for this panel.', lambda: [])
|
||||
|
||||
col_b = [
|
||||
_heading('Stress Options', 2),
|
||||
F('design_criteria.stress_failure_if_cracked'),
|
||||
F('design_criteria.stress_ignore_lateral_ecc'),
|
||||
F('design_criteria.stress_use_principal_axis'),
|
||||
_heading('In-Place Stress Limits', 2),
|
||||
F('inplace_tension_sqrt'),
|
||||
F('design_criteria.inplace_modulus_rupture_sqrt_fc'),
|
||||
F('inplace_comp_ratio'),
|
||||
_heading('Handling Stress Limits', 2),
|
||||
F('handling_tension_sqrt'),
|
||||
F('handling_comp_ratio'),
|
||||
_heading('Flexure Options', 2),
|
||||
F('design_criteria.flexure_method'),
|
||||
F('design_criteria.flexure_ignored_end_distance_in'),
|
||||
F('design_criteria.flexure_cracking_check'),
|
||||
]
|
||||
|
||||
col_c = [
|
||||
_heading('Resistance Factors', 2),
|
||||
# A `table`, not the `columns`-of-`field`s this used to be -- the
|
||||
# 2026-09-04 review (WPA655) found every phi editor rendered with
|
||||
# zero width. That old layout was two `columns` sub-columns
|
||||
# ("Developed" | "Transferring") of `field` blocks, nested inside
|
||||
# `col_c` itself one of three columns -- two levels of halving, each
|
||||
# `field` needing the renderer's fixed ~450px `_two_col_row` it never
|
||||
# had a fraction of. `_render_table` puts a cell's editor in an
|
||||
# ordinary (unconstrained) grid column instead, so there is always
|
||||
# room. See the module docstring's 2026-09-04 update for why `_cell`
|
||||
# still routes every key through `F()` first (it is what keeps the
|
||||
# existing "no backing field yet" `problems` bookkeeping honest).
|
||||
_table(
|
||||
[u'φ', 'Developed', 'Transferring'],
|
||||
[
|
||||
['Bending', _cell(F('phi_flexure')),
|
||||
_cell(F('design_criteria.phi_tension_transfer'))],
|
||||
['Compression', _cell(F('design_criteria.phi_compression')),
|
||||
'Not applicable once the strand has fully transferred.'],
|
||||
['Shear', _cell(F('phi_shear')),
|
||||
_cell(F('design_criteria.phi_shear_transfer'))],
|
||||
],
|
||||
),
|
||||
_heading('Development Multipliers', 2),
|
||||
F('dev_mult'),
|
||||
F('design_criteria.dev_mult_debonded'),
|
||||
F('design_criteria.dev_mult_cut'),
|
||||
F('design_criteria.dev_mult_mild'),
|
||||
_heading('Shear Options', 2),
|
||||
F('design_criteria.shear_treat_as_column'),
|
||||
_heading('Axial Options', 2),
|
||||
F('design_criteria.axial_ignore_tensile_failures'),
|
||||
F('design_criteria.axial_ignore_buckling_failures'),
|
||||
]
|
||||
|
||||
blocks.append(_cols([col_a, col_b, col_c]))
|
||||
|
||||
return blocks, problems
|
||||
Reference in New Issue
Block a user