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,136 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user