/* 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 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 include include include // --------------------------------------------------------------------------- // 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 // --------------------------------------------------------------------------- // 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))];