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