WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Panel Analysis: Top/Bottom Elevation and in-place Restraints, PURE.
|
||||
|
||||
Owner, 2026-09-04 (WPA655's morning run came up blank on `top_elevation_ft`,
|
||||
`bottom_elevation_ft` and `restraints` -- see runlog): Eriksson's Concrete
|
||||
Extents page carries Top Elevation / Bottom Elevation, and the Structural
|
||||
Model page's in-place restraints are typically the same embed positions
|
||||
`supports_ft` already found, just reported as elevations instead of a
|
||||
distance up from the lower end. Both are arithmetic over numbers the caller
|
||||
already measured -- no Revit API here, so this module is host-tested like
|
||||
`pa_supports` / `pa_lifters` / `pa_reinf`, and the live half (reading a
|
||||
framing element's world-space bounding box, in `script.py`'s `_read_
|
||||
elevations`) stays a thin Revit-touching wrapper around it.
|
||||
|
||||
WHERE THE ELEVATION NUMBER COMES FROM. `elevations_from_bbox` takes the
|
||||
framing element's AXIS-ALIGNED WORLD bounding box Z extent
|
||||
(`el.get_BoundingBox(None)`), already in decimal feet the way every Revit Z
|
||||
coordinate is -- the same "project elevation" a schedule or a section view
|
||||
shows for anything placed in the model, because Revit's internal origin
|
||||
already IS the datum those numbers are measured from. This is the piece AS
|
||||
MODELLED, not a hypothetical erected attitude the way `pa_supports` /
|
||||
`liftexport.erected_orientation` derive one for a piece that is still lying
|
||||
flat on paper -- a wall panel placed in a real building model, the way this
|
||||
tool's precast jobs use Revit, is modelled standing up in place, so its own
|
||||
bounding box IS its in-place elevation. A piece not actually modelled
|
||||
in-place (still on the casting-bed sheet, or a horizontal working view) gives
|
||||
a bounding box that is NOT an elevation, and this module cannot tell the
|
||||
difference -- callers name that limitation in the basis text they build
|
||||
around this, never silently here.
|
||||
|
||||
WHERE RESTRAINTS COME FROM. `restraints_from_supports` re-expresses the same
|
||||
`supports_ft` positions `pa_supports.read_supports` already measured (feet
|
||||
from the erected-lower end) as elevations, adding the panel's own
|
||||
`bottom_elevation_ft`. It is NOT a second measurement -- a plant that names
|
||||
no support families leaves `supports_ft` blank, and this function then has
|
||||
nothing to convert and returns `(None, why)` rather than fabricating an
|
||||
elevation from zero.
|
||||
|
||||
Written for IronPython 2.7: no f-strings, no pathlib, `'{0}'.format(...)`
|
||||
throughout.
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
|
||||
def elevations_from_bbox(min_z_ft, max_z_ft, length_ft=None, tol_ft=0.1):
|
||||
"""(top_ft, bottom_ft, height_ft, basis) off a world-space bounding box.
|
||||
|
||||
`min_z_ft` / `max_z_ft` are the two Z extents of the element's own
|
||||
bounding box (`el.get_BoundingBox(None)`), in any order -- the smaller
|
||||
becomes `bottom_ft`, the larger `top_ft`. Either missing (`None`) is the
|
||||
could-not-read answer: `(None, None, None, why)`, never a fabricated
|
||||
elevation.
|
||||
|
||||
`length_ft`, when given, is compared against the measured height
|
||||
(`top_ft - bottom_ft`): for a wall panel modelled standing up, the two
|
||||
should agree, and a real gap larger than `tol_ft` is named in the basis
|
||||
rather than silently absorbed -- it usually means the piece is not
|
||||
modelled fully upright, or `length_ft` was measured along a different
|
||||
axis than the one that is actually vertical here.
|
||||
"""
|
||||
if min_z_ft is None or max_z_ft is None:
|
||||
return None, None, None, ('could not read a world-space bounding '
|
||||
'box on this piece')
|
||||
bottom = round(min(min_z_ft, max_z_ft), 4)
|
||||
top = round(max(min_z_ft, max_z_ft), 4)
|
||||
height = round(top - bottom, 4)
|
||||
basis = ('the framing element\'s own world-space bounding box, Z {0:.2f}'
|
||||
' to {1:.2f} ft -- the piece AS MODELLED, not a hypothetical '
|
||||
'erected attitude'.format(bottom, top))
|
||||
if length_ft is not None:
|
||||
try:
|
||||
gap = abs(height - float(length_ft))
|
||||
except (TypeError, ValueError):
|
||||
gap = None
|
||||
if gap is not None and gap > tol_ft:
|
||||
basis += ('; this differs from the measured length ({0:.2f} ft) '
|
||||
'by {1:.2f} ft -- this piece may not be modelled '
|
||||
'fully upright'.format(float(length_ft), gap))
|
||||
return top, bottom, height, basis
|
||||
|
||||
|
||||
def restraints_from_supports(supports_ft_text, bottom_elevation_ft,
|
||||
height_ft=None, length_ft=None,
|
||||
stand_tol_ft=1.0):
|
||||
"""(text, basis) turning `supports_ft` positions into elevations, or
|
||||
`(None, why)` when there is nothing to convert.
|
||||
|
||||
`supports_ft_text` is the SAME comma-separated feet string `pa_supports.
|
||||
read_supports` writes into `reads['supports_ft']` (feet from the
|
||||
erected-lower end); `bottom_elevation_ft` is this panel's own bottom
|
||||
elevation (`elevations_from_bbox`'s third return). Either blank/None, or
|
||||
a token that will not parse as a number, comes back as `(None, why)` --
|
||||
never a partial or guessed conversion. The output is comma-separated,
|
||||
the same grammar `panelanalysisform.parse_supports` already reads back
|
||||
for both `supports_ft` and `restraints`.
|
||||
|
||||
`height_ft` / `length_ft`, when both given, guard against a piece that
|
||||
does NOT stand with its length vertical -- a flat slab's world-space
|
||||
bounding-box height is its THICKNESS, not its length, and adding a
|
||||
`supports_ft` position (measured along the length) to that height would
|
||||
not be an elevation at all. A gap bigger than `stand_tol_ft` between the
|
||||
two skips the conversion and says why, rather than emitting a number
|
||||
that looks like an elevation but mixes two different axes.
|
||||
"""
|
||||
if height_ft is not None and length_ft is not None:
|
||||
try:
|
||||
gap = abs(float(height_ft) - float(length_ft))
|
||||
except (TypeError, ValueError):
|
||||
gap = None
|
||||
if gap is not None and gap > stand_tol_ft:
|
||||
return None, (
|
||||
'this piece\'s bounding-box height ({0:.2f} ft) does not '
|
||||
'match its length ({1:.2f} ft) -- it does not appear to '
|
||||
'stand with its length vertical, so supports_ft is not a '
|
||||
'vertical offset here; restraints left blank rather than '
|
||||
'converting a horizontal position into an elevation'
|
||||
.format(float(height_ft), float(length_ft)))
|
||||
|
||||
if not supports_ft_text or bottom_elevation_ft is None:
|
||||
missing = []
|
||||
if not supports_ft_text:
|
||||
missing.append('supports_ft')
|
||||
if bottom_elevation_ft is None:
|
||||
missing.append('bottom_elevation_ft')
|
||||
return None, ('nothing to convert -- {0} not read'.format(
|
||||
' and '.join(missing)))
|
||||
|
||||
positions = []
|
||||
for tok in str(supports_ft_text).replace(',', ' ').split():
|
||||
try:
|
||||
positions.append(float(tok))
|
||||
except (TypeError, ValueError):
|
||||
return None, ('supports_ft carried a value that is not a '
|
||||
'number ({0!r}); restraints could not be '
|
||||
'converted'.format(tok))
|
||||
if not positions:
|
||||
return None, 'supports_ft parsed to no positions'
|
||||
|
||||
try:
|
||||
bottom = float(bottom_elevation_ft)
|
||||
except (TypeError, ValueError):
|
||||
return None, 'bottom_elevation_ft is not a number'
|
||||
|
||||
elevations = sorted(round(bottom + p, 4) for p in positions)
|
||||
text = ', '.join('{0:.2f}'.format(e) for e in elevations)
|
||||
basis = ('the support embed positions ({0}) converted to elevations off '
|
||||
'this panel\'s own bottom_elevation_ft ({1:.2f} ft) -- the '
|
||||
'same embeds, not a second measurement'.format(
|
||||
supports_ft_text, bottom))
|
||||
return text, basis
|
||||
Reference in New Issue
Block a user