tweak the fit of the parts

This commit is contained in:
Nick Estes
2026-08-04 12:27:31 -07:00
parent 45a77885f9
commit 69d9b956e6
41 changed files with 5378 additions and 324 deletions
+84 -37
View File
@@ -10,6 +10,11 @@ module FeatherCaseWizard
FACES = %w[front rear left right].freeze
SHAPES = %w[circle rect roundrect slot].freeze
COUPONS = %w[insert_coupon switch_coupon fit_coupon].freeze
LID_ART_ORIENTATIONS = %w[usb_left usb_front usb_right usb_rear].freeze
LEGACY_SWITCH_FIELDS = %w[switch_hole_diameter switch_hole_compensation
switch_cap_diameter switch_flange_diameter
switch_body_length switch_body_diameter
switch_wire_allowance].freeze
DEFAULT_PARAMETERS = {
"top_profile" => "generic",
@@ -38,20 +43,20 @@ module FeatherCaseWizard
"battery_diameter" => 18.0,
"battery_radial_clearance" => 0.6,
"battery_end_clearance" => 1.0,
"switch_hole_diameter" => 12.0,
"switch_hole_compensation" => 0.3,
"switch_cap_diameter" => 8.0,
"switch_flange_diameter" => 16.2,
"switch_body_length" => 24.0,
"switch_body_diameter" => 12.0,
"switch_wire_allowance" => 3.0,
"battery_partition_thickness" => 1.8,
"rocker_cutout_size" => [12.9, 19.4],
"rocker_corner_radius" => 0.8,
"rocker_body_size" => [15.0, 21.0, 21.0],
"rocker_artwork_gap" => 2.0,
"tripod_insert_enabled" => false,
"tripod_insert_length" => 6.4,
"tripod_insert_hole_diameter" => 8.8,
"tripod_insert_length" => 5.0,
"tripod_insert_hole_diameter" => 7.2,
"tripod_insert_outer_diameter" => 8.0,
"tripod_thread_clearance_diameter" => 6.8,
"tripod_boss_diameter" => 14.0,
"m25_insert_length" => 4.0,
"m25_insert_hole_diameter" => 3.7,
"m25_insert_hole_diameter" => 3.1,
"m25_insert_outer_diameter" => 3.5,
"m25_screw_clearance" => 2.8,
"m25_boss_diameter" => 7.0,
"insert_entry_chamfer" => 0.6,
@@ -76,6 +81,14 @@ module FeatherCaseWizard
"usb_connector_overhang" => 2.0,
"usb_guide_wall_thickness" => 1.2,
"usb_guide_end_gap" => 1.0,
"lid_label" => "",
"lid_logo_enabled" => false,
"lid_art_orientation" => "usb_left",
"lid_inlay_depth" => 0.6,
"lid_inlay_fit_clearance" => 0.08,
"lid_label_size" => 5.0,
"lid_label_logo_gap" => 2.0,
"lid_art_margin" => 8.5,
"usb_opening_width" => 12.5,
"usb_opening_height" => 7.0,
"usb_opening_radius" => 1.5,
@@ -87,19 +100,23 @@ module FeatherCaseWizard
gps_antenna_height gps_top_gap oled_component_height
oled_display_height oled_face_gap],
"Shell and fit" => %w[wall_thickness floor_thickness lid_thickness corner_radius
board_bottom_clearance board_side_clearance lid_lip_depth
lid_lip_thickness fit_clearance cutout_min_web hole_compensation
lid_screw_head_diameter lid_screw_head_height],
board_bottom_clearance board_side_clearance board_rear_clearance
usb_board_wall_gap lid_lip_depth lid_lip_thickness fit_clearance
cutout_min_web hole_compensation lid_screw_head_diameter
lid_screw_head_height],
"Lid artwork" => %w[lid_inlay_depth lid_inlay_fit_clearance lid_label_size
lid_label_logo_gap lid_art_margin],
"M2.5 inserts" => %w[m25_insert_length m25_insert_hole_diameter
m25_insert_outer_diameter
m25_screw_clearance m25_boss_diameter
insert_entry_chamfer insert_melt_relief],
"Tripod insert" => %w[tripod_insert_length tripod_insert_hole_diameter
tripod_insert_outer_diameter
tripod_thread_clearance_diameter tripod_boss_diameter],
"Battery and switch" => %w[battery_length battery_diameter battery_radial_clearance
battery_end_clearance switch_hole_diameter
switch_hole_compensation switch_cap_diameter
switch_flange_diameter switch_body_length
switch_body_diameter switch_wire_allowance],
battery_end_clearance battery_partition_thickness
rocker_cutout_size rocker_corner_radius
rocker_body_size rocker_artwork_gap],
"USB-C opening" => %w[usb_opening_width usb_opening_height
usb_opening_radius usb_vertical_offset usb_connector_overhang
usb_guide_wall_thickness usb_guide_end_gap],
@@ -158,6 +175,7 @@ module FeatherCaseWizard
def printable_parts
parts = %w[body lid]
parts.concat(%w[lid_base lid_accent]) if lid_artwork?
if parameters["top_profile"] == "oled128x64" &&
%w[button_a button_b button_c button_reset].any? { |key| parameters[key] }
parts << "buttons"
@@ -165,6 +183,10 @@ module FeatherCaseWizard
parts + outputs["coupons"]
end
def lid_artwork?
!parameters["lid_label"].empty? || parameters["lid_logo_enabled"]
end
def model_parameters
parameters.merge("extra_cutouts" => scad_cutouts)
end
@@ -179,7 +201,10 @@ module FeatherCaseWizard
p["m25_insert_length"] + p["insert_melt_relief"] + 0.6
].max
board_origin_x = p["wall_thickness"] + p["usb_board_wall_gap"]
board_origin_y = p["wall_thickness"] + p["board_side_clearance"]
combined_tripod_board_gutter = p["battery_enabled"] && p["tripod_insert_enabled"] ?
2.4 : 0.0
board_origin_y = p["wall_thickness"] + p["board_side_clearance"] +
combined_tripod_board_gutter
board_top_z = effective_floor + effective_board_clearance + 1.6
top_pcb_z = board_top_z + (
p["stack_height_override"].positive? ?
@@ -199,40 +224,40 @@ module FeatherCaseWizard
p["wall_thickness"]
if p["battery_enabled"]
partition_y = board_origin_y + 22.86 + 1
battery_y = partition_y + p["wall_thickness"] +
battery_y = partition_y + p["battery_partition_thickness"] +
p["battery_radial_clearance"] + p["battery_diameter"] / 2
battery_start_x = board_origin_x + p["battery_end_clearance"]
lid_boss_wall_overlap = 0.6
lid_boss_front_x = p["wall_thickness"] + p["m25_boss_diameter"] / 2 -
lid_boss_wall_overlap
battery_start_x = lid_boss_front_x + p["m25_boss_diameter"] / 2 +
p["battery_end_clearance"]
battery_end_x = battery_start_x + p["battery_length"]
switch_x = battery_end_x + [
p["switch_body_diameter"] / 2 + 0.8,
p["switch_flange_diameter"] / 2 + 0.5
].max
length = [plain_length, switch_x + p["switch_flange_diameter"] / 2 +
1.2 + p["wall_thickness"]].max
battery_length_required = battery_end_x + p["battery_end_clearance"] +
p["wall_thickness"] + p["m25_boss_diameter"] -
lid_boss_wall_overlap
length = [plain_length, battery_length_required].max
width = [plain_width, battery_y + p["battery_diameter"] / 2 +
p["battery_radial_clearance"] + p["wall_thickness"]].max
battery_z = effective_floor + p["battery_radial_clearance"] +
p["battery_diameter"] / 2
switch_z = [
effective_floor + p["switch_flange_diameter"] / 2 + 1,
battery_z
].max
battery_ceiling = [
battery_z + p["battery_diameter"] / 2 + p["battery_radial_clearance"],
switch_z + p["switch_flange_diameter"] / 2 + 1
].max
battery_ceiling = battery_z + p["battery_diameter"] / 2 +
p["battery_radial_clearance"]
rocker_ceiling = effective_floor + p["rocker_body_size"][2] +
p["fit_clearance"]
else
length = plain_length
width = plain_width
battery_ceiling = 0
rocker_ceiling = 0
end
{
"length" => length,
"width" => width,
"height" => [top_pcb_z + component_height + top_gap, battery_ceiling].max,
"height" => [top_pcb_z + component_height + top_gap,
battery_ceiling, rocker_ceiling].max,
"floor" => effective_floor,
"lip_bottom" => [top_pcb_z + component_height + top_gap, battery_ceiling].max -
p["lid_lip_depth"]
"lip_bottom" => [top_pcb_z + component_height + top_gap,
battery_ceiling, rocker_ceiling].max - p["lid_lip_depth"]
}
end
@@ -265,6 +290,15 @@ module FeatherCaseWizard
raise ConfigError, "Slug must contain lowercase letters, numbers, and single hyphens"
end
raise ConfigError, "Unsupported top profile" unless PROFILES.include?(parameters["top_profile"])
unless LID_ART_ORIENTATIONS.include?(parameters["lid_art_orientation"])
raise ConfigError, "Unsupported lid art orientation"
end
unless parameters["lid_label"].is_a?(String)
raise ConfigError, "lid_label must be text"
end
if lid_artwork? && parameters["top_profile"] == "oled128x64"
raise ConfigError, "Lid artwork is not supported with the OLED profile"
end
raise ConfigError, "wing_count must be between 1 and 8" unless (1..8).cover?(parameters["wing_count"])
raise ConfigError, "At least one export format is required" if outputs["formats"].empty?
unless (outputs["formats"] - FORMATS).empty?
@@ -289,8 +323,20 @@ module FeatherCaseWizard
parameters["stack_height_override"].negative?
raise ConfigError, "wall_thickness must be at least 1.2 mm" if
parameters["wall_thickness"] < 1.2
raise ConfigError, "battery_partition_thickness must be at least 1.2 mm" if
parameters["battery_partition_thickness"] < 1.2
raise ConfigError, "lid_lip_thickness must be at least 1.2 mm" if
parameters["lid_lip_thickness"] < 1.2
raise ConfigError, "lid_inlay_depth must be less than lid_thickness" if
parameters["lid_inlay_depth"] >= parameters["lid_thickness"]
if parameters["m25_insert_outer_diameter"] < parameters["m25_insert_hole_diameter"]
raise ConfigError, "m25_insert_outer_diameter must not be smaller than the pilot"
end
if parameters["tripod_insert_outer_diameter"] <
parameters["tripod_insert_hole_diameter"]
raise ConfigError,
"tripod_insert_outer_diameter must not be smaller than the pilot"
end
validate_cutouts!
self
@@ -305,6 +351,7 @@ module FeatherCaseWizard
legacy_usb_guide = !supplied_parameters.key?("usb_guide_enabled")
data["parameters"] = self.class.deep_copy(DEFAULT_PARAMETERS).merge(supplied_parameters)
data["parameters"]["usb_guide_enabled"] = false if legacy_usb_guide
LEGACY_SWITCH_FIELDS.each { |key| data["parameters"].delete(key) }
data["cutouts"] ||= []
data["outputs"] ||= {}
data["outputs"]["formats"] ||= ["3mf"]