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.
160 lines
5.4 KiB
Python
160 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Shared fixtures, built from the ASPIRE Winter 2019 worked examples.
|
|
|
|
Brown, K. "Calculations for Handling and Shipping Precast Concrete Deck
|
|
Panels." ASPIRE, Winter 2019, pp. 24-27. The article reproduces PCI Design
|
|
Handbook Table 8.3.1 and Figure 8.3.2 and works two examples end to end, which
|
|
makes it the one published check available without a PCI licence.
|
|
|
|
Panel: 10 ft x 32 ft x 8.75 in., normalweight, f'ci = 4000, f'c = 5000,
|
|
four-point pick at the Figure 8.3.2 locations.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'src'))
|
|
|
|
from panellift.model import Blockout, Lifter, Panel, Rigging, Stage # noqa: E402
|
|
from panellift import pci # noqa: E402
|
|
|
|
|
|
WIDTH_FT = 10.0
|
|
LENGTH_FT = 32.0
|
|
THICKNESS_IN = 8.75
|
|
|
|
# The article's rounded values, quoted so a failure names the source number.
|
|
ASPIRE = {
|
|
'w_psf': 109.4,
|
|
'moment_lbft': 23973.0,
|
|
's_solid_in3': 1531.0,
|
|
's_pocketed_in3': 1148.0,
|
|
'strip_stress_psi': 244.0,
|
|
'strip_allowable_psi': 316.0,
|
|
'ship_stress_psi': 282.0,
|
|
'ship_allowable_psi': 354.0,
|
|
'pocketed_strip_stress_psi': 326.0,
|
|
'pocketed_ship_stress_psi': 376.0,
|
|
}
|
|
|
|
|
|
def four_point_lifters(length_ft=LENGTH_FT, width_ft=WIDTH_FT):
|
|
"""Two lines of two, at the Figure 8.3.2 equalising positions."""
|
|
left, right = pci.optimal_two_line_supports(length_ft)
|
|
ys = (0.25 * width_ft, 0.75 * width_ft)
|
|
return [Lifter(mark='L{0}'.format(i + 1), family='TEST_LIFTER',
|
|
x=x, y=y, role='strip', capacity_lb=8000.0)
|
|
for i, (x, y) in enumerate([(left, ys[0]), (left, ys[1]),
|
|
(right, ys[0]), (right, ys[1])])]
|
|
|
|
|
|
def make_panel(mark='ASPIRE-1', blockouts=None):
|
|
"""A fresh panel every time.
|
|
|
|
Built by a plain function rather than by one fixture mutating another:
|
|
pytest caches a fixture per test, so a test asking for both the solid and
|
|
the pocketed panel would otherwise be handed the same object twice and
|
|
quietly compare it with itself.
|
|
"""
|
|
return Panel(
|
|
mark=mark,
|
|
length_ft=LENGTH_FT,
|
|
width_ft=WIDTH_FT,
|
|
thickness_in=THICKNESS_IN,
|
|
unit_weight_pcf=150.0,
|
|
fci_psi=4000.0,
|
|
fc_psi=5000.0,
|
|
finish='smooth',
|
|
product_type='flat',
|
|
lifters=four_point_lifters(),
|
|
blockouts=list(blockouts or []),
|
|
)
|
|
|
|
|
|
def aspire_pockets():
|
|
"""Five 6 in. x 24 in. full-depth pockets in one row across the width.
|
|
|
|
The article puts them 'at or near the lifting points' and notes that is
|
|
not good practice -- it is the location of maximum moment. Five pockets
|
|
six inches wide take 2.5 ft out of a 10 ft width, giving S = 1148 in^3.
|
|
"""
|
|
left, _ = pci.optimal_two_line_supports(LENGTH_FT)
|
|
return [Blockout(name='P{0}'.format(i + 1),
|
|
x0=left - 1.0, y0=1.0 + i * 1.5,
|
|
length=2.0, width=0.5)
|
|
for i in range(5)]
|
|
|
|
|
|
@pytest.fixture
|
|
def solid_panel():
|
|
return make_panel()
|
|
|
|
|
|
@pytest.fixture
|
|
def pocketed_panel():
|
|
return make_panel(mark='ASPIRE-2', blockouts=aspire_pockets())
|
|
|
|
|
|
@pytest.fixture
|
|
def stripping():
|
|
return Stage(name='stripping',
|
|
multiplier=pci.stripping_multiplier('flat', 'smooth'),
|
|
fc_psi=4000.0)
|
|
|
|
|
|
@pytest.fixture
|
|
def shipping():
|
|
return Stage(name='transportation',
|
|
multiplier=pci.TRANSPORTATION_MULTIPLIER,
|
|
fc_psi=5000.0)
|
|
|
|
|
|
@pytest.fixture
|
|
def rolling():
|
|
return Rigging(mode='rolling', sling_angle_deg=90.0)
|
|
|
|
|
|
@pytest.fixture
|
|
def static_lift():
|
|
return Rigging(mode='static', sling_angle_deg=90.0)
|
|
|
|
|
|
# ============================================================================
|
|
# Appended by tools/make_dist.py -- knowledge-content test exclusion
|
|
# ============================================================================
|
|
# See knowledge/catalogue.yaml's own header. test_safety_factors.py already
|
|
# self-skips its whole module when knowledge/catalogue.yaml is ABSENT (see
|
|
# its own pytestmark) -- but this distribution's carve-out ships that file
|
|
# with `catalogue.inserts` EMPTY, so the file-existence check alone no
|
|
# longer catches the 1 test(s) below that need real ALP rows in it.
|
|
# AUTO-DETECTING the same way: real inserts present, and these run normally.
|
|
_KNOWLEDGE_CONTENT_TESTS = ['test_the_catalogue_publishes_four_to_one_in_shear_for_every_qlpd_row']
|
|
|
|
|
|
def _dist_real_inserts_present():
|
|
import io as _io
|
|
import os as _os
|
|
here = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
|
|
path = _os.path.join(_os.path.dirname(here), 'knowledge', 'catalogue.yaml')
|
|
try:
|
|
import yaml as _yaml
|
|
with _io.open(path, encoding='utf-8') as fh:
|
|
data = _yaml.safe_load(fh.read()) or {}
|
|
except Exception:
|
|
return False
|
|
return bool(((data.get('catalogue') or {}).get('inserts')) or None)
|
|
|
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
import pytest as _pytest
|
|
if _dist_real_inserts_present():
|
|
return
|
|
skip = _pytest.mark.skip(
|
|
reason='knowledge-content test: asserts the owner\'s real product '
|
|
'capacity data, which this distribution does not ship -- '
|
|
'see knowledge/catalogue.yaml\'s header and CONTRIBUTING.md')
|
|
for item in items:
|
|
if item.originalname in _KNOWLEDGE_CONTENT_TESTS or item.name in _KNOWLEDGE_CONTENT_TESTS:
|
|
item.add_marker(skip)
|