WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s

History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
This commit is contained in:
2026-09-09 08:32:15 -05:00
commit 8e6ee31596
406 changed files with 223435 additions and 0 deletions
+118
View File
@@ -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)