110 lines
4.1 KiB
Ruby
110 lines
4.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module MoonModel
|
|
class Config
|
|
MOON_DATUM_DIAMETER_MM = 3_474_800_000.0
|
|
X2D = {
|
|
"name" => "Bambu Lab X2D",
|
|
"tooling" => "both_nozzles",
|
|
"build_volume_mm" => [235.5, 256.0, 256.0],
|
|
"main_only_volume_mm" => [256.0, 256.0, 260.0]
|
|
}.freeze
|
|
|
|
DEFAULTS = {
|
|
"printer" => X2D,
|
|
"nozzle_mm" => 0.4,
|
|
"edge_clearance_mm" => 2.0,
|
|
"purpose" => "display",
|
|
"assembly" => "auto",
|
|
"size" => { "input" => "diameter", "value" => 120.0, "unit" => "mm", "meaning" => "final_envelope" },
|
|
"vertical_exaggeration" => 1.0,
|
|
"palette" => [{ "name" => "Moon gray", "hex" => "#A9A9A9" }],
|
|
"grid" => { "enabled" => false, "interval_degrees" => 30.0 },
|
|
"shell" => {},
|
|
"magnets" => { "diameter_mm" => 6.0, "thickness_mm" => 3.0, "count" => 6 },
|
|
"stand" => { "enabled" => false, "style" => "orbital_triskelion", "base_clearance_mm" => 10.0, "material" => nil },
|
|
"resolution" => { "longitude_segments" => nil },
|
|
"output" => { "name" => "moon" }
|
|
}.freeze
|
|
|
|
attr_reader :data
|
|
|
|
def initialize(values = {})
|
|
@data = deep_merge(Marshal.load(Marshal.dump(DEFAULTS)), stringify(values))
|
|
derive!
|
|
validate!
|
|
end
|
|
|
|
def [](key) = data.fetch(key.to_s)
|
|
|
|
def to_h = Marshal.load(Marshal.dump(data))
|
|
|
|
def save(path)
|
|
File.write(path, YAML.dump(to_h))
|
|
end
|
|
|
|
def diameter_mm
|
|
size = self["size"]
|
|
return MOON_DATUM_DIAMETER_MM / Float(size["value"]) if size["input"] == "scale"
|
|
|
|
value = Float(size["value"])
|
|
size["unit"] == "in" ? value * 25.4 : value
|
|
end
|
|
|
|
def build_volume
|
|
self["printer"]["build_volume_mm"].map(&:to_f)
|
|
end
|
|
|
|
def segmented?
|
|
self["purpose"] == "box" || self["assembly"] == "eight_piece" ||
|
|
(self["assembly"] == "auto" && diameter_mm > maximum_diameter("one_piece"))
|
|
end
|
|
|
|
def maximum_diameter(mode)
|
|
usable = build_volume.map { |v| v - 2.0 * self["edge_clearance_mm"] }
|
|
mode == "one_piece" ? usable.min : 2.0 * usable.min
|
|
end
|
|
|
|
private
|
|
|
|
def derive!
|
|
nozzle = Float(data["nozzle_mm"])
|
|
shell = data["shell"]
|
|
shell["wall_mm"] ||= 4.0 * nozzle
|
|
shell["inlay_depth_mm"] ||= 2.0 * nozzle
|
|
shell["minimum_feature_mm"] ||= 2.0 * nozzle
|
|
shell["joint_clearance_mm"] ||= [0.2, 0.5 * nozzle].max
|
|
data["grid"]["line_width_mm"] ||= 2.0 * nozzle
|
|
data["grid"]["depth_mm"] ||= nozzle
|
|
data["size"]["meaning"] = "datum" if data["size"]["input"] == "scale"
|
|
end
|
|
|
|
def validate!
|
|
raise ArgumentError, "nozzle must be positive" unless self["nozzle_mm"].to_f.positive?
|
|
raise ArgumentError, "build volume requires three positive dimensions" unless build_volume.length == 3 && build_volume.all?(&:positive?)
|
|
raise ArgumentError, "purpose must be display or box" unless %w[display box].include?(self["purpose"])
|
|
raise ArgumentError, "assembly must be auto, one_piece, or eight_piece" unless %w[auto one_piece eight_piece].include?(self["assembly"])
|
|
raise ArgumentError, "unsupported stand style" unless self["stand"]["style"] == "orbital_triskelion"
|
|
raise ArgumentError, "stand base clearance must be nonnegative" if Float(self["stand"]["base_clearance_mm"]).negative?
|
|
raise ArgumentError, "palette must contain at least one color" if self["palette"].empty?
|
|
self["palette"].each do |color|
|
|
raise ArgumentError, "invalid color #{color.inspect}" unless color["name"].to_s.match?(/\S/) && color["hex"].to_s.match?(/\A#[0-9a-fA-F]{6}\z/)
|
|
end
|
|
raise ArgumentError, "diameter/scale must be positive" unless diameter_mm.positive?
|
|
raise ArgumentError, "vertical exaggeration must be nonnegative" if self["vertical_exaggeration"].to_f.negative?
|
|
end
|
|
|
|
def deep_merge(left, right)
|
|
left.merge(right) { |_key, a, b| a.is_a?(Hash) && b.is_a?(Hash) ? deep_merge(a, b) : b }
|
|
end
|
|
|
|
def stringify(value)
|
|
case value
|
|
when Hash then value.to_h { |key, val| [key.to_s, stringify(val)] }
|
|
when Array then value.map { |val| stringify(val) }
|
|
else value
|
|
end
|
|
end
|
|
end
|
|
end
|