52d94303bc
ci / test (push) Has been cancelled
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.
134 lines
5.0 KiB
Python
134 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""The panelanalysis/1 In-Place SERVICE section (docs sec.6) + the P-Delta note.
|
|
|
|
Host CPython, loaded exactly like its siblings: reaches the tool through
|
|
`tools/` with the `vibe` stub in place. A wired deck (restraints + wind +
|
|
service inputs) must render the In-Place Service Summary -- tension/cracking,
|
|
compression, deflection -- and the strength section must carry the P-Delta
|
|
(2nd-order) magnifier. The could-not-judge discipline is enforced: a deflection
|
|
with no L/n limit is NOT JUDGED, named, never a fabricated pass; nothing on the
|
|
page is CJK; nothing is a traceback.
|
|
|
|
Everything written at runtime goes under tmp_path.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
|
|
os.path.abspath(__file__))))
|
|
TOOLS = os.path.join(ROOT, 'tools')
|
|
PANELLIFT_SRC = os.path.join(ROOT, 'panellift', 'src')
|
|
for p in (TOOLS, PANELLIFT_SRC):
|
|
if p not in sys.path:
|
|
sys.path.insert(0, p)
|
|
|
|
from vibe import knowledge # noqa: E402,F401 (registers the vibe stub)
|
|
from vibe import plantpath # noqa: E402,F401
|
|
|
|
pa = pytest.importorskip('panelanalysis')
|
|
schema = pytest.importorskip('panelanalysis_schema')
|
|
|
|
CJK = re.compile(u'[一-鿿]')
|
|
|
|
|
|
def _wired_deck(deflection_limit_ratio=150.0, wind=True, class_t=False):
|
|
"""A mild solid wall pinned top and bottom, with a service wind pressure."""
|
|
deck = schema.new_deck(length_ft=30.0, width_ft=10.0, thickness_in=12.0,
|
|
fc_psi=6000.0, strands=[])
|
|
deck['structural_model']['restraints'] = [
|
|
{'x_ft': 0.0, 'type': 'pin'}, {'x_ft': 30.0, 'type': 'pin'}]
|
|
if wind:
|
|
deck['loading']['wind'] = {'zones': [
|
|
{'x0_ft': 0.0, 'x1_ft': 30.0, 'pressure_psf': 50.0, 'label': 'W1'}]}
|
|
svc = {}
|
|
if deflection_limit_ratio is not None:
|
|
svc['deflection_limit_ratio'] = deflection_limit_ratio
|
|
if class_t:
|
|
svc['cracking_class_t'] = True
|
|
if svc:
|
|
deck['loading']['service'] = svc
|
|
return deck
|
|
|
|
|
|
# ------------------------------------------------------------ the run itself
|
|
|
|
def test_wired_deck_produces_a_service_result():
|
|
deck = _wired_deck()
|
|
panel = schema.to_panel(deck)
|
|
result, reason = pa.solve_in_place(deck, panel)
|
|
assert reason is None and result is not None and not result.refusal
|
|
svc = result.service
|
|
assert svc is not None and svc.stations
|
|
# a wind-loaded wall develops real service tension and compression
|
|
assert svc.max_tension_psi > 0.0
|
|
assert svc.max_compression_psi > 0.0
|
|
# cracking limit is the Class U 7.5 sqrt(6000) = 580.9 psi
|
|
assert abs(svc.stations[0].cracking_limit_psi - 580.9) < 0.5
|
|
# deflection judged against L/150 = 2.4 in
|
|
d = svc.deflection
|
|
assert d.combo == 'SC5' and d.util is not None
|
|
assert abs(d.limit_in - 30.0 * 12.0 / 150.0) < 1e-6
|
|
|
|
|
|
def test_class_t_deck_raises_the_cracking_limit():
|
|
deck = _wired_deck(class_t=True)
|
|
panel = schema.to_panel(deck)
|
|
result, _reason = pa.solve_in_place(deck, panel)
|
|
import math
|
|
assert abs(result.service.stations[0].cracking_limit_psi
|
|
- 12.0 * math.sqrt(6000.0)) < 0.5
|
|
|
|
|
|
# ------------------------------------------------------------- the rendering
|
|
|
|
def test_page_renders_the_service_summary_and_pdelta():
|
|
deck = _wired_deck()
|
|
html = pa.build_analysis_page(deck)
|
|
assert '>In-Place Service Summary<' in html
|
|
for needle in ('Tension vs cracking', 'Total compression', 'Deflection',
|
|
'Cracking (psi)', 'Combination'):
|
|
assert needle in html, needle
|
|
# the P-Delta 2nd-order magnifier appears in the strength section
|
|
assert 'P-Δ (2nd-order' in html
|
|
assert 'magnifier' in html
|
|
assert 'Traceback' not in html
|
|
m = CJK.search(html)
|
|
assert not m, 'CJK: {0!r}'.format(m.group() if m else '')
|
|
|
|
|
|
def test_deflection_without_limit_is_not_judged_not_faked():
|
|
deck = _wired_deck(deflection_limit_ratio=None)
|
|
html = pa.build_analysis_page(deck)
|
|
assert '>In-Place Service Summary<' in html
|
|
# the deflection is computed, but the LIMIT check is named NOT JUDGED
|
|
assert 'Deflection limit NOT' in html or 'NOT JUDGED' in html
|
|
result, _r = pa.solve_in_place(deck, schema.to_panel(deck))
|
|
assert result.service.deflection.util is None
|
|
assert result.service.deflection.why
|
|
|
|
|
|
def test_unwired_deck_keeps_the_explicit_service_placeholder():
|
|
deck = schema.new_deck(support_xs=(2.0, 22.0)) # no restraints
|
|
html = pa.build_analysis_page(deck)
|
|
assert '>In-Place Service (not computed)<' in html
|
|
assert 'Traceback' not in html
|
|
|
|
|
|
def test_cli_end_to_end_renders_service_under_tmp_path(tmp_path):
|
|
deck = _wired_deck()
|
|
deck_path = tmp_path / 'PNL101.pa.json'
|
|
out_path = tmp_path / 'out.html'
|
|
with io.open(str(deck_path), 'w', encoding='utf-8') as fh:
|
|
json.dump(deck, fh)
|
|
rc = pa.main([str(deck_path), '-o', str(out_path)])
|
|
assert rc == 0 and out_path.exists()
|
|
html = out_path.read_text(encoding='utf-8')
|
|
assert 'In-Place Service Summary' in html
|