# -*- coding: utf-8 -*- """Two constraints, not one, and they are not the same inserts. A panel erected on its END carries its anchors in that end: they cannot move along the piece, and where they sit across it is still a choice. A panel erected on its long EDGE carries them along that edge: they may slide freely down the length, and they may not come off the edge. Collapsing both into one "pinned" flag gets one of the two wrong, and the wrong one is a recommendation to put an anchor where the piece has no edge. """ import dataclasses import pytest from conftest import LENGTH_FT, WIDTH_FT, make_panel from panellift import cli, layout, statics from panellift.model import Lifter, Rigging, Stage def build(anchor_kwargs, anchor_xs=(LENGTH_FT,), anchor_ys=None): panel = make_panel() face = [dataclasses.replace(l, capacity_lb=12000.0) for l in panel.lifters if l.x < 0.5 * LENGTH_FT] anchors = [] for n, x in enumerate(anchor_xs): for m, y in enumerate(anchor_ys or (0.25 * WIDTH_FT, 0.75 * WIDTH_FT)): anchors.append(Lifter( mark='E{0}{1}'.format(n, m), family='EDGE_ANCHOR', x=x, y=y, role='strip', capacity_lb=7690.0, shear_capacity_lb=4010.0, on_edge=True, **anchor_kwargs)) return dataclasses.replace(panel, lifters=face + anchors, weight_lb=35000.0) def recommend(panel, stage, rigging): a = statics.analyse(panel, stage, rigging, role='strip') return layout.recommend([(a, rigging, [])]) def row_at(rec, x, tol=0.5): return [r for r in rec.rows if abs(r.x - x) < tol][0] # ------------------------------------------------- erected on its end def test_an_end_anchor_row_holds_its_station(stripping, rolling): rec = recommend(build({'pinned': True}), stripping, rolling) row = row_at(rec, LENGTH_FT) assert row.to_x == pytest.approx(LENGTH_FT) assert row.pinned and not row.pinned_y def test_an_end_anchor_row_may_still_be_moved_across_the_width(stripping, rolling): """Being in the end says nothing about where across the piece it sits. Drawn hard against one side, so there is something to move: the pair keeps its spacing and is brought back onto the centreline. """ rec = recommend(build({'pinned': True}, anchor_ys=(1.0, 2.0)), stripping, rolling) moves = [m for m in rec.moves if m.family == 'EDGE_ANCHOR'] assert all(m.dx_in == pytest.approx(0.0) for m in moves) assert all(abs(m.dy_in) > 0.5 for m in moves) assert sum(m.to_y for m in moves) / len(moves) == pytest.approx( WIDTH_FT / 2.0) # ------------------------------------------- erected on its long edge def test_an_edge_anchor_row_is_not_lifted_off_its_edge(stripping, rolling): """The one that was broken: a horizontal panel's anchors live on the top edge, and recommending a y in the middle of the panel asks for an anchor where there is no edge to cast it into.""" rec = recommend(build({'pinned_y': True}, anchor_xs=(8.0, 24.0), anchor_ys=(WIDTH_FT, WIDTH_FT)), stripping, rolling) for move in [m for m in rec.moves if m.family == 'EDGE_ANCHOR']: assert move.to_y == pytest.approx(WIDTH_FT) assert move.dy_in == pytest.approx(0.0) def test_an_edge_anchor_row_may_still_slide_along_the_piece(stripping, rolling): """Sliding along the edge is the one degree of freedom it has, and it is exactly the one the search exists to use.""" rec = recommend(build({'pinned_y': True}, anchor_xs=(1.0,), anchor_ys=(WIDTH_FT, WIDTH_FT)), stripping, rolling) row = row_at(rec, 1.0) assert not row.pinned and row.pinned_y assert row.to_x != pytest.approx(1.0) # ------------------------------------------------------ the job file def test_pinned_y_survives_the_job_file(): """It is written by the Revit side; if the reader drops it the constraint is silently gone and only a horizontal panel would ever show it.""" job = { 'schema': cli.SCHEMA, 'panel': { 'mark': 'H1', 'length_ft': 30.0, 'width_ft': 8.0, 'thickness_in': 8.0, 'weight_lb': 20000.0, 'lifters': [ {'mark': 'A', 'family': 'F', 'x': 5.0, 'y': 8.0, 'pinned_y': True, 'on_edge': True, 'capacity_lb': 7690.0}, {'mark': 'B', 'family': 'F', 'x': 25.0, 'y': 8.0, 'pinned_y': True, 'on_edge': True, 'capacity_lb': 7690.0}, ], }, 'stages': [{'name': 'erection', 'multiplier': 1.2, 'fc_psi': 5000.0}], } panel, _rig, _cases = cli.analyse_job(job) assert all(l.pinned_y and not l.pinned for l in panel.lifters)