update some deisng parameters and add new cases

This commit is contained in:
2026-07-29 14:32:22 -07:00
parent 683697604a
commit 45a77885f9
34 changed files with 4602 additions and 79 deletions
+84 -9
View File
@@ -14,13 +14,14 @@ module FeatherCaseWizard
DEFAULT_PARAMETERS = {
"top_profile" => "generic",
"wing_count" => 1,
"wing_pitch" => 11.0,
"wing_pitch" => 12.25,
"stack_height_override" => 0.0,
"generic_component_height" => 3.0,
"generic_top_gap" => 2.0,
"gps_antenna_height" => 4.0,
"gps_antenna_height" => 6.0,
"gps_top_gap" => 5.0,
"oled_component_height" => 2.4,
"oled_display_height" => 1.5,
"oled_face_gap" => 0.8,
"button_a" => true,
"button_b" => true,
@@ -62,13 +63,19 @@ module FeatherCaseWizard
"board_bottom_clearance" => 5.4,
"board_side_clearance" => 7.0,
"board_rear_clearance" => 4.0,
"usb_board_wall_gap" => 1.2,
"usb_board_wall_gap" => 2.0,
"lid_lip_depth" => 2.4,
"lid_lip_thickness" => 1.2,
"fit_clearance" => 0.35,
"cutout_min_web" => 1.2,
"hole_compensation" => 0.2,
"lid_screw_head_diameter" => 5.2,
"lid_screw_head_height" => 2.5,
"usb_opening_enabled" => true,
"usb_guide_enabled" => true,
"usb_connector_overhang" => 2.0,
"usb_guide_wall_thickness" => 1.2,
"usb_guide_end_gap" => 1.0,
"usb_opening_width" => 12.5,
"usb_opening_height" => 7.0,
"usb_opening_radius" => 1.5,
@@ -77,11 +84,12 @@ module FeatherCaseWizard
ADVANCED_FIELDS = {
"Stack" => %w[wing_pitch generic_component_height generic_top_gap
gps_antenna_height gps_top_gap oled_component_height oled_face_gap],
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 board_rear_clearance
usb_board_wall_gap lid_lip_depth lid_lip_thickness fit_clearance
hole_compensation lid_screw_head_diameter lid_screw_head_height],
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],
"M2.5 inserts" => %w[m25_insert_length m25_insert_hole_diameter
m25_screw_clearance m25_boss_diameter
insert_entry_chamfer insert_melt_relief],
@@ -93,7 +101,8 @@ module FeatherCaseWizard
switch_flange_diameter switch_body_length
switch_body_diameter switch_wire_allowance],
"USB-C opening" => %w[usb_opening_width usb_opening_height
usb_opening_radius usb_vertical_offset],
usb_opening_radius usb_vertical_offset usb_connector_overhang
usb_guide_wall_thickness usb_guide_end_gap],
"OLED buttons" => %w[button_projection button_flange_thickness
button_travel button_contact_extension]
}.freeze
@@ -264,6 +273,9 @@ module FeatherCaseWizard
unless (outputs["coupons"] - COUPONS).empty?
raise ConfigError, "Unsupported calibration coupon"
end
if parameters["usb_guide_enabled"] && !parameters["usb_opening_enabled"]
raise ConfigError, "USB guide requires the USB opening"
end
positive = DEFAULT_PARAMETERS.keys - %w[usb_vertical_offset stack_height_override]
positive.each do |key|
@@ -289,7 +301,10 @@ module FeatherCaseWizard
def normalize!
data["schema_version"] = Integer(data.fetch("schema_version", SCHEMA_VERSION))
data["generator"] ||= {}
data["parameters"] = self.class.deep_copy(DEFAULT_PARAMETERS).merge(data["parameters"] || {})
supplied_parameters = data["parameters"] || {}
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
data["cutouts"] ||= []
data["outputs"] ||= {}
data["outputs"]["formats"] ||= ["3mf"]
@@ -302,6 +317,7 @@ module FeatherCaseWizard
def validate_cutouts!
dims = dimensions
names = {}
bounds = []
cutouts.each do |cutout|
name = cutout["name"].to_s.strip
raise ConfigError, "Every cutout needs a name" if name.empty?
@@ -328,9 +344,68 @@ module FeatherCaseWizard
radius = Float(cutout.fetch("radius", [width, height].min / 2))
raise ConfigError, "#{name} dimensions must be positive" unless
width.positive? && height.positive? && radius.positive?
bounds << {
"name" => name, "face" => face,
"left" => horizontal - width / 2, "right" => horizontal + width / 2,
"bottom" => z - height / 2, "top" => z + height / 2
}
rescue ArgumentError, TypeError, KeyError => e
raise ConfigError, "Invalid cutout #{name.inspect}: #{e.message}"
end
validate_cutout_spacing!(bounds, dims)
end
def validate_cutout_spacing!(bounds, dims)
web = parameters["cutout_min_web"]
bounds.combination(2) do |first, second|
next unless first["face"] == second["face"]
next if bounds_separated?(first, second, web)
raise ConfigError,
"#{first["name"]} and #{second["name"]} are too close on the #{first["face"]} wall"
end
return unless parameters["usb_opening_enabled"]
usb = usb_wall_bounds(dims)
bounds.select { |item| item["face"] == "front" }.each do |cutout|
next if bounds_separated?(cutout, usb, web)
raise ConfigError, "#{cutout["name"]} is too close to the USB opening or guide"
end
end
def bounds_separated?(first, second, web)
first["right"] + web <= second["left"] ||
second["right"] + web <= first["left"] ||
first["top"] + web <= second["bottom"] ||
second["top"] + web <= first["bottom"]
end
def usb_wall_bounds(dims)
p = parameters
effective_floor = p["tripod_insert_enabled"] ?
[p["floor_thickness"], p["tripod_insert_length"] + p["insert_melt_relief"] + 1.2].max :
p["floor_thickness"]
effective_board_clearance = [
p["board_bottom_clearance"],
p["m25_insert_length"] + p["insert_melt_relief"] + 0.6
].max
board_top_z = effective_floor + effective_board_clearance + 1.6
global_y = p["wall_thickness"] + p["board_side_clearance"] + 11.43
horizontal = dims["width"] - global_y
z = board_top_z + 1.6 + p["usb_vertical_offset"]
guide_length = [
p["usb_board_wall_gap"] - p["usb_connector_overhang"] - p["usb_guide_end_gap"], 0
].max
guide_active = p["usb_guide_enabled"] && guide_length.positive?
shell = guide_active ? 2 * p["usb_guide_wall_thickness"] : 0
width = p["usb_opening_width"] + p["hole_compensation"] + shell
height = p["usb_opening_height"] + p["hole_compensation"] + shell
{
"name" => "USB opening", "face" => "front",
"left" => horizontal - width / 2, "right" => horizontal + width / 2,
"bottom" => z - height / 2, "top" => z + height / 2
}
end
def stringify_keys(value)