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.
216 lines
8.1 KiB
OpenSCAD
216 lines
8.1 KiB
OpenSCAD
/*
|
|
strap-beam-4x.scad — Four-strap longitudinal beam enclosures
|
|
============================================================
|
|
Companion to strap-beam-3x.scad. Four pallet-strap bundles run parallel
|
|
to one longitudinal axis (Z); a profile decides only how the four
|
|
cross-sections are arranged in XY.
|
|
|
|
Profiles
|
|
1. Square ring, four equal sides
|
|
2. Rectangle ring, caller-shaped by aspect ratio
|
|
3. Diamond ring, square rotated 45 degrees
|
|
4. Cross four spokes about a plugged centre
|
|
5. Four-Fin tangential square, one cyclic fin per side
|
|
|
|
---------------------------------------------------------------------
|
|
How much of this file is new
|
|
---------------------------------------------------------------------
|
|
None of the geometry. All five profiles are one call each into
|
|
lib/sb-profiles.scad with N = 4:
|
|
|
|
Square / Rectangle / Diamond -> sb_ring_polygon_profile()
|
|
Cross -> sb_spoke_profile(4, ...)
|
|
Four-Fin -> sb_fin_profile(4, ...)
|
|
|
|
The same three calls with N = 3 produce the Triangles, the Y and the
|
|
Three-Fin in strap-beam-3x.scad. The polygon fitting, the bore
|
|
derivation, the butt joints, the fillets, the validation and the report
|
|
are all member-count agnostic, so this file contains only parameters, a
|
|
catalogue table and the outline shapes.
|
|
|
|
A profile that genuinely does not generalise - the 3x A Frame and T, which
|
|
are specific arrangements rather than instances of a family - stays in its
|
|
own generator. That is the intended split: shapes that are an N-instance
|
|
live in the library, one-off arrangements live with their family.
|
|
*/
|
|
|
|
include <lib/sb-core.scad>
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parameters
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/* [Profile] */
|
|
profile_type = "Square"; // ["Square","Rectangle","Diamond","Cross","Four-Fin"]
|
|
|
|
/* [Pallet strap] */
|
|
strap_width_mm = 15.875;
|
|
strap_thickness_mm = 0.508;
|
|
bundle_count = 1; // [1:1:3]
|
|
|
|
/* [Longitudinal axis] */
|
|
member_length_ft = 10; // [10:1:100]
|
|
length_view = "Preview"; // ["Preview","Full Length"]
|
|
preview_length_mm = 100;
|
|
|
|
/* [PLA+ enclosure] */
|
|
fit_clearance_mm = 0.25;
|
|
inside_wall_thickness_mm = 1.20;
|
|
outside_wall_thickness_mm = 1.20;
|
|
edge_wall_thickness_mm = 1.20;
|
|
min_wall_mm = 1.20;
|
|
|
|
/* [Ring profiles] */
|
|
ring_corner_web_mm = 1.20;
|
|
// 1.25 mm is usable by all three ring outlines. The report publishes
|
|
// RING_CORNER_R_MAX_MM per profile if you want to push a specific entry.
|
|
ring_corner_radius_mm = 1.25;
|
|
// Proportions only; absolute size is solved from the strap width.
|
|
rectangle_aspect = 1.60;
|
|
|
|
/* [Cross Profile] */
|
|
cross_rotation_deg = 0;
|
|
cross_junction_web_mm = 1.20;
|
|
cross_junction_round_mm = 1.50;
|
|
|
|
/* [Four-Fin Profile] */
|
|
four_fin_fin_mm = 6.25;
|
|
four_fin_web_mm = 1.20;
|
|
four_fin_bore_side_mm = 7.50;
|
|
four_fin_rotation_deg = 0;
|
|
four_fin_junction_round_mm = 2.00;
|
|
|
|
/* [Display] */
|
|
render_mode = "3D"; // ["3D","Section"]
|
|
show_straps = true;
|
|
show_enclosure = true;
|
|
|
|
/* [Quality] */
|
|
facets = 48; // [12:4:128]
|
|
|
|
/* [Reporting] */
|
|
material_density_g_cm3 = 1.24;
|
|
|
|
$fn = facets;
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Derived
|
|
// ---------------------------------------------------------------------------
|
|
|
|
SB_FAMILY = "4x";
|
|
// Generator revision. Qualification attestations bind to this, so any change
|
|
// that alters emitted geometry MUST bump it. Cosmetic or comment-only edits
|
|
// must not. See sb-report.scad for what is published.
|
|
SB_REVISION = "8.0.0";
|
|
SB_MEMBER_COUNT = 4;
|
|
|
|
geo = sb_geo(
|
|
strap_width_mm, strap_thickness_mm, bundle_count, fit_clearance_mm,
|
|
inside_wall_thickness_mm, outside_wall_thickness_mm,
|
|
edge_wall_thickness_mm, min_wall_mm
|
|
);
|
|
|
|
model_length_mm = length_view == "Full Length"
|
|
? member_length_ft * 304.8
|
|
: preview_length_mm;
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Profiles
|
|
// ---------------------------------------------------------------------------
|
|
// Each is an outline plus a library call. The outlines set shape only; the
|
|
// ring fitter grows them until every corner carries ring_corner_web_mm.
|
|
|
|
function sb4_rect_outline(w, h) =
|
|
[[-w / 2, -h / 2], [w / 2, -h / 2], [w / 2, h / 2], [-w / 2, h / 2]];
|
|
|
|
function sb4_square(g) =
|
|
sb_ring_polygon_profile(sb4_rect_outline(1, 1), g,
|
|
ring_corner_web_mm, ring_corner_radius_mm, "Square");
|
|
|
|
function sb4_rectangle(g) =
|
|
sb_ring_polygon_profile(sb4_rect_outline(rectangle_aspect, 1), g,
|
|
ring_corner_web_mm, ring_corner_radius_mm, "Rectangle");
|
|
|
|
function sb4_diamond(g) =
|
|
sb_ring_polygon_profile([for (p = sb4_rect_outline(1, 1)) sb_rot2(p, 45)], g,
|
|
ring_corner_web_mm, ring_corner_radius_mm, "Diamond");
|
|
|
|
function sb4_cross(g) =
|
|
sb_spoke_profile(4, cross_rotation_deg, cross_junction_web_mm,
|
|
cross_junction_round_mm, g, "Cross");
|
|
|
|
function sb4_four_fin(g) =
|
|
sb_fin_profile(4, four_fin_fin_mm, four_fin_web_mm, four_fin_bore_side_mm,
|
|
four_fin_rotation_deg, four_fin_junction_round_mm, g,
|
|
"Four-Fin");
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Catalogue
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function sb4_build(name, g) =
|
|
name == "Square" ? sb4_square(g)
|
|
: name == "Rectangle" ? sb4_rectangle(g)
|
|
: name == "Diamond" ? sb4_diamond(g)
|
|
: name == "Cross" ? sb4_cross(g)
|
|
: name == "Four-Fin" ? sb4_four_fin(g)
|
|
: sb_profile_failed(str("Unknown profile_type: ", name));
|
|
|
|
profile = sb4_build(profile_type, geo);
|
|
|
|
base_checks = [
|
|
sb_check(strap_width_mm > 0, "strap_width_mm must be greater than zero."),
|
|
sb_check(strap_thickness_mm > 0, "strap_thickness_mm must be greater than zero."),
|
|
sb_check(bundle_count >= 1 && bundle_count == floor(bundle_count),
|
|
"bundle_count must be a whole number of at least 1."),
|
|
sb_check(fit_clearance_mm >= 0, "fit_clearance_mm cannot be negative."),
|
|
sb_check(inside_wall_thickness_mm > 0, "inside_wall_thickness_mm must be positive."),
|
|
sb_check(outside_wall_thickness_mm > 0, "outside_wall_thickness_mm must be positive."),
|
|
sb_check(edge_wall_thickness_mm > 0, "edge_wall_thickness_mm must be positive."),
|
|
sb_check(min_wall_mm > 0, "min_wall_mm must be positive."),
|
|
sb_check(rectangle_aspect > 0, "rectangle_aspect must be greater than zero."),
|
|
sb_check(preview_length_mm > 0, "preview_length_mm must be greater than zero.")
|
|
];
|
|
|
|
build_status = sb_require(concat(base_checks, sb_p_checks(profile)));
|
|
|
|
centred = sb_centred(profile, geo);
|
|
section = centred[SB_C_SECTION];
|
|
members = sb_centred_members(profile, centred[SB_C_SHIFT]);
|
|
metrics = sb_metrics(section, centred[SB_C_SHELL], members, geo);
|
|
|
|
status = sb_require(sb_universal_checks(section, metrics, SB_MEMBER_COUNT, geo));
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Report
|
|
// ---------------------------------------------------------------------------
|
|
|
|
sb_report(SB_FAMILY, profile_type, status, geo, metrics,
|
|
model_length_mm, material_density_g_cm3,
|
|
concat([sb_kv("REVISION", SB_REVISION),
|
|
sb_kv("LENGTH_FT", member_length_ft),
|
|
sb_kv("LENGTH_VIEW", length_view)],
|
|
sb_p_info(profile)));
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Output
|
|
// ---------------------------------------------------------------------------
|
|
|
|
if (render_mode == "Section") {
|
|
if (show_enclosure)
|
|
color([0.20, 0.55, 0.95]) sb_draw_section(section);
|
|
if (show_straps)
|
|
color([0.96, 0.72, 0.05]) sb_draw_section(centred[SB_C_STRAPS]);
|
|
} else {
|
|
if (show_enclosure)
|
|
color([0.20, 0.55, 0.95, 0.55]) sb_extrude_section(section, model_length_mm);
|
|
if (show_straps)
|
|
color([0.96, 0.72, 0.05, 0.82]) sb_extrude_straps(members, geo, model_length_mm);
|
|
}
|