# -*- coding: utf-8 -*- """A piece does not always hang the way it was cast. A horizontal panel is stripped flat, spanning its long direction like everything else, and then stood up on its long EDGE. Gravity then runs across what used to be the width, and that is the direction it spans. The solver is not taught about orientation. The piece is turned instead, so the mechanics stay one implementation checked against published examples. """ import dataclasses import pytest from conftest import LENGTH_FT, THICKNESS_IN, WIDTH_FT, make_panel from panellift import cli, statics from panellift.model import Blockout, Lifter def horizontal_job(span): """Anchors along one long edge, face lifters inboard of it.""" return { 'schema': cli.SCHEMA, 'panel': { 'mark': 'H1', 'length_ft': 30.0, 'width_ft': 8.0, 'thickness_in': 8.0, 'weight_lb': 24000.0, 'fc_psi': 5000.0, 'blockouts': [{'name': 'O', 'x0': 10.0, 'y0': 2.0, 'length': 6.0, 'width': 3.0}], 'lifters': [ {'mark': 'A', 'family': 'ANCHOR', 'x': 6.0, 'y': 8.0, 'on_edge': True, 'pinned_y': True, 'capacity_lb': 7690.0, 'shear_capacity_lb': 4010.0}, {'mark': 'B', 'family': 'ANCHOR', 'x': 24.0, 'y': 8.0, 'on_edge': True, 'pinned_y': True, 'capacity_lb': 7690.0, 'shear_capacity_lb': 4010.0}, {'mark': 'C', 'family': 'FACE', 'x': 6.0, 'y': 1.5, 'capacity_lb': 8000.0}, {'mark': 'D', 'family': 'FACE', 'x': 24.0, 'y': 1.5, 'capacity_lb': 8000.0}, ], }, 'stages': [{'name': 'erection', 'multiplier': 1.2, 'fc_psi': 5000.0, 'span': span}], } # --------------------------------------------------------- the transposition def test_turning_the_piece_swaps_its_two_in_plane_dimensions(): panel = make_panel() turned = cli.transpose(panel) assert turned.length_ft == pytest.approx(WIDTH_FT) assert turned.width_ft == pytest.approx(LENGTH_FT) assert turned.thickness_in == pytest.approx(THICKNESS_IN) def test_turning_the_piece_takes_the_blockouts_with_it(): panel = dataclasses.replace(make_panel(), blockouts=[ Blockout(name='O', x0=4.0, y0=1.0, length=6.0, width=2.0)]) hole = cli.transpose(panel).blockouts[0] assert (hole.x0, hole.y0, hole.length, hole.width) == (1.0, 4.0, 2.0, 6.0) def test_turning_the_piece_swaps_which_way_an_insert_is_pinned(): """An anchor pinned to a long edge is pinned across the piece as cast and ALONG it once the piece has been stood up on that edge. Carrying the flag through unchanged would free it to slide off its own edge.""" panel = dataclasses.replace(make_panel(), lifters=[ Lifter(mark='A', family='F', x=2.0, y=9.0, pinned_y=True)]) turned = cli.transpose(panel).lifters[0] assert (turned.x, turned.y) == (9.0, 2.0) assert turned.pinned and not turned.pinned_y def test_turning_twice_is_the_piece_you_started_with(): panel = make_panel() there_and_back = cli.transpose(cli.transpose(panel)) assert there_and_back.length_ft == pytest.approx(panel.length_ft) assert [(l.x, l.y) for l in there_and_back.lifters] == \ [(l.x, l.y) for l in panel.lifters] # ------------------------------------------------------------ through a job def test_a_stage_spanning_the_width_really_spans_the_width(): _p, _r, cases = cli.analyse_job(horizontal_job('width')) assert cases[0][0].panel.length_ft == pytest.approx(8.0) assert cases[0][0].stage.span == 'width' def test_the_same_inserts_group_differently_when_the_piece_turns(): """The anchors along the top edge are ONE support when the piece is standing and TWO when it is flat. That is the whole reason to turn it.""" _p, _r, flat = cli.analyse_job(horizontal_job('length')) _p, _r, stood = cli.analyse_job(horizontal_job('width')) assert len(flat[0][0].rig.lines) == 2 # by x: 6 ft and 24 ft assert len(stood[0][0].rig.lines) == 2 # by y: 1.5 ft and 8 ft stood_line = max(stood[0][0].rig.lines, key=lambda l: l.x) assert stood_line.count == 2 # both anchors, one support def test_a_stage_defaults_to_spanning_its_length(): _p, _r, cases = cli.analyse_job(horizontal_job(None)) assert cases[0][0].stage.span == 'length' assert cases[0][0].panel.length_ft == pytest.approx(30.0) def test_an_unknown_span_is_refused(): with pytest.raises(cli.JobError): cli.analyse_job(horizontal_job('diagonally')) def test_the_position_search_stands_aside_for_a_turned_stage(): """It optimises one set of coordinates. A turned stage's x is the panel's y, so including it would be optimising against two different pieces. It still answers for the stages it can, and says which one it left out -- silently optimising against two of three stages would read as an answer for all three. """ from panellift import layout job = horizontal_job('width') job['stages'].insert(0, {'name': 'stripping', 'multiplier': 1.3, 'fc_psi': 4000.0}) _p, _r, cases = cli.analyse_job(job) rec = layout.recommend(cases) assert rec.moves # stripping still answered assert [s.name for s in rec.stages] == ['stripping'] assert 'erection' in rec.note def test_the_search_says_so_when_every_stage_is_turned(): from panellift import layout _p, _r, cases = cli.analyse_job(horizontal_job('width')) rec = layout.recommend(cases) assert not rec.moves and 'erection' in rec.note