Reference implementation of the strap-beam generators at revision 8.0.0, kept so the acceptance oracle can be regenerated. Not a live target; the running application has no OpenSCAD dependency. The oracle holds 123 frozen cases, 113 accepted and 10 rejected, produced by OpenSCAD 2021.01 with BOSL2 at 92d697c2. The ten rejections are part of the contract: a port that accepts them is wrong. tests/test_oracle.py specifies the port API and was written before the port, so the interface follows from what must be verified rather than what is convenient to implement. Proven by adversarial stub: a build() that rejects everything passes all 10 rejection tests and fails all 226 acceptance tests.
134 lines
5.5 KiB
OpenSCAD
134 lines
5.5 KiB
OpenSCAD
/*
|
|
sb-core.scad — Strap-Beam library, umbrella include
|
|
===================================================
|
|
|
|
A "strap beam" is N pallet-strap bundles running parallel to a common
|
|
longitudinal axis (Z), held in a printed PLA+ enclosure. A profile only
|
|
decides how the N cross-sections are arranged in XY; that arrangement is
|
|
then swept along Z. Member length in the cross-section is therefore the
|
|
strap's WIDTH, never the beam's length.
|
|
|
|
include <lib/sb-core.scad>
|
|
|
|
brings in BOSL2 and the three library files. Generators for a particular
|
|
member count (strap-beam-3x.scad, strap-beam-4x.scad, ...) include this
|
|
and supply only their own profile catalogue.
|
|
|
|
---------------------------------------------------------------------
|
|
What lives where
|
|
---------------------------------------------------------------------
|
|
sb-geom.scad GEO and MEMBER records; strap, cavity and sleeve paths;
|
|
exact polyline distance; the monotone solver.
|
|
sb-join.scad Butt joints, hull caps, fillets; ring envelopes and
|
|
bores; polygon fitting. All written for N members.
|
|
sb-report.scad Value-based checks, section metrics, the SB_KEY=value
|
|
report, and the 2D/3D output modules.
|
|
sb-profiles.scad Three complete N-generic arrangements - ring, spokes,
|
|
fins - each returning a finished PROFILE record.
|
|
|
|
---------------------------------------------------------------------
|
|
Contract for a profile builder
|
|
---------------------------------------------------------------------
|
|
A generator supplies one function per profile that takes a GEO record and
|
|
returns a PROFILE record built with sb_profile():
|
|
|
|
members list of MEMBER records, one per strap bundle
|
|
shell region: all PLA+ before any void is removed
|
|
bore path: the enclosed central void, or [] if there is none
|
|
checks list from sb_check(), covering only this profile's own
|
|
parameters — never another profile's
|
|
info list of [key, value] pairs to add to the report
|
|
|
|
The core removes the bore and all cavities from the shell in one step, so
|
|
one member's plastic can never fill another member's channel. Everything
|
|
a builder needs to construct `shell` is in sb-join.scad; a builder should
|
|
not be doing its own boolean algebra.
|
|
|
|
---------------------------------------------------------------------
|
|
Design rules the library enforces
|
|
---------------------------------------------------------------------
|
|
1. Declared webs are cavity-to-cavity. A stated 1.2 mm web is 1.2 mm of
|
|
plastic; the library adds the fit clearance internally.
|
|
2. Placement that cannot be derived exactly is solved numerically against
|
|
the measured web, not approximated with a closed form.
|
|
3. Junctions are structural before they are pretty: members butt through
|
|
their neighbours, and fillets are applied on top of that overlap.
|
|
4. A member with no enclosed side gets the outside wall on both faces, so
|
|
asymmetric wall settings never make a symmetric profile chiral.
|
|
5. Validation measures the finished section. Connectivity is necessary
|
|
but never sufficient; the minimum wall is what is actually checked.
|
|
*/
|
|
|
|
include <BOSL2/std.scad>
|
|
|
|
include <sb-geom.scad>
|
|
include <sb-join.scad>
|
|
include <sb-report.scad>
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PROFILE record
|
|
// ---------------------------------------------------------------------------
|
|
|
|
SB_P_MEMBERS = 0;
|
|
SB_P_SHELL = 1;
|
|
SB_P_BORE = 2;
|
|
SB_P_CHECKS = 3;
|
|
SB_P_INFO = 4;
|
|
|
|
function sb_profile(members, shell, bore = [], checks = [], info = []) =
|
|
[members, shell, bore, checks, info];
|
|
|
|
function sb_p_members(p) = p[SB_P_MEMBERS];
|
|
function sb_p_shell(p) = p[SB_P_SHELL];
|
|
function sb_p_bore(p) = p[SB_P_BORE];
|
|
function sb_p_checks(p) = p[SB_P_CHECKS];
|
|
function sb_p_info(p) = p[SB_P_INFO];
|
|
|
|
// A builder that could not produce a usable arrangement returns this instead
|
|
// of guessing. The message reaches the user through the normal check list.
|
|
function sb_profile_failed(message) =
|
|
sb_profile([], [], [], [sb_check(false, message)], []);
|
|
|
|
function sb_profile_ok(p) = len(sb_p_members(p)) > 0;
|
|
|
|
|
|
// Shared arrangements. Included last because they build PROFILE records.
|
|
include <sb-profiles.scad>
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Centred results
|
|
// ---------------------------------------------------------------------------
|
|
/*
|
|
Everything is generated about whatever origin the profile found natural,
|
|
then shifted once so the finished envelope's bounding box is centred. The
|
|
shift is applied to the section, the straps and the cavities together, so
|
|
they stay registered with each other.
|
|
*/
|
|
|
|
function sb_centred(p, g) =
|
|
let(
|
|
shell = sb_p_shell(p),
|
|
shift = sb_centering_shift(shell)
|
|
)
|
|
[
|
|
move(shift, p = sb_clean_region(sb_section(shell, sb_p_members(p), g, sb_p_bore(p)))),
|
|
move(shift, p = sb_strap_region(sb_p_members(p), g)),
|
|
move(shift, p = sb_cavity_region(sb_p_members(p), g)),
|
|
move(shift, p = shell),
|
|
shift
|
|
];
|
|
|
|
SB_C_SECTION = 0;
|
|
SB_C_STRAPS = 1;
|
|
SB_C_CAVITY = 2;
|
|
SB_C_SHELL = 3;
|
|
SB_C_SHIFT = 4;
|
|
|
|
// Members translated by the same shift, for drawing individual laminae.
|
|
function sb_centred_members(p, shift) =
|
|
[for (m = sb_p_members(p))
|
|
sb_member(m[SB_M_CX] + shift.x, m[SB_M_CY] + shift.y,
|
|
sb_mang(m), sb_mface(m))];
|