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