# -*- 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..`, see `stage_field_key`). New keys here that do not fit a single flat name follow the same shape: `design_criteria.` for one-off Settings/Project fields (e.g. `design_criteria.analysis_beta_d`), and `load_combo...include` / `load_combo..min_earth_factor` for the Load Combinations grid, mirroring `stage_field_key`'s `..` 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}..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 " 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