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,528 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The Panel Analysis form's "Handling" section, rendered as generic blocks.
|
||||
|
||||
WHY THIS EXISTS: the owner has now said three times that the Panel Analysis
|
||||
input form is too ugly and unreadable to use ("太丑了", "你这么输入用户上哪儿能
|
||||
看懂"), and the last ruling was blunt about the fix -- "全部给我做成普通人能看懂
|
||||
的样子", make every part of it something an ordinary person can understand. The
|
||||
"Handling" section is the worst offender: it used to be three lonely text
|
||||
boxes (`strip_mult` / `transport_mult` / `erection_mult`) even though Eriksson
|
||||
Wall's own printed page for this section is a whole per-stage MATRIX --
|
||||
eleven questions asked once per handling stage, plus lifter/dunnage tables and
|
||||
a plan drawing. `vibe.panelanalysisform` was rebuilt first (its own docstring
|
||||
covers that ruling and the Eriksson-parity one before it) and now exposes a
|
||||
real stage matrix (`STAGES`, `STAGE_OPTIONS`, `stage_matrix`,
|
||||
`stage_field_key`). This module is the second half: turning that data plus
|
||||
the plain-language field metadata into the flat, renderer-agnostic BLOCK list
|
||||
a generic WPF page consumes, matching Eriksson's own page 25/26 layout
|
||||
(Stage Options matrix + legend + weight/centroid + plan drawing + lifter and
|
||||
dunnage tables + Lifting Hardware) as closely as the two modules' real data
|
||||
will honestly support.
|
||||
|
||||
THE BLOCK VOCABULARY IS FIXED and belongs to the page renderer, not to this
|
||||
module -- 'heading' / 'columns' / 'field' / 'matrix' / 'table' / 'legend' /
|
||||
'kv' / 'sentence' / 'drawing' / 'rule' / 'space', nothing else. An unknown
|
||||
`kind` renders as nothing, so a typo here is a silently blank section rather
|
||||
than a crash; `page()` is tested against exactly that vocabulary for that
|
||||
reason.
|
||||
|
||||
READ `vibe.panelanalysisform` DEFENSIVELY. Another agent owns that module and
|
||||
was mid-restructure of exactly the stage matrix this file depends on while
|
||||
this file was written. Every accessor this module leans on --
|
||||
`STAGES` / `STAGE_OPTIONS` / `stage_field_key` / `FIELDS` / `label_of` /
|
||||
`symbol_of` / `help_of` -- is reached through `getattr`/`hasattr` or a
|
||||
try/except, never assumed. A shape that is not there yet (or has changed
|
||||
shape) becomes one plain-English sentence in `problems`, and the rest of the
|
||||
page still renders around the hole -- never a raised exception, and never an
|
||||
invented value standing in for the one that could not be read. This is the
|
||||
same "could not judge" vs "judged, found nothing" discipline as everywhere
|
||||
else in this codebase (see CLAUDE.md SS3): a missing accessor is reported by
|
||||
name, not silently skipped and not guessed at.
|
||||
|
||||
WHAT COULD NOT BE BUILT FROM REAL DATA, and why it is a `problems` entry
|
||||
instead of a fabricated block: Eriksson's own "Handling" pages carry several
|
||||
things `vibe.panelanalysisform` does not model yet --
|
||||
|
||||
* a per-lifter STRUCTURED row (quantity, longitudinal position, two
|
||||
transverse positions, a tick per stage, "on top face") for Face / Side
|
||||
Edge Lifters. The form only has `face_lifters` / `edge_lifters` as two
|
||||
free-text fields ("column @ fraction of width", one line per lifter), so
|
||||
the structured table is rendered as those two text fields instead of an
|
||||
invented set of per-row keys nothing in the form can parse or validate.
|
||||
* a dedicated "top edge lifters" field. There isn't one -- only
|
||||
`face_lifters` / `edge_lifters` exist -- so the section renders a
|
||||
sentence saying so, in the same voice Eriksson's own page uses for "no
|
||||
top edge lifters on this design" (never a blank, per CLAUDE.md SS3's
|
||||
"nothing here" vs "never asked" rule -- an omitted field and an empty
|
||||
one must not look the same).
|
||||
* a "Dunnage" field of any kind. Same treatment.
|
||||
* the computed Panel Weight / Centroid block. `weight_lb` is a real field
|
||||
(the user's override, or what the model could read) so its row renders
|
||||
from `values`/`reads`; the three centroid components (X from start, Y
|
||||
from bottom, Z from left) are not produced anywhere in this pipeline yet,
|
||||
so those rows say so in plain words rather than printing a fabricated
|
||||
0.000.
|
||||
* the "Lifting Configurations" sketches on Eriksson's second page (one
|
||||
small diagram per included stage, each with its own G/CG marker) and the
|
||||
"Face Lifters / Name / Capacity Method" block above them. No
|
||||
`panelsketch` entry point for either exists (only `plan` and
|
||||
`weight_diagram` do, per the sibling agent adding them), and no
|
||||
panelanalysisform field carries a lifter name or capacity-check method --
|
||||
so both become one `problems` entry apiece and a short explanatory
|
||||
sentence, not an invented drawing source or an invented field.
|
||||
|
||||
THE STAGE MATRIX'S "DOES NOT APPLY" COLUMNS are a STRUCTURAL fact about which
|
||||
questions make sense for which stage, not a report of which stage happens to
|
||||
be ticked on right now. Reading Eriksson's own page 25 by column: Rigging
|
||||
Configuration / Sling Angle / Add Ground Reaction / Minimum-Maximum Tilt
|
||||
Angle / Assume Top Edge Lifters Carry Panel Weight are answered only for the
|
||||
two erection stages (Primary, Secondary) and dashed for Stripping and
|
||||
Transportation in EVERY sample seen, independent of whether a given erection
|
||||
stage is itself switched on -- a crane pick has a rigging configuration and a
|
||||
tilt range; a stripping pull or a truck ride does not. `_ERECTION_ONLY` below
|
||||
is that fixed structural map. It is deliberately NOT keyed off each stage's
|
||||
own `include` flag: the owner's rule for `panelanalysisform._parse_stage_matrix`
|
||||
is that switching a stage off must never make its OTHER cells un-requestable
|
||||
or drop what the user already typed into them, and printing an em dash over a
|
||||
cell the user could still type into would do exactly that. So the only cells
|
||||
this module ever marks `{'na': True}` are ones no stage state can turn back
|
||||
on; a merely-unticked stage's cells stay ordinary editable cells, matrix-wide.
|
||||
|
||||
Standard-library only. No Revit, no WPF, no `panelsketch` import (a source
|
||||
name is enough -- see the block vocabulary's 'drawing' kind). Runs on
|
||||
IronPython 2.7 in the button and on plain CPython in `tests/`: no f-strings,
|
||||
no dataclasses/typing/enum, `'{0}'.format(...)` throughout.
|
||||
"""
|
||||
|
||||
# Editor kind a FIELDS-style `kind` renders as, for the plain fields this
|
||||
# section pulls straight from `vibe.panelanalysisform.FIELDS` (weight_lb,
|
||||
# self_weight_factor, face_lifters, edge_lifters). Anything not listed here
|
||||
# falls back to a free-text box -- 'supports' / 'openings' / 'strands' are
|
||||
# all typed as structured-but-still-one-text-box fields on the real form, and
|
||||
# nothing in the Handling section uses them, but the fallback keeps this
|
||||
# honest if that ever changes.
|
||||
_EDITOR_OF_KIND = {
|
||||
'text': 'text',
|
||||
'num': 'num',
|
||||
'int': 'num',
|
||||
'supports': 'text',
|
||||
'openings': 'text',
|
||||
'strands': 'text',
|
||||
}
|
||||
|
||||
# Fallback choice vocabulary for the free-text stage-matrix options, used
|
||||
# ONLY when `STAGE_OPTIONS`'s own row is the older 6-tuple shape with no
|
||||
# choices slot of its own (see `_unpack_stage_option`). Once the row carries
|
||||
# its own choices, that is authoritative and this is never consulted -- two
|
||||
# sources of truth for the same list would drift. These are OFFERED choices,
|
||||
# never a default -- the current value still comes from whatever is in
|
||||
# `values[key]`, blank if nothing was typed. Picking one is the user's
|
||||
# decision; the owner's instruction is explicit that none of them stand in
|
||||
# as a default.
|
||||
_STAGE_CHOICES_FALLBACK = {
|
||||
'lift_type': ['Static Lift', 'Rolling Block'],
|
||||
'concrete_strength': ['Initial', 'In-Place'],
|
||||
'stress_limits': ['Handling', 'In-Place'],
|
||||
'rigging': ['Auto'],
|
||||
}
|
||||
|
||||
|
||||
def _unpack_stage_option(opt):
|
||||
"""(opt_key, label, symbol, help, kind, choices) from one STAGE_OPTIONS
|
||||
row, or None if the row cannot be read at all.
|
||||
|
||||
Defensive against exactly the kind of mid-refactor shape change this
|
||||
module was written under: `STAGE_OPTIONS` grew a seventh `choices` slot
|
||||
on live disk between this module's first draft and this test run (a
|
||||
7-tuple with the new slot, where the 6-tuple it replaced had none). Both
|
||||
shapes are accepted; `choices` falls back to
|
||||
`_STAGE_CHOICES_FALLBACK` only for the older shape, and is `None`/`[]`
|
||||
for an option with no fixed vocabulary either way. `drives` (index 5 of
|
||||
the 7-tuple) is not needed here -- NA-ness is decided structurally by
|
||||
`_ERECTION_ONLY`, not by `drives` -- so it is simply not unpacked."""
|
||||
try:
|
||||
opt_key, label, symbol, help_text, kind, _drives, choices = opt
|
||||
choices = list(choices) if choices else None
|
||||
return opt_key, label, symbol, help_text, kind, choices
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
try:
|
||||
opt_key, label, symbol, help_text, kind, _drives = opt
|
||||
choices = _STAGE_CHOICES_FALLBACK.get(opt_key)
|
||||
return opt_key, label, symbol, help_text, kind, choices
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
# The stage-matrix options that only make sense for a stage where the panel
|
||||
# is actually being picked up and set (Primary / Secondary Erection). See the
|
||||
# module docstring's "STRUCTURAL fact" paragraph -- this is NOT about whether
|
||||
# a given stage is ticked on.
|
||||
_ERECTION_ONLY = set([
|
||||
'rigging', 'sling_angle', 'ground_reaction',
|
||||
'min_tilt_deg', 'max_tilt_deg', 'top_crane_carries',
|
||||
])
|
||||
_NOT_ERECTION_STAGES = set(['Stripping', 'Transportation'])
|
||||
|
||||
# Fields this section pulls straight out of `vibe.panelanalysisform.FIELDS`
|
||||
# by key, grouped the way this page presents them. Every key here is checked
|
||||
# against the real module before use -- see `_field_block`.
|
||||
_LOAD_FACTOR_KEYS = ('weight_lb', 'self_weight_factor')
|
||||
_LIFTER_TEXT_KEYS = ('face_lifters', 'edge_lifters')
|
||||
|
||||
|
||||
def _import_paf():
|
||||
"""The real `vibe.panelanalysisform`, or (None, reason).
|
||||
|
||||
A separate function so `page()` reads like the rest of this module: one
|
||||
early check, one plain-English reason, never a raised ImportError.
|
||||
"""
|
||||
try:
|
||||
from vibe import panelanalysisform as paf
|
||||
except ImportError as exc:
|
||||
return None, ('vibe.panelanalysisform could not be imported '
|
||||
'({0}); the Handling section cannot be built at '
|
||||
'all'.format(exc))
|
||||
return paf, None
|
||||
|
||||
|
||||
def _field_exists(paf, key):
|
||||
"""Whether `key` is a real entry in `paf.FIELDS` right now.
|
||||
|
||||
Never assumes `FIELDS` itself is present or list-shaped -- the module
|
||||
this reads from is being restructured by another agent as this is
|
||||
written.
|
||||
"""
|
||||
fields = getattr(paf, 'FIELDS', None)
|
||||
if not fields:
|
||||
return False
|
||||
try:
|
||||
for f in fields:
|
||||
if isinstance(f, dict) and f.get('key') == key:
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def _field_kind(paf, key):
|
||||
for f in getattr(paf, 'FIELDS', None) or []:
|
||||
if isinstance(f, dict) and f.get('key') == key:
|
||||
return f.get('kind') or 'text', bool(f.get('required'))
|
||||
return 'text', False
|
||||
|
||||
|
||||
def _field_block(paf, key):
|
||||
"""A `{'kind': 'field', ...}` block for a real `panelanalysisform` key.
|
||||
|
||||
Caller must have already confirmed `_field_exists(paf, key)`. Labels,
|
||||
symbols and hints all come from the module's own accessors rather than
|
||||
being re-typed here, so this page can never drift from the field it is
|
||||
describing -- and those accessors already degrade to the bare key if
|
||||
something about the field record is missing, so this cannot raise.
|
||||
"""
|
||||
label = paf.label_of(key) if hasattr(paf, 'label_of') else key
|
||||
symbol = paf.symbol_of(key) if hasattr(paf, 'symbol_of') else ''
|
||||
hint = paf.help_of(key) if hasattr(paf, 'help_of') else ''
|
||||
kind, required = _field_kind(paf, key)
|
||||
editor = _EDITOR_OF_KIND.get(kind, 'text')
|
||||
return {
|
||||
'kind': 'field', 'key': key, 'label': label,
|
||||
'symbol': symbol or None, 'hint': hint or None,
|
||||
'required': required, 'editor': editor, 'choices': None,
|
||||
}
|
||||
|
||||
|
||||
def _row_label(label, symbol):
|
||||
"""'label — symbol', matching `panelanalysisform.display_label`'s own
|
||||
join -- the symbol rides beside the plain English, never replaces it."""
|
||||
if symbol:
|
||||
return u'{0} — {1}'.format(label, symbol)
|
||||
return label
|
||||
|
||||
|
||||
def _stage_options_editor(kind):
|
||||
if kind == 'bool':
|
||||
return 'check'
|
||||
if kind == 'num':
|
||||
return 'num'
|
||||
return 'text'
|
||||
|
||||
|
||||
def _stage_matrix_block(paf, problems):
|
||||
"""The Stage Options matrix block, or None if it cannot be built.
|
||||
|
||||
Reads `STAGES` and `STAGE_OPTIONS` off `paf` defensively -- each missing
|
||||
or malformed piece is one `problems` sentence naming it, and a row that
|
||||
cannot be unpacked is skipped rather than raising, so a partial
|
||||
restructure of the source module degrades this one matrix instead of the
|
||||
whole page.
|
||||
"""
|
||||
stages = list(getattr(paf, 'STAGES', ()) or ())
|
||||
if not stages:
|
||||
problems.append('vibe.panelanalysisform has no STAGES list yet; '
|
||||
'the Stage Options matrix cannot be built')
|
||||
return None
|
||||
options = getattr(paf, 'STAGE_OPTIONS', None)
|
||||
if not options:
|
||||
problems.append('vibe.panelanalysisform has no STAGE_OPTIONS list '
|
||||
'yet; the Stage Options matrix cannot be built')
|
||||
return None
|
||||
stage_field_key = getattr(paf, 'stage_field_key', None)
|
||||
if not callable(stage_field_key):
|
||||
problems.append('vibe.panelanalysisform has no stage_field_key() '
|
||||
'yet; the Stage Options matrix cells have no keys '
|
||||
'to write to')
|
||||
return None
|
||||
|
||||
rows = []
|
||||
for opt in options:
|
||||
unpacked = _unpack_stage_option(opt)
|
||||
if unpacked is None:
|
||||
problems.append('a Handling stage option row has an unexpected '
|
||||
'shape ({0!r}); skipped'.format(opt))
|
||||
continue
|
||||
opt_key, label, symbol, help_text, kind, choices = unpacked
|
||||
editor = _stage_options_editor(kind)
|
||||
cells = []
|
||||
for stage in stages:
|
||||
if opt_key in _ERECTION_ONLY and stage in _NOT_ERECTION_STAGES:
|
||||
cells.append({'na': True})
|
||||
continue
|
||||
try:
|
||||
key = stage_field_key(stage, opt_key)
|
||||
except Exception as exc:
|
||||
# A broken key builder must not take the whole page down;
|
||||
# name it and leave that one cell blank instead.
|
||||
problems.append(
|
||||
'could not build the form key for {0} / {1} ({2}); that '
|
||||
'cell is left blank'.format(stage, label, exc))
|
||||
cells.append(None)
|
||||
continue
|
||||
cells.append({'editor': editor, 'key': key, 'choices': choices})
|
||||
rows.append({'label': _row_label(label, symbol),
|
||||
'hint': help_text or None, 'cells': cells})
|
||||
|
||||
if not rows:
|
||||
problems.append('every Handling stage option row was unreadable; '
|
||||
'the Stage Options matrix has nothing to show')
|
||||
return None
|
||||
return {'kind': 'matrix', 'columns': stages, 'rows': rows}
|
||||
|
||||
|
||||
def _legend_block():
|
||||
"""Eriksson's own two-column legend, redrawn generically.
|
||||
|
||||
Shape names ('triangle', 'bar') and fill names ('blank', 'light',
|
||||
'hatch', 'solid', 'tan') are markers for the renderer to draw, not
|
||||
engineering values -- there is nothing here for `panelanalysisform` to
|
||||
supply, so this block is static.
|
||||
"""
|
||||
return {
|
||||
'kind': 'legend',
|
||||
'groups': [
|
||||
('Shapes', [
|
||||
('triangle', 'Face lifter'),
|
||||
('bar', 'Dunnage'),
|
||||
]),
|
||||
('Fills', [
|
||||
('blank', 'Not specified'),
|
||||
('light', 'Stripping'),
|
||||
('hatch', 'Erection'),
|
||||
('solid', 'Stripping + Erection'),
|
||||
('tan', 'Transportation'),
|
||||
]),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _weight_centroid_block(paf, values, reads, problems):
|
||||
"""The Panel Weight / Centroid kv block.
|
||||
|
||||
Weight comes from the real `weight_lb` field -- the user's override, or
|
||||
whatever the model could read, exactly like every other prefilled field
|
||||
on this form -- converted to kips to match Eriksson's own display. The
|
||||
three centroid components are not computed anywhere in this pipeline
|
||||
yet, so they say that in plain words rather than printing a fabricated
|
||||
0.000; see the module docstring.
|
||||
"""
|
||||
weight_row = ('Panel weight', 'not entered yet -- computed from the '
|
||||
'geometry above once you run the analysis')
|
||||
if _field_exists(paf, 'weight_lb'):
|
||||
text = (values.get('weight_lb') or '').strip()
|
||||
if not text and reads:
|
||||
text = paf._fmt(reads.get('weight_lb')) if hasattr(paf, '_fmt') \
|
||||
else str(reads.get('weight_lb') or '')
|
||||
if text:
|
||||
try:
|
||||
kips = float(text) / 1000.0
|
||||
weight_row = ('Panel weight',
|
||||
'{0:.3f} kips (as entered)'.format(kips))
|
||||
except (TypeError, ValueError):
|
||||
weight_row = ('Panel weight',
|
||||
'{0} lb (as entered, not a plain number)'
|
||||
.format(text))
|
||||
else:
|
||||
problems.append('vibe.panelanalysisform has no weight_lb field; '
|
||||
'panel weight cannot be shown')
|
||||
|
||||
not_tracked = 'not tracked on this form yet'
|
||||
problems.append('panel centroid (X from start / Y from bottom / Z '
|
||||
'from left) is not computed anywhere in this pipeline '
|
||||
'yet; the Panel Weight / Centroid block shows weight '
|
||||
'only')
|
||||
return {
|
||||
'kind': 'kv',
|
||||
'rows': [
|
||||
weight_row,
|
||||
('Centroid, X (from start)', not_tracked),
|
||||
('Centroid, Y (from bottom)', not_tracked),
|
||||
('Centroid, Z (from left)', not_tracked),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _lifter_section_blocks(paf, problems):
|
||||
"""Face / Side Edge Lifters, Top Edge Lifters, Dunnage -- three blocks.
|
||||
|
||||
Only `face_lifters` / `edge_lifters` are real fields; the structured
|
||||
per-lifter table Eriksson prints (qty, two positions, a tick per stage)
|
||||
has no backing data model here, and "top edge lifters" / "dunnage" have
|
||||
no field at all. See the module docstring for why each of those is a
|
||||
sentence and a `problems` entry rather than an invented table.
|
||||
"""
|
||||
blocks = [{'kind': 'heading', 'text': 'Face / Side Edge Lifters',
|
||||
'level': 2}]
|
||||
any_lifter_field = False
|
||||
for key in _LIFTER_TEXT_KEYS:
|
||||
if _field_exists(paf, key):
|
||||
blocks.append(_field_block(paf, key))
|
||||
any_lifter_field = True
|
||||
else:
|
||||
problems.append('vibe.panelanalysisform has no {0} field; that '
|
||||
'lifter position cannot be entered'.format(key))
|
||||
if any_lifter_field:
|
||||
blocks.append({
|
||||
'kind': 'sentence',
|
||||
'text': ('Eriksson prints one row per lifter with a quantity, '
|
||||
'two positions and a tick per stage; this form does '
|
||||
'not yet parse the text above into that shape, so '
|
||||
'each line is kept as typed.'),
|
||||
})
|
||||
|
||||
blocks.append({'kind': 'heading', 'text': 'Top Edge Lifters', 'level': 2})
|
||||
if _field_exists(paf, 'top_edge_lifters'):
|
||||
# Not expected today, but if a future restructure adds the field,
|
||||
# render it rather than silently keeping the old sentence.
|
||||
blocks.append(_field_block(paf, 'top_edge_lifters'))
|
||||
else:
|
||||
problems.append('vibe.panelanalysisform has no top edge lifters '
|
||||
'field; this section always reads as empty')
|
||||
blocks.append({'kind': 'sentence',
|
||||
'text': 'Design contains no top edge lifters.'})
|
||||
|
||||
blocks.append({'kind': 'heading', 'text': 'Dunnage', 'level': 2})
|
||||
if _field_exists(paf, 'dunnage'):
|
||||
blocks.append(_field_block(paf, 'dunnage'))
|
||||
else:
|
||||
problems.append('vibe.panelanalysisform has no dunnage field; '
|
||||
'this section always reads as empty')
|
||||
blocks.append({'kind': 'sentence',
|
||||
'text': 'Design contains no dunnage entered.'})
|
||||
return blocks
|
||||
|
||||
|
||||
def _lifting_hardware_blocks(problems):
|
||||
"""Eriksson page 26: the weight-distribution drawing plus a note on what
|
||||
could not be carried over (see the module docstring's last bullet)."""
|
||||
blocks = [
|
||||
{'kind': 'heading', 'text': 'Lifting Hardware', 'level': 2},
|
||||
{'kind': 'drawing', 'source': 'weight_diagram',
|
||||
'caption': 'PANEL WEIGHT DISTRIBUTION', 'w': 760, 'h': 220},
|
||||
]
|
||||
problems.append(
|
||||
'Eriksson also prints a small lifting-configuration sketch per '
|
||||
'included stage (a G/CG marker over each stage\'s lift points) '
|
||||
'and a Face Lifters name / capacity-check method block above the '
|
||||
'weight distribution; neither has a panelsketch drawing source or '
|
||||
'a panelanalysisform field yet, so neither is shown')
|
||||
return blocks
|
||||
|
||||
|
||||
def page(values, reads=None):
|
||||
"""(blocks, problems) for the Handling section of the Panel Analysis form.
|
||||
|
||||
`values` is the form's current string-valued dict, read-only here -- this
|
||||
function never assigns into it, so the caller's dict comes back
|
||||
unchanged no matter what this renders. `reads` is the optional dict of
|
||||
what the model could read off the framing element (the same shape
|
||||
`panelanalysisform.prefill` takes); it is used only as a fallback for the
|
||||
weight row when `values` itself is blank, and its absence never raises.
|
||||
|
||||
Returns a flat list of block dicts and a list of plain-English problem
|
||||
sentences -- never raises, per the module docstring's "could not judge"
|
||||
discipline. An empty `problems` list means every accessor this section
|
||||
depends on was present and shaped as expected; a non-empty one names
|
||||
every gap, so the caller can decide whether to surface it to the user
|
||||
(a stale accessor is worth knowing about even though the page itself
|
||||
still renders around it).
|
||||
"""
|
||||
problems = []
|
||||
paf, reason = _import_paf()
|
||||
if paf is None:
|
||||
problems.append(reason)
|
||||
return ([{'kind': 'heading', 'text': 'Handling', 'level': 1},
|
||||
{'kind': 'sentence',
|
||||
'text': ('The Handling section could not be built -- see '
|
||||
'the problem above.')}],
|
||||
problems)
|
||||
|
||||
values = values or {}
|
||||
blocks = [{'kind': 'heading', 'text': 'Handling', 'level': 1}]
|
||||
|
||||
# --- Stage Options matrix -------------------------------------------
|
||||
blocks.append({'kind': 'heading', 'text': 'Stage Options', 'level': 2})
|
||||
matrix_block = _stage_matrix_block(paf, problems)
|
||||
if matrix_block is not None:
|
||||
blocks.append(matrix_block)
|
||||
else:
|
||||
blocks.append({'kind': 'sentence',
|
||||
'text': ('The per-stage handling options could not '
|
||||
'be built -- see the problem above.')})
|
||||
blocks.append({'kind': 'space', 'h': 8})
|
||||
blocks.append(_legend_block())
|
||||
blocks.append({'kind': 'space', 'h': 8})
|
||||
|
||||
# --- Panel Weight / Centroid ------------------------------------------
|
||||
blocks.append({'kind': 'heading', 'text': 'Panel Weight & Centroid',
|
||||
'level': 3})
|
||||
blocks.append(_weight_centroid_block(paf, values, reads, problems))
|
||||
blocks.append({'kind': 'rule'})
|
||||
|
||||
# --- Load factors (weight override / self-weight factor) --------------
|
||||
load_factor_blocks = []
|
||||
for key in _LOAD_FACTOR_KEYS:
|
||||
if _field_exists(paf, key):
|
||||
load_factor_blocks.append(_field_block(paf, key))
|
||||
else:
|
||||
problems.append('vibe.panelanalysisform has no {0} field'
|
||||
.format(key))
|
||||
if load_factor_blocks:
|
||||
blocks.append({'kind': 'heading', 'text': 'Load Factors',
|
||||
'level': 3})
|
||||
blocks.extend(load_factor_blocks)
|
||||
blocks.append({'kind': 'space', 'h': 8})
|
||||
|
||||
# --- plan drawing -------------------------------------------------
|
||||
blocks.append({'kind': 'drawing', 'source': 'plan',
|
||||
'caption': 'LIFTER AND DUNNAGE DETAIL', 'w': 760,
|
||||
'h': 420})
|
||||
blocks.append({'kind': 'space', 'h': 8})
|
||||
|
||||
# --- lifter / dunnage sections -----------------------------------
|
||||
blocks.extend(_lifter_section_blocks(paf, problems))
|
||||
blocks.append({'kind': 'rule'})
|
||||
|
||||
# --- Lifting Hardware (page 26) ------------------------------------
|
||||
blocks.extend(_lifting_hardware_blocks(problems))
|
||||
|
||||
return blocks, problems
|
||||
Reference in New Issue
Block a user