Brian Silverman | 8fa2aad | 2017-06-10 16:45:30 -0700 | [diff] [blame] | 1 | VersionRegex = /v (\d{8}) (\d+)/ |
| 2 | ComponentRegex = /C (\d+) (\d+) ([01]) (\d{1,3}) ([01]) (.+)/ |
| 3 | TextRegex = /T (\d+) (\d+) (\d+) (\d+) ([01]) ([012]) (\d{1,3}) ([0-8]) (\d+)/ |
Brian Silverman | de75645 | 2017-07-02 12:43:02 -0700 | [diff] [blame] | 4 | PowerRegex = /^(([0-9.]+V-plus)|(title-.)|(gnd)|(output)|(input)|(io)|(vcc)|-1)|(vbat)|(vhalfbat)|(generic-power).sym$/ |
Brian Silverman | 8fa2aad | 2017-06-10 16:45:30 -0700 | [diff] [blame] | 5 | |
| 6 | # All coordinates are in "mils". |
| 7 | |
| 8 | class Component |
| 9 | attr_accessor :x, :y, :selectable, :angle, :mirrored, :filename |
| 10 | attr_accessor :attributes |
| 11 | |
| 12 | def initialize(x, y, selectable, angle, mirrored, filename) |
| 13 | @x, @y, @selectable = x, y, selectable |
| 14 | @angle, @mirrored, @filename = angle, mirrored, filename |
| 15 | |
| 16 | @attributes = {} |
| 17 | end |
| 18 | |
| 19 | def refdes |
| 20 | if attributes.has_key?(:refdes) |
| 21 | attributes[:refdes].value |
| 22 | else |
| 23 | nil |
| 24 | end |
| 25 | end |
| 26 | |
| 27 | def [](key) |
| 28 | r = attributes[key] |
| 29 | if r |
| 30 | r.value |
| 31 | else |
| 32 | nil |
| 33 | end |
| 34 | end |
| 35 | |
| 36 | def is_power |
| 37 | filename =~ PowerRegex |
| 38 | end |
| 39 | end |
| 40 | |
| 41 | def parse_bool(string) |
| 42 | if string == '0' |
| 43 | return false |
| 44 | elsif string == '1' |
| 45 | return true |
| 46 | else |
| 47 | raise "'#{string}' is not a bool" |
| 48 | end |
| 49 | end |
| 50 | |
| 51 | class Attribute |
| 52 | attr_accessor :x, :y, :color, :size, :visible, :show, :angle, :alignment |
| 53 | attr_accessor :name, :value |
| 54 | |
| 55 | def initialize(x, y, color, size, visible, show, angle, alignment, name, value) |
| 56 | @x, @y, @color, @size, @visible, @show = x, y, color, size, visible, show |
| 57 | @angle, @alignment, @name, @value = angle, alignment, name, value |
| 58 | end |
| 59 | |
| 60 | def Attribute.parse(match, value_line) |
| 61 | raise "Don't know what multi-line attributes mean" if match[9] != '1' |
| 62 | attribute_value = value_line.chomp.split('=', 2) |
| 63 | |
| 64 | Attribute.new(match[1].to_i, |
| 65 | match[2].to_i, |
| 66 | match[3].to_i, |
| 67 | match[4].to_i, |
| 68 | parse_bool(match[5]), |
| 69 | match[6], |
| 70 | match[7].to_i, |
| 71 | match[8].to_i, |
| 72 | attribute_value[0], |
| 73 | attribute_value[1]) |
| 74 | end |
| 75 | end |
| 76 | |
| 77 | class GschemFile |
| 78 | attr_accessor :version_date, :version |
| 79 | |
| 80 | def initialize(filename) |
| 81 | lines = nil |
| 82 | File.open(filename) do |f| |
| 83 | lines = f.readlines |
| 84 | end |
| 85 | version_line = lines.shift |
| 86 | version_match = VersionRegex.match version_line |
| 87 | raise "Can't parse version line '#{version_line}'" unless version_match |
| 88 | @version_date = version_match[1] |
| 89 | @version = version_match[2].to_i |
| 90 | |
| 91 | return lines |
| 92 | end |
| 93 | end |
| 94 | |
| 95 | class GschemSymbol < GschemFile |
| 96 | attr_accessor :attributes |
| 97 | |
| 98 | def initialize(filename) |
| 99 | lines = super(filename) |
| 100 | |
| 101 | @attributes = {} |
| 102 | |
| 103 | until lines.empty? |
| 104 | line = lines.shift |
| 105 | |
| 106 | match = TextRegex.match(line) |
| 107 | if match |
| 108 | attribute = Attribute.parse(match, lines.shift) |
| 109 | @attributes[attribute.name.intern] = attribute |
| 110 | end |
| 111 | end |
| 112 | end |
| 113 | |
| 114 | def [](key) |
| 115 | r = attributes[key] |
| 116 | if r |
| 117 | r.value |
| 118 | else |
| 119 | nil |
| 120 | end |
| 121 | end |
| 122 | end |
| 123 | |
| 124 | class GschemSchematic < GschemFile |
| 125 | attr_accessor :components |
| 126 | |
| 127 | def initialize(filename) |
| 128 | lines = super(filename) |
| 129 | |
| 130 | @components = [] |
| 131 | |
| 132 | until lines.empty? |
| 133 | line = lines.shift |
| 134 | |
| 135 | match = ComponentRegex.match(line) |
| 136 | if match |
| 137 | c = Component.new(match[1].to_i, |
| 138 | match[2].to_i, |
| 139 | parse_bool(match[3]), |
| 140 | match[4].to_i, |
| 141 | parse_bool(match[5]), |
| 142 | match[6]) |
| 143 | line = lines.shift |
| 144 | unless line |
| 145 | # There's a component without {} at the end of the file. |
| 146 | raise "Huh?" unless lines.empty? |
| 147 | next |
| 148 | end |
| 149 | if line.chomp == '{' |
| 150 | line = lines.shift |
| 151 | until line.chomp == '}' |
| 152 | match = TextRegex.match(line) |
| 153 | raise "Error parsing attribute '#{line}'" unless match |
| 154 | |
| 155 | attribute = Attribute.parse(match, lines.shift) |
| 156 | c.attributes[attribute.name.intern] = attribute |
| 157 | |
| 158 | line = lines.shift |
| 159 | end |
| 160 | else |
| 161 | lines.unshift line |
| 162 | end |
| 163 | @components.push c |
| 164 | next |
| 165 | end |
| 166 | end |
| 167 | end |
| 168 | end |
| 169 | |
| 170 | def get_schematic_filenames files |
| 171 | r = [] |
| 172 | files.each do |file| |
| 173 | if file.end_with? '.gsch2pcb' |
| 174 | File.open(file, 'r') do |f| |
Brian Silverman | 856af59 | 2017-12-18 11:17:09 -0500 | [diff] [blame] | 175 | dirname = File.dirname(file) + '/' |
| 176 | r += f.readline.split(' ')[1..-1].map do |filename| |
| 177 | dirname + filename |
| 178 | end |
Brian Silverman | 8fa2aad | 2017-06-10 16:45:30 -0700 | [diff] [blame] | 179 | end |
| 180 | else |
| 181 | r.push file |
| 182 | end |
| 183 | end |
| 184 | r |
| 185 | end |