require "bigdecimal" require "set" module Lace PREFIXES = { "femto" => 10**-15, "f" => 10**-15, "pico" => 10**-12, "p" => 10**-12, "nano" => 10**-9, "n" => 10**-9, "micro" => 10**-6, "u" => 10**-6, "ยต" => 10**-6, "milli" => 10**-3, "m" => 10**-3, "" => 10**0, "kilo" => 10**3, "k" => 10**3, "mega" => 10**6, "M" => 10**6, } def self.number(num) num = num.to_s PREFIXES.each do | p, d | if num =~ /^(.+)#{p}$/i begin num = BigDecimal($1) * d break rescue ArgumentError end end end num = BigDecimal(num) unless num.instance_of?(BigDecimal) return "0" if num == 0 prefix = "f" div = PREFIXES["f"] PREFIXES.each do | p, d | if num.abs >= d and d >= div and (d > div or p.size < prefix.size or (p.size == prefix.size and p > prefix)) prefix = p div = d end end num /= div num = num.to_s("F") num.sub!(/\.0$/, "") return "#{num}#{prefix}" end E3 = [10, 22, 47] E6 = E3 + [15, 33, 68] E12 = E6 + [12, 18, 27, 39, 56, 82] E24 = E12 + [11, 13, 16, 20, 24, 30, 36, 43, 51, 62, 75, 91] E48 = [100, 105, 110, 115, 121, 127, 133, 140, 147, 154, 162, 169, 178, 187, 196, 205, 215, 226, 237, 249, 261, 274, 287, 301, 316, 332, 348, 365, 383, 402, 422, 442, 464, 487, 511, 536, 562, 590, 619, 649, 681, 715, 750, 787, 825, 866, 909, 953] E96 = E48 + [102, 107, 113, 118, 124, 130, 137, 143, 150, 158, 165, 174, 182, 191, 200, 210, 221, 232, 243, 255, 267, 280, 294, 309, 324, 340, 357, 374, 392, 412, 432, 453, 475, 499, 523, 549, 576, 604, 634, 665, 698, 732, 768, 806, 845, 887, 931, 976] E192 = E96 + [101, 104, 106, 109, 111, 114, 117, 120, 123, 126, 129, 132, 135, 138, 142, 145, 149, 152, 156, 160, 164, 167, 172, 176, 180, 184, 189, 193, 198, 203, 208, 213, 218, 223, 229, 234, 240, 246, 252, 258, 264, 271, 277, 284, 291, 298, 305, 312, 320, 328, 336, 344, 352, 361, 370, 379, 388, 397, 407, 417, 427, 437, 448, 459, 470, 481, 493, 505, 517, 530, 542, 556, 569, 583, 597, 612, 626, 642, 657, 673, 690, 706, 723, 741, 759, 777, 796, 816, 835, 856, 876, 898, 920, 942, 965, 988] def self.nearest(num, set) return BigDecimal(0) if num == 0 base = set.min exp = Math.log10(base).round numexp = Math.log10(num).floor num = num * 10**(exp-numexp) candidate = nil delta = Float::INFINITY set.each do | c | d = (num - c).abs if d < delta candidate = c delta = d end end return BigDecimal(candidate * 10**(numexp-exp), exp+1) end def self.e3(num); nearest(num, E3); end def self.e6(num); nearest(num, E6); end def self.e12(num); nearest(num, E12); end def self.e24(num); nearest(num, E24); end def self.e48(num); nearest(num, E48); end def self.e96(num); nearest(num, E96); end def self.e192(num); nearest(num, E192); end class Netlist attr_reader :components attr_reader :nets def initialize(&p) @components = {} @nets = {} edit(&p) if p end def definitive_net(n) nn = @nets[n.object_id] return n if nn.object_id == n.object_id return definitive_net(nn) end def merge_nets(a, b) da = definitive_net(a) db = definitive_net(b) return da if da.object_id == db.object_id into = @nets[[da.object_id, db.object_id].min] if da.name raise "Nets #{into.name} and #{da.name} merged!" if into.name and into.name != da.name into.name = da.name end if db.name raise "Nets #{into.name} and #{db.name} merged!" if into.name and into.name != db.name into.name = db.name end @nets.each do | id, n | dn = definitive_net(n) if dn.object_id != into.object_id and (dn.object_id == da.object_id or dn.object_id == db.object_id) @nets[id] = into end end return into end def edit(&p) instance_eval &p end def comp(*args) c = Component.new(self, *args) @components[c.object_id] = c return c end def net(*args) n = Net.new(self, *args) @nets[n.object_id] = n return n end def pair(left, right) x = Wireable.new(self, left, right) return x end def real_refs refs = {} counters = {} seen = Set.new @components.each do | id, comp | next if seen.member?(comp.object_id) seen.add(comp.object_id) counter = counters[comp.refbase] || 1 counters[comp.refbase] = counter + 1 ref = "#{comp.refbase}#{counter}" refs[ref] = comp end return refs end def real_nets(refs) nets = {} seen = Set.new @nets.each do | id, net | next if seen.member?(net.object_id) seen.add(net.object_id) nodes = [] parts = [] refs.each do | ref, comp | comp.pins.each do | pin, pinnet | next unless definitive_net(pinnet).object_id == net.object_id nodes << {:ref => ref, :pin => pin} parts << "#{ref}$#{pin}" end end next unless nodes.size > 1 name = net.name name = parts.sort.join("_") unless name raise "Multiple disconnected nets named #{name}" if nets.member?(name) nets[name] = nodes end return nets end def real refs = real_refs nets = real_nets(refs) return [refs, nets] end def kicad(stream=$stdout) refs, nets = real stream.write(<