Class: Gerber::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/gerber/parser.rb

Overview

Read and parse Gerber files (RS-274X)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeParser

Returns a new instance of Parser.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/gerber/parser.rb', line 20

def initialize
    @apertures = []
    @eof = false
    @layers = []
    @layer_parsers = []
    @axis_mirror = {:a => 1, :b => 1}   # 1 => not mirrored, -1 => mirrored
    @axis_select = {:a => :x, :b => :y}
    @offset = Point[0,0]
    @polarity = :positive
    @rotation = 0.degrees
    @scale = Vector[0,0]
    @symbol_mirror = {:a => 1, :b => 1} # 1 => not mirrored, -1 => mirrored
    @units = nil

    @new_layer_polarity = :dark
end

Instance Attribute Details

#absoluteObject

Returns the value of attribute absolute.



14
15
16
# File 'lib/gerber/parser.rb', line 14

def absolute
  @absolute
end

#aperturesObject (readonly)

Returns the value of attribute apertures.



16
17
18
# File 'lib/gerber/parser.rb', line 16

def apertures
  @apertures
end

#decimal_placesObject

Returns the value of attribute decimal_places.



13
14
15
# File 'lib/gerber/parser.rb', line 13

def decimal_places
  @decimal_places
end

#eofObject (readonly)

Returns the value of attribute eof.



17
18
19
# File 'lib/gerber/parser.rb', line 17

def eof
  @eof
end

#integer_placesObject

Returns the value of attribute integer_places.



13
14
15
# File 'lib/gerber/parser.rb', line 13

def integer_places
  @integer_places
end

#layersObject (readonly)

Returns the value of attribute layers.



16
17
18
# File 'lib/gerber/parser.rb', line 16

def layers
  @layers
end

#total_placesObject (readonly)

Returns the value of attribute total_places.



18
19
20
# File 'lib/gerber/parser.rb', line 18

def total_places
  @total_places
end

#zero_omissionObject

Returns the value of attribute zero_omission.



14
15
16
# File 'lib/gerber/parser.rb', line 14

def zero_omission
  @zero_omission
end

Instance Method Details

#coordinate_format=(*args) ⇒ Object

Set the format used for coordinates

Parameters:

  • integer_places (Number)

    The number of digits to the left of the decimal point

  • decimal_places (Number)

    The number of digits to the right of the decimal point



48
49
50
51
# File 'lib/gerber/parser.rb', line 48

def coordinate_format=(*args)
    self.integer_places, self.decimal_places = args.flatten.map {|a| a.to_i }
    @total_places = self.decimal_places + self.integer_places
end

#new_layerObject

Create and return a new Layer::Parser



60
61
62
63
64
# File 'lib/gerber/parser.rb', line 60

def new_layer
    (@layer_parsers << Layer::Parser.new).last.polarity = @new_layer_polarity
    ('inch' == @units) ? @layer_parsers.last.set_inches : @layer_parsers.last.set_millimeters
    @layer_parsers.last
end

#parse(input) ⇒ Object

Parse the given IO stream

Parameters:

  • input (IO)

    An IO-like object to parse



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/gerber/parser.rb', line 80

def parse(input)
    input.each('*') do |block|
  block.strip!
  next if !block || block.empty?
  raise ParseError, "Found blocks after M02" if self.eof
  case block
      when /^%AM/ # Special handling for aperture macros
    parse_parameter((block + input.gets('%')).gsub(/[\n%]/,''))
      when /^%[A-Z]{2}/
    (block + input.gets('%')).gsub(/[\n%]/,'').gsub(/\* /,'').lines('*') {|b| parse_parameter(b)}
      when /^D(\d{2,3})/
    current_layer.parse_dcode(block)
      when /^M0(0|1|2)/
    mcode = $1
    raise ParseError, "Invalid M code: #{m}" unless mcode
    @eof = true if mcode.to_i == 2
      when /^G54D(\d{2,3})/  # Deprecated G54 function code
    current_layer.parse_gcode(54, nil, nil, nil, nil, $1)
      when /^G70/
    set_inches
      when /^G71/
    set_millimeters
      when /^(G(\d{2}))?(X([\d\+-]+))?(Y([\d\+-]+))?(I([\d\+-]+))?(J([\d\+-]+))?(D0(1|2|3))?/
    gcode, dcode = $2, $12
    x, y, i, j = [$4, $6, $8, $10].map {|a| parse_coordinate(a) }
    current_layer.parse_gcode(gcode, x, y, i, j, dcode)
      else
    raise ParseError,"Unrecognized block: \"#{block}\""
  end
    end

    # FIXME apply any @rotation

    @layers = @layer_parsers.map {|parser| parser.layer }.select {|layer| !layer.empty? }

    gerber = Gerber.new
    gerber.apertures.replace @apertures
    gerber.coordinate_format = self.integer_places, self.decimal_places
    gerber.layers.replace @layers
    gerber.zero_omission = self.zero_omission
    gerber
end

#parse_coordinate(s) ⇒ Float

Convert a string into a Float using the current coordinate formating setting

Parameters:

  • s (String)

    The string to convert

Returns:

  • (Float)

    The resulting Float, or nil



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/gerber/parser.rb', line 126

def parse_coordinate(s)
    return nil unless s # Ignore nil coordinates so that they can be handled later

    sign = s.start_with?('-') ? '-' : '+'
    s.sub!(sign,'')

    if s.length < total_places
  if( zero_omission == :leading )
      s = s.rjust(total_places, '0')
  elsif( zero_omission == :trailing )
      s = s.ljust(total_places, '0')
  end
    end

    current_layer.apply_units((sign + s).insert(sign.length + integer_places, '.').to_f)
end

#parse_float(s) ⇒ Float

Convert a string into a Float and apply the appropriate Units

Parameters:

  • s (String)

    The string to convert

Returns:

  • (Float)

    The resulting Float with units, or nil



146
147
148
# File 'lib/gerber/parser.rb', line 146

def parse_float(s)
    apply_units(s.to_f)
end

#parse_parameter(s) ⇒ Object

Parse a set of parameter blocks



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/gerber/parser.rb', line 151

def parse_parameter(s)
    directive = s[0,2]
    case directive
  when 'AD'  # Section 4.1
      dcode, type = s.match(/ADD(\d{2,3})(\w+)/).captures
      dcode = dcode.to_i
      raise ParseError unless (dcode >= 10) and (dcode <= 999)
      case type
    when 'C'
        m = s.match(/C,(?<diameter>[\d.]+)(X(?<x>[\d.]+)(X(?<y>[\d.]+))?)?/)
        aperture = Aperture.new(:circle => parse_float(m[:diameter]))
        if( m[:x] )
      x = parse_float(m[:x])
      aperture.hole = m[:y] ? {:x => x, :y => parse_float(m[:y])} : x
        end

    when 'R'
        m = s.match(/R,(?<x>[\d.]+)X(?<y>[\d.]+)(X(?<hole_x>[\d.]+)(X(?<hole_y>[\d.]+))?)?/)
        aperture = Aperture.new(:rectangle => [parse_float(m[:x]), parse_float(m[:y])])
        if( m[:hole_x] )
      hole_x = parse_float(m[:hole_x])
      aperture.hole = m[:hole_y] ? {:x => hole_x, :y => parse_float(m[:hole_y])} : hole_x
        end

    when 'O'
        m = s.match(/O,(?<x>[\d.]+)X(?<y>[\d.]+)(X(?<hole_x>[\d.]+)(X(?<hole_y>[\d.]+))?)?/)
        aperture = Aperture.new(:obround => [parse_float(m[:x]), parse_float(m[:y])])
        if( m[:hole_x] )
      hole_x = parse_float(m[:hole_x])
      aperture.hole = m[:hole_y] ? {:x => hole_x, :y => parse_float(m[:hole_y])} : hole_x
        end

    when 'P'
        m = s.match(/P,(?<diameter>[\d.]+)X(?<sides>[\d.]+)(X(?<rotation>[\d.]+)(X(?<hole_x>[\d.]+)(X(?<hole_y>[\d.]+))?)?)?/)
        aperture = Aperture.new(:polygon => parse_float(m[:diameter]), :sides => m[:sides].to_i)
        if( m[:rotation] )
      aperture.rotation = m[:rotation].to_i.degrees
      if( m[:hole_x] )
          hole_x = parse_float(m[:hole_x])
          aperture.hole = m[:hole_y] ? {:x => hole_x, :y => parse_float(m[:hole_y])} : hole_x
      end
        end

    else    # Special Aperture
        captures = s.match(/#{type}(,([\d.]+)(X([\d.]+))*)?/).captures
        parameters = captures.values_at(* captures.each_index.select {|i| i.odd?}).select {|p| p }
        aperture = Aperture.new(:name=>type)
        aperture.parameters = parameters.map {|p| parse_float(p) } if( parameters && (0 != parameters.size ) )
      end
      self.apertures[dcode] = aperture

  # Section 4.2
  when 'AM'
   #    macro_name = block.match(/AM(\w*)\*/)[0]
      primitives = s.split '*'
      macro_name = primitives.shift.sub(/AM/,'')
      p "Aperature Macro: #{macro_name} => #{primitives}"
  when 'SM'  # Deprecated
      /^SM(A(0|1))?(B(0|1))?/ =~ s
      @symbol_mirror[:a] = ('1' == $1) ? -1 : 1
      @symbol_mirror[:b] = ('1' == $2) ? -1 : 1

  # Section 4.3 - Directive Parameters
  when 'AS'  # Deprecated
      /^ASA(X|Y)B(X|Y)/ =~ s
      raise ParseError, "The AS directive requires that both axes must be specified" unless $1 && $2
      raise ParseError, "Axis Select directive can't map both data axes to the same output axis" if $1 == $2
      @axis_select[:a] = $1.downcase.to_sym
      @axis_select[:b] = $2.downcase.to_sym
  when 'FS'
      /^FS(L|T)(A|I)(N\d)?(G\d)?X(\d)(\d)Y(\d)(\d)(D\d)?(M\d)?/ =~ s
      self.absolute = ($2 == 'A')
      self.zero_omission = ($1 == 'L') ? :leading : (($1 == 'T') ? :trailing : nil)
      xn, xm, yn, ym = $5, $6, $7, $8
      raise ParseError, "X and Y coordinate formats must equal" unless (xn == yn) && (xm == ym)
      self.coordinate_format = xn, xm
  when 'MI'  # Deprecated
      /^MIA(0|1)B(0|1)/ =~ s
      raise ParseError, "The MI directive requires that both axes be specified" unless $1 || $2
      @axis_mirror[:a] = ('0' == $1) ? 1 : -1
      @axis_mirror[:b] = ('0' == $2) ? 1 : -1
  when 'MO'
      /^MO(IN|MM)/ =~ s
      set_inches if 'IN' == $1
      set_millimeters if 'MM' == $1
  when 'OF'  # Deprecated
      /^OF(A([\d.+-]+))?(B([\d.+-]+))?/ =~ s
      @offset = Point[parse_float($2) || 0.0, parse_float($4) || 0.0]
  when 'SF'  # Deprecated
      /^SF(A([\d.+-]+))?(B([\d.+-]+))?/ =~ s
      @scale = Vector[parse_float($2) || 0.0, parse_float($4) || 0.0]

  # Section 4.4 - Image Parameters
  when 'IJ'  # Deprecated
  when 'IP'  # Deprecated
      /^IP(POS|NEG)/ =~ s
      current_layer.polarity = ('NEG' == $1) ? :negative : :positive
  when 'IR'  # Deprecated
      /^IR(0|90|180|270)/ =~ s
      @rotation = $1.to_f.degrees

  # Section 4.5 - Layer Specific Parameters
  when 'KO'  # Deprecated
      /^KO(C|D)?(X([\d.+-]+)Y([\d.+-]+)I([\d.+-]+)J([\d.+-]+))?/ =~ s
      polarity, x, y, i, j = $1, $3, $4, $5, $6
      raise ParseError, "KO not supported"
  when 'LN'
      /^LN([[:print:]]+)\*/ =~ s
      new_layer.name = $1
  when 'LP'
      /^LP(C|D)/ =~ s
      @new_layer_polarity = ('C' == $1) ? :clear : :dark
      current_layer.polarity = @new_layer_polarity
  when 'SR'
      /^SR(X(\d+))?(Y(\d+))?(I([\d.+-]+))?(J([\d.+-]+))?/ =~ s
      x, y, i, j = $2, $4, parse_float($6), parse_float($8)
      layer = new_layer
      layer.step = Vector[i || 0, j || 0]
      layer.repeat = Vector[x || 1, y || 1]
  else
      raise ParseError, "Unrecognized Parameter Type: '#{directive}'"
    end
end

#set_inchesObject

Assume that all dimensions are in inches



67
68
69
70
# File 'lib/gerber/parser.rb', line 67

def set_inches
    @units = 'inch'
    current_layer.set_inches
end

#set_millimetersObject

Assume that all dimensions are in millimeters



73
74
75
76
# File 'lib/gerber/parser.rb', line 73

def set_millimeters
    @units = 'millimeters'
    current_layer.set_millimeters
end