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,224 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Deviation table: beamcalc vs an Eriksson Beam package's printed results.
|
||||
|
||||
python beamcalc/tools/compare_eriksson.py <job.json> <truth.json> [-o report.md]
|
||||
|
||||
`truth.json` comes from eriksson_extract.py. Engine stations are matched to
|
||||
the printed stations by nearest x within TOL ft; where the engine has no
|
||||
station within TOL, the engine is re-run with that station added, so every
|
||||
printed row gets a comparison. Prints one Markdown block per table with the
|
||||
largest deviations, and a one-line scoreboard (max |dev| per quantity)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
from beamcalc import model, solve # noqa: E402
|
||||
|
||||
TOL = 0.03
|
||||
|
||||
|
||||
def pct(a, b):
|
||||
if a is None or b is None:
|
||||
return None
|
||||
if abs(b) < 1e-9:
|
||||
return 0.0 if abs(a) < 1e-6 else None
|
||||
return 100.0 * (a - b) / abs(b)
|
||||
|
||||
|
||||
def fmt(v, nd=2):
|
||||
return '-' if v is None else ('{0:.%df}' % nd).format(v)
|
||||
|
||||
|
||||
def nearest(rows, x, key=lambda r: r.x_ft):
|
||||
best = None
|
||||
for r in rows:
|
||||
if abs(key(r) - x) <= TOL and (best is None or abs(key(r) - x) < abs(key(best) - x)):
|
||||
best = r
|
||||
return best
|
||||
|
||||
|
||||
def compare(job_path, truth_path):
|
||||
truth = json.load(open(truth_path))
|
||||
beam = model.load_job(job_path)
|
||||
extra = sorted({r['x_ft'] * 12.0 for t in ('flexure', 'shear', 'tension') for r in truth[t]})
|
||||
a = solve.analyse(beam, extra_stations_in=extra) if 'extra_stations_in' in solve.analyse.__code__.co_varnames else solve.analyse(beam)
|
||||
out = {'tables': {}, 'score': {}}
|
||||
|
||||
def table(name, rows, pairs):
|
||||
# pairs: list of (label, truth_key, engine_fn, nd, is_abs_tol)
|
||||
lines = ['| x ft | ' + ' | '.join('%s (engine / Eriksson / dev)' % p[0] for p in pairs) + ' |',
|
||||
'|---|' + '---|' * len(pairs)]
|
||||
worst = {p[0]: (0.0, None) for p in pairs}
|
||||
n = 0
|
||||
for tr in rows:
|
||||
x = tr['x_ft']
|
||||
eng = nearest_engine(name, a, x)
|
||||
if eng is None:
|
||||
continue
|
||||
n += 1
|
||||
cells = []
|
||||
for label, tkey, efn, nd, atol in pairs:
|
||||
tv = tr.get(tkey)
|
||||
ev = efn(eng)
|
||||
if tv is None:
|
||||
cells.append('%s / NC / -' % fmt(ev, nd)); continue
|
||||
if name in ('shear',) or tkey == 'mcr12':
|
||||
tv, ev = abs(tv), (abs(ev) if ev is not None else None) # the tool prints signed V
|
||||
d = pct(ev, tv)
|
||||
if tv is not None and ev is not None and atol is not None and abs(ev - tv) <= atol:
|
||||
d = 0.0
|
||||
if d is not None and abs(d) > worst[label][0]:
|
||||
worst[label] = (abs(d), x)
|
||||
cells.append('%s / %s / %s' % (fmt(ev, nd), fmt(tv, nd), ('%+.1f%%' % d) if d is not None else '-'))
|
||||
lines.append('| %.2f | %s |' % (x, ' | '.join(cells)))
|
||||
out['tables'][name] = {'md': '\n'.join(lines), 'n': n, 'worst': worst}
|
||||
for label, (w, x) in worst.items():
|
||||
out['score']['%s.%s' % (name, label)] = (w, x)
|
||||
|
||||
table('flexure', truth['flexure'], [
|
||||
('Mu', 'mu', lambda e: e.mu, 2, 0.05),
|
||||
('phiMn', 'phi_mn', lambda e: e.phi_mn, 2, 0.05),
|
||||
('1.2Mcr', 'mcr12', lambda e: e.mcr12, 2, 0.05),
|
||||
('c', 'c', lambda e: e.c, 2, 0.01),
|
||||
('As', 'as', lambda e: e.as_t, 2, 0.01),
|
||||
('d', 'd', lambda e: e.d, 2, 0.01),
|
||||
])
|
||||
table('shear', truth['shear'], [
|
||||
('Vu', 'vu', lambda e: e.r.vu, 1, 0.1),
|
||||
('phiVn', 'phi_vn', lambda e: e.r.phi_vn, 1, 0.1),
|
||||
('Vc', 'vc', lambda e: e.r.vc, 1, 0.1),
|
||||
('Vs prov', 'vs_prov', lambda e: e.r.vs_prov, 1, 0.1),
|
||||
('bwd', 'bwd', lambda e: e.r.bwd, 1, 0.1),
|
||||
('Av req', 'av_req', lambda e: e.r.av_req, 3, 0.002),
|
||||
('f act', 'f_act', lambda e: e.r.f_act, 1, 0.5),
|
||||
])
|
||||
table('tension', truth['tension'], [
|
||||
('f bot', 'f', lambda e: -min(e.f_bot, e.f_top, 0.0), 1, 1.0),
|
||||
('Mz', 'mz', lambda e: e.m, 2, 0.05),
|
||||
])
|
||||
table('comp_total', truth['comp_total'], [
|
||||
('f comp', 'f', lambda e: max(e.f_top, e.f_bot, 0.0), 1, 1.0),
|
||||
])
|
||||
table('comp_sust', truth['comp_sust'], [
|
||||
('f comp sus', 'f', lambda e: max(e.f_top_sus, e.f_bot_sus, 0.0), 1, 1.0),
|
||||
])
|
||||
# deflection rows
|
||||
d = a.deflection
|
||||
xs = truth.get('defl_x') or []
|
||||
dl = ['| stage | case | mult E/T | max E | max T | dev |', '|---|---|---|---|---|---|']
|
||||
for tr in truth['deflection']:
|
||||
case = tr['case']
|
||||
if tr['stage'] == 'Total' or case == 'Total':
|
||||
ev = d['total'] if tr['stage'] == 'Total' else None
|
||||
m_e = None
|
||||
elif tr['stage'].startswith('Start'):
|
||||
if case != 'SW':
|
||||
continue
|
||||
ev = [v / d['start_sw_multiplier'] * d['start_sw_multiplier'] for v in d['start_total']]
|
||||
m_e = d['start_sw_multiplier']
|
||||
else:
|
||||
row = d['rows'].get(case)
|
||||
if not row:
|
||||
continue
|
||||
ev = [row['multiplier'] * v for v in row['elastic']]
|
||||
m_e = row['multiplier']
|
||||
if ev is None:
|
||||
continue
|
||||
# engine values are on the engine grid; sample at the printed stations
|
||||
ge = _sample(a.grid, ev, [x * 12.0 for x in xs]) if xs else ev
|
||||
tv = tr['values']
|
||||
te = -max(abs(v) for v in tv) if tv else 0.0
|
||||
ee = -max(abs(v) for v in ge) if ge else 0.0
|
||||
dev = pct(ee, te)
|
||||
dl.append('| %s | %s | %s / %.2f | %.3f | %.3f | %s |' % (
|
||||
tr['stage'], case, fmt(m_e), tr['mult'], ee, te, ('%+.1f%%' % dev) if dev is not None else '-'))
|
||||
if dev is not None and abs(te) >= 0.01:
|
||||
out['score']['defl.%s.%s' % (tr['stage'], case)] = (abs(dev), None)
|
||||
out['tables']['deflection'] = {'md': '\n'.join(dl), 'n': len(dl) - 2, 'worst': {}}
|
||||
# summary
|
||||
s = truth['summary']
|
||||
sm = ['| item | engine | Eriksson | dev |', '|---|---|---|---|']
|
||||
worst_f = max(a.flex, key=lambda f: f.util) if a.flex else None
|
||||
items = [
|
||||
('flexure util', worst_f.util if worst_f else None, s.get('flex_util')),
|
||||
('phiMn governing', worst_f.phi_mn if worst_f else None, s.get('flex_phi_mn')),
|
||||
('Mu governing', worst_f.mu if worst_f else None, s.get('flex_mu')),
|
||||
('Av/s max', max(x.r.av_req for x in a.shr) if a.shr else None, s.get('av_s')),
|
||||
('defl total down', d['total_down'], s.get('defl_total_down')),
|
||||
('defl total up', d['total_up'], s.get('defl_total_up')),
|
||||
('defl live down', d['live_down'], s.get('defl_live_down')),
|
||||
('comp total max', max(max(r.f_top, r.f_bot) for r in a.service) if a.service else None, s.get('comp_tot_max')),
|
||||
('comp sust max', max(max(r.f_top_sus, r.f_bot_sus) for r in a.service) if a.service else None, s.get('comp_sust_max')),
|
||||
('tension max', max(-min(r.f_top, r.f_bot) for r in a.service) if a.service else None, s.get('tension_max')),
|
||||
]
|
||||
for k, side in (('left', 0), ('right', 1)):
|
||||
for case in ('deadnc', 'deadc', 'live', 'roof'):
|
||||
tv = (s.get('reactions') or {}).get(case)
|
||||
ev = a.reactions.get(case)
|
||||
if case == 'deadnc' and ev: # the tool's Deadnc includes self weight
|
||||
sw = a.reactions.get('self') or [0.0, 0.0]
|
||||
ev = [ev[0] + sw[0], ev[1] + sw[1]]
|
||||
items.append(('R %s %s' % (case, k), ev[side] if ev else None, tv[side] if tv else None))
|
||||
for k in ('left', 'right'):
|
||||
b = (s.get('bearing') or {}).get(k) or {}
|
||||
items.append(('bearing Vu %s' % k, (a.bearing.get(k) or {}).get('vu'), b.get('vu')))
|
||||
items.append(('bearing plain phiVn %s' % k, (a.bearing.get(k) or {}).get('phi_vn_plain'), b.get('phi_vn_plain')))
|
||||
st = s.get('stripping') or {}
|
||||
if a.stripping:
|
||||
items += [('strip Mu', a.stripping.get('mu'), st.get('mu')),
|
||||
('strip phiMn', a.stripping.get('phi_mn'), st.get('phi_mn')),
|
||||
('strip f tens', a.stripping.get('f_tens'), (st.get('tens') or [None])[0]),
|
||||
('strip f comp', a.stripping.get('f_comp'), (st.get('comp') or [None])[0])]
|
||||
for label, ev, tv in items:
|
||||
dev = pct(ev, tv)
|
||||
sm.append('| %s | %s | %s | %s |' % (label, fmt(ev, 3), fmt(tv, 3), ('%+.1f%%' % dev) if dev is not None else '-'))
|
||||
if dev is not None:
|
||||
out['score']['summary.%s' % label] = (abs(dev), None)
|
||||
out['tables']['summary'] = {'md': '\n'.join(sm), 'n': len(items), 'worst': {}}
|
||||
return out, a
|
||||
|
||||
|
||||
def _sample(grid, vals, xs):
|
||||
out = []
|
||||
for x in xs:
|
||||
i = min(range(len(grid)), key=lambda k: abs(grid[k] - x))
|
||||
out.append(vals[i])
|
||||
return out
|
||||
|
||||
|
||||
def nearest_engine(name, a, x):
|
||||
if name == 'flexure':
|
||||
return nearest(a.flex, x)
|
||||
if name == 'shear':
|
||||
return nearest(a.shr, x)
|
||||
return nearest(a.service, x)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('job'); ap.add_argument('truth'); ap.add_argument('-o', '--out')
|
||||
ap.add_argument('--full', action='store_true', help='print every row, not just the scoreboard')
|
||||
args = ap.parse_args(argv)
|
||||
out, a = compare(args.job, args.truth)
|
||||
md = ['# %s vs Eriksson' % a.beam.mark, '']
|
||||
md.append('## Scoreboard (max |dev|)'); md.append('')
|
||||
md.append('| quantity | max dev | at x ft |'); md.append('|---|---|---|')
|
||||
for k, (w, x) in sorted(out['score'].items(), key=lambda kv: -kv[1][0]):
|
||||
md.append('| %s | %.1f%% | %s |' % (k, w, fmt(x)))
|
||||
for name, t in out['tables'].items():
|
||||
md += ['', '## %s (%d rows)' % (name, t['n']), '', t['md']]
|
||||
text = '\n'.join(md)
|
||||
if args.out:
|
||||
open(args.out, 'w', encoding='utf-8').write(text)
|
||||
if args.full:
|
||||
print(text)
|
||||
else:
|
||||
print('\n'.join(md[:md.index('') + 40]) if len(md) > 40 else text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Pull the result tables out of an Eriksson Beam v5.9 PDF, deterministically.
|
||||
|
||||
python beamcalc/tools/eriksson_extract.py <package.pdf> <out.json>
|
||||
|
||||
Uses pymupdf word coordinates: every printed table row is one text line, so
|
||||
words are grouped by their y position and split on the known column count.
|
||||
Nothing is inferred -- a row that does not have the expected number of
|
||||
numeric cells is skipped and counted in `skipped`. Only numbers are kept;
|
||||
the title block (client / job / engineer) is not copied out.
|
||||
|
||||
Tables recognised (first run in the document only -- the "END CHECK" strut
|
||||
and tie re-run that some packages append is ignored on purpose):
|
||||
summary -- the "Summary" page numbers (reactions, bearing, strength, service)
|
||||
flexure -- In-Place Strength / Flexure Summary Table (14 columns)
|
||||
shear -- In-Place Strength / Shear Summary Table (15 columns)
|
||||
tension -- In-Place Service / Tension: station, psi, cracking psi, Mz
|
||||
comp_total -- Total Compression: station, psi, Mz
|
||||
comp_sust -- Sustained Compression: station, psi, Mz
|
||||
deflection -- Camber and Deflections: rows {case, stage, mult, values[11]}
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
import pymupdf
|
||||
|
||||
NUM = re.compile(r'^-?\d+(\.\d+)?$')
|
||||
|
||||
|
||||
def _num(t):
|
||||
if t == 'NC':
|
||||
return None
|
||||
return float(t)
|
||||
|
||||
|
||||
def lines_of(page):
|
||||
rows = {}
|
||||
for x0, y0, x1, y1, word, b, l, n in page.get_text('words'):
|
||||
rows.setdefault(round(y0), []).append((x0, word))
|
||||
out = []
|
||||
for y in sorted(rows):
|
||||
out.append([w for x, w in sorted(rows[y])])
|
||||
return out
|
||||
|
||||
|
||||
FLEX_COLS = ['x_ft', 'util', 'mu', 'phi', 'tc', 'phi_mn', 'mcr12', 'c', 'theta',
|
||||
'as', 'as_c', 'aps', 'loss', 'd']
|
||||
SHEAR_COLS = ['x_ft', 'util', 'vu', 'phi_vn', 'vc', 'vs_prov', 'bwd', 'legs',
|
||||
'av_min', 'av_prov', 'av_req', 's', 's_max', 'f_act', 'f_alw']
|
||||
|
||||
|
||||
def parse_row(words, cols):
|
||||
if len(words) != len(cols):
|
||||
return None
|
||||
row = {}
|
||||
for c, w in zip(cols, words):
|
||||
if c == 'tc':
|
||||
row[c] = w
|
||||
continue
|
||||
if not (NUM.match(w) or w == 'NC'):
|
||||
return None
|
||||
row[c] = _num(w)
|
||||
return row
|
||||
|
||||
|
||||
def extract(pdf_path):
|
||||
doc = pymupdf.open(pdf_path)
|
||||
out = {'flexure': [], 'shear': [], 'tension': [], 'comp_total': [], 'comp_sust': [],
|
||||
'deflection': [], 'summary': {}, 'skipped': 0, 'pages': len(doc)}
|
||||
mode = None # which table the following numeric rows belong to
|
||||
stage = None # 'strength' / 'service' / 'stripping' ... from page headers
|
||||
defl_stage = None
|
||||
defl_case = None
|
||||
seen_summary = False
|
||||
seen_run_end = False
|
||||
for pno, page in enumerate(doc, 1):
|
||||
text = page.get_text()
|
||||
if 'Project Information' in text and seen_summary:
|
||||
# a second Eriksson run begins (END CHECK etc.) -- stop
|
||||
seen_run_end = True
|
||||
if seen_run_end:
|
||||
break
|
||||
for words in lines_of(page):
|
||||
line = ' '.join(words)
|
||||
# --- section headers drive the mode
|
||||
if line.startswith('In-Place Strength Summary'):
|
||||
stage = 'strength'; mode = None
|
||||
elif line.startswith('In-Place Service Summary'):
|
||||
stage = 'service'; mode = None
|
||||
elif line.startswith('Camber and Deflections') and 'Note' not in line:
|
||||
stage = 'deflection'; mode = 'defl'
|
||||
elif line.split(' ')[0] in ('Stripping', 'Handling', 'Erection', 'Transfer') and 'Summary' in line:
|
||||
stage = 'handling'; mode = None
|
||||
elif line == 'Flexure' and stage == 'strength':
|
||||
mode = 'flexure'
|
||||
elif line.startswith('Pure Shear') and stage == 'strength':
|
||||
mode = 'shear'
|
||||
elif line.startswith('Loc. Util Vu') and stage == 'strength':
|
||||
mode = 'shear'
|
||||
elif line.startswith('Loc. Util.') and stage == 'strength':
|
||||
mode = 'flexure' if mode != 'shear' else mode
|
||||
elif line.startswith('Precast') and stage == 'service':
|
||||
pass
|
||||
elif line.startswith('Location Tension Cracking Mz'):
|
||||
mode = 'tension'
|
||||
elif line.startswith('Total Compression'):
|
||||
mode = 'comp_total'
|
||||
elif line.startswith('Sustained Compression'):
|
||||
mode = 'comp_sust'
|
||||
elif line.startswith('Servicability Class') or line.startswith('Location Tension Class'):
|
||||
mode = 'svc_class'
|
||||
elif line.startswith('Torsion') and stage == 'strength':
|
||||
mode = None
|
||||
if stage == 'deflection':
|
||||
if line in ('Start of Construction', 'End of Construction'):
|
||||
defl_stage = line
|
||||
elif line.startswith('Final') and len(words) == 1:
|
||||
defl_stage = 'Final'
|
||||
elif line.startswith('Deflections Occuring Prior'):
|
||||
defl_stage = 'Final prior'
|
||||
elif line.startswith('Deflections Occuring After'):
|
||||
defl_stage = 'Final after'
|
||||
elif line == 'Total Deflections':
|
||||
defl_stage = 'Total'
|
||||
elif len(words) == 1 and not NUM.match(words[0]):
|
||||
if words[0] in ('SW', 'Dnc', 'Dt', 'Dc', 'Live', 'Roof', 'Wind', 'Seismic', 'PS', 'Total'):
|
||||
defl_case = words[0]
|
||||
elif len(words) == 2 and words[0] == 'Sust.' and words[1] == 'Live':
|
||||
defl_case = 'Sust. Live'
|
||||
elif len(words) == 12 and all(NUM.match(w) for w in words) and defl_case:
|
||||
out['deflection'].append({'stage': defl_stage, 'case': defl_case,
|
||||
'mult': float(words[0]),
|
||||
'values': [float(w) for w in words[1:]],
|
||||
'page': pno})
|
||||
defl_case = None
|
||||
elif len(words) == 11 and all(NUM.match(w) for w in words) and defl_case is None and not out.get('defl_x'):
|
||||
out['defl_x'] = [float(w) for w in words]
|
||||
continue
|
||||
if mode == 'flexure':
|
||||
r = parse_row(words, FLEX_COLS)
|
||||
if r:
|
||||
r['page'] = pno; out['flexure'].append(r)
|
||||
elif words and NUM.match(words[0]) and len(words) > 8:
|
||||
out['skipped'] += 1
|
||||
elif mode == 'shear':
|
||||
r = parse_row(words, SHEAR_COLS)
|
||||
if r:
|
||||
r['page'] = pno; out['shear'].append(r)
|
||||
elif words and NUM.match(words[0]) and len(words) > 8:
|
||||
out['skipped'] += 1
|
||||
elif mode == 'tension' and len(words) == 4 and all(NUM.match(w) for w in words):
|
||||
out['tension'].append({'x_ft': float(words[0]), 'f': float(words[1]),
|
||||
'f_cr': float(words[2]), 'mz': float(words[3]), 'page': pno})
|
||||
elif mode in ('comp_total', 'comp_sust') and len(words) == 3 and all(NUM.match(w) for w in words):
|
||||
out[mode].append({'x_ft': float(words[0]), 'f': float(words[1]),
|
||||
'mz': float(words[2]), 'page': pno})
|
||||
# --- the one-page Summary block (regex over the page text)
|
||||
if 'Stage: In-Place Strength' in text and 'Stage: In-Place Service' in text and not seen_summary:
|
||||
seen_summary = True
|
||||
s = out['summary']
|
||||
flat = text.replace('\n', ' ')
|
||||
|
||||
def grab(pattern, cast=float):
|
||||
m = re.search(pattern, flat)
|
||||
return cast(m.group(1)) if m else None
|
||||
s['flex_util'] = grab(r'Stage: In-Place Strength.*?Utilization\s*:\s*([\d.]+)')
|
||||
s['flex_phi_mn'] = grab(r'Stage: In-Place Strength.*?ϕMn \(k-ft\):\s*([\d.-]+)')
|
||||
s['flex_mu'] = grab(r'Stage: In-Place Strength.*?Mu \(k-ft\):\s*([\d.-]+)')
|
||||
s['av_s'] = grab(r'Av/s \(in²/ft\):\s*([\d.]+)')
|
||||
s['vc'] = grab(r'Vc \(k\)\s*:\s*([\d.]+)')
|
||||
s['vu'] = grab(r'Vu \(k\)\s*:\s*([\d.]+)')
|
||||
s['defl_total_up'] = grab(r'Defl\. Total\s*↑ \(in\):\s*([\d.-]+)')
|
||||
s['defl_total_down'] = grab(r'Defl\. Total.*?↓ \(in\):\s*([\d.-]+)')
|
||||
s['defl_total_limit'] = grab(r'Defl\. Total.*?Limit \(in\):\s*([\d.]+)')
|
||||
m = re.search(r'LL\s*↑ \(in\):\s*([\d.-]+)\s*↓ \(in\):\s*([\d.-]+)\s*Limit \(in\):\s*([\d.]+)', flat)
|
||||
if m:
|
||||
s['defl_live_up'], s['defl_live_down'], s['defl_live_limit'] = (float(v) for v in m.groups())
|
||||
m = re.search(r'Defl\. Total\s*↑ \(in\):\s*([\d.-]+)\s*↓ \(in\):\s*([\d.-]+)\s*Limit \(in\):\s*([\d.]+)', flat)
|
||||
if m:
|
||||
s['defl_total_up'], s['defl_total_down'], s['defl_total_limit'] = (float(v) for v in m.groups())
|
||||
s['comp_tot_max'] = grab(r'Comp\. Tot\. Max \(psi\):\s*([\d.]+)')
|
||||
s['comp_sust_max'] = grab(r'Comp\. Sust\.Max \(psi\):\s*([\d.]+)')
|
||||
s['tension_max'] = grab(r'Tension\s*Max \(psi\):\s*([\d.]+)')
|
||||
s['class_u_limit'] = grab(r'Class U Limit \(psi\):\s*([\d.]+)')
|
||||
s['class_t_limit'] = grab(r'Class T Limit \(psi\):\s*([\d.]+)')
|
||||
m = re.search(r'Fy\s+([\d.]+)/([\d.]+)\s+([\d.]+)/([\d.]+)\s+([\d.]+)/([\d.]+)\s+([\d.]+)/([\d.]+)', flat)
|
||||
if m:
|
||||
g = [float(v) for v in m.groups()]
|
||||
s['reactions'] = {'deadnc': g[0:2], 'deadc': g[2:4], 'live': g[4:6], 'roof': g[6:8]}
|
||||
m = re.search(r'Left\s+([\d.]+)\s+([\d.]+)\s+.*?Right\s+([\d.]+)\s+([\d.]+)', flat)
|
||||
if m:
|
||||
s['bearing'] = {'left': {'vu': float(m.group(1)), 'phi_vn_plain': float(m.group(2))},
|
||||
'right': {'vu': float(m.group(3)), 'phi_vn_plain': float(m.group(4))}}
|
||||
m = re.search(r'Stage: Stripping.*?Max Shear \(k\)\s*:\s*([\d.]+)\s*Max Tension \(k\):\s*([\d.]+).*?Compression \(psi\):\s*(\d+)/(\d+)\s*Tension \(psi\)\s*:\s*(\d+) / (\d+).*?Utilization:\s*([\d.]+)\s*ϕMn \(k-ft\):\s*([\d.]+)\s*Mu \(k-ft\):\s*([\d.]+)', flat)
|
||||
if m:
|
||||
g = m.groups()
|
||||
s['stripping'] = {'max_shear': float(g[0]), 'max_tension': float(g[1]),
|
||||
'comp': [float(g[2]), float(g[3])], 'tens': [float(g[4]), float(g[5])],
|
||||
'util': float(g[6]), 'phi_mn': float(g[7]), 'mu': float(g[8])}
|
||||
s['page'] = pno
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
data = extract(src)
|
||||
with open(dst, 'w', encoding='utf-8') as fh:
|
||||
json.dump(data, fh, indent=1)
|
||||
print('%s: flexure %d rows, shear %d, tension %d, comp %d/%d, defl rows %d, skipped %d, summary keys %d'
|
||||
% (dst, len(data['flexure']), len(data['shear']), len(data['tension']),
|
||||
len(data['comp_total']), len(data['comp_sust']), len(data['deflection']),
|
||||
data['skipped'], len(data['summary'])))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user