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.
This commit is contained in:
@@ -0,0 +1,788 @@
|
||||
# -*- 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`, `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, by contrast, use `table` for real: every row is a
|
||||
fixed ACI 318-14 coefficient (public code text, not this job's data -- see
|
||||
"psi vs. ksi" below for the same reasoning applied to a different table) and
|
||||
the only per-row editable thing is the Include tick, which the `table`
|
||||
vocabulary already supports natively.
|
||||
|
||||
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, e.g. '
|
||||
'"ACI 318-14". Leave blank to leave it unstated.',
|
||||
False, 'text', None),
|
||||
# ------------------------------------------- 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),
|
||||
'conc_eci_psi': (
|
||||
'Concrete elastic modulus at release', u"E′ci (psi)",
|
||||
u"How stiff the concrete is at release strength. Not yet used by "
|
||||
u"the solve. Leave blank if unknown -- it can be computed from "
|
||||
u"f′ci once a rule for that is agreed.",
|
||||
False, 'num', None),
|
||||
'conc_ec_psi': (
|
||||
'Concrete elastic modulus, in-place', u"E′c (psi)",
|
||||
u"How stiff the concrete is once fully cured. Not yet used by the "
|
||||
u"solve. Leave blank if unknown.",
|
||||
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),
|
||||
# -------------------------------------------------------- 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. Not yet used by the solve.',
|
||||
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, e.g. "75" for L/75.',
|
||||
False, 'text', None),
|
||||
# ---------------------------------------------- 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),
|
||||
'design_criteria.analysis_beta_d': (
|
||||
u'Sustained-load ratio for 2nd-order analysis', u'βd',
|
||||
'The fraction of the axial load that is sustained (long-term), '
|
||||
'used by the 2nd-order analysis above. Leave blank if not '
|
||||
'applicable.',
|
||||
False, 'num', None),
|
||||
'design_criteria.cracking_method': (
|
||||
'Cracking method', '',
|
||||
'Which published method decides whether the section is treated as '
|
||||
'cracked. Leave blank if not applicable.',
|
||||
False, 'text', None),
|
||||
'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),
|
||||
'design_criteria.inplace_modulus_rupture_sqrt_fc': (
|
||||
'Modulus of rupture, in place', 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': (
|
||||
'Compression allowed once erected', u"n·f′c",
|
||||
'The compression limit for the panel once it is standing in '
|
||||
'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 (e.g. a stress-block '
|
||||
'method). Leave blank to use the built-in default.',
|
||||
False, 'text', None),
|
||||
'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, 'text', None),
|
||||
# ------------------------------------------------ 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_days', '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_days'] = (
|
||||
'Days from casting to release', '',
|
||||
'How many days 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 _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. Kept as a fixed reference table (see the module docstring's
|
||||
# "WHY THE 3-COLUMN CONCRETE GRID ... `columns`, NOT `table`" note for why
|
||||
# THIS table, unlike the material grids, is a real `table` block): the only
|
||||
# thing a user edits per row is which combinations apply, via the Include
|
||||
# tick, and `table` already carries a native check-editor cell for that.
|
||||
_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, rows, problems):
|
||||
table_rows = []
|
||||
for row in rows:
|
||||
n = row[0]
|
||||
include_key = 'load_combo.{0}.{1}.include'.format(name, n)
|
||||
table_rows.append(list(row) + [{'editor': 'check', 'key': include_key}])
|
||||
problems.append(
|
||||
"the Include tick boxes in the {0} load-combination table have no "
|
||||
"backing fields yet (load_combo.{0}.<n>.include) -- shown as empty "
|
||||
"check boxes.".format(name))
|
||||
return {'kind': 'table', 'headers': list(_COMBO_HEADERS),
|
||||
'header_groups': None, 'rows': table_rows, '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),
|
||||
_sentence('No mesh material properties are collected on this '
|
||||
'form yet.')],
|
||||
[_heading('Strand', 3), F('ps_fpu_ksi'), F('strand_ep_ksi')],
|
||||
]))
|
||||
blocks.append(_rule())
|
||||
|
||||
# ---------------------------------------------------------- Prestress Loss
|
||||
blocks.append(_cols([
|
||||
[_heading('Prestress Losses', 2)] +
|
||||
_group_or_sentence(
|
||||
values, _PRESTRESS_LOSS_FIELDS,
|
||||
'No prestress loss assumptions entered for this panel.',
|
||||
lambda: [F(k) for k in _PRESTRESS_LOSS_FIELDS]),
|
||||
]))
|
||||
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', _STRENGTH_COMBOS, problems),
|
||||
F('design_criteria.min_earth_factor_strength')],
|
||||
[_heading('Service Combinations', 2),
|
||||
_combo_table('service', _SERVICE_COMBOS, 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('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('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),
|
||||
_cols([
|
||||
[_heading('Developed', 3),
|
||||
F('phi_flexure'), F('design_criteria.phi_compression'),
|
||||
F('phi_shear')],
|
||||
[_heading('Transferring', 3),
|
||||
F('design_criteria.phi_tension_transfer'),
|
||||
_sentence('Not applicable for compression once the strand '
|
||||
'has fully transferred.'),
|
||||
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