gerber 0 → 1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in manufacturing.gemspec
3
+ # Specify your gem's dependencies in gerber.gemspec
4
4
  gemspec
@@ -0,0 +1,42 @@
1
+ # Gerber
2
+
3
+ Everything you need to read and write Gerber RS-274-D and [Extended Gerber RS-274X](http://en.wikipedia.org/wiki/Gerber_Format) files
4
+
5
+ Files created by this gem conform to the latest [Gerber format specification](http://www.ucamco.com/Portals/0/Public/The_Gerber_File_%20Format_Specification.pdf)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'gerber'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install gerber
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'gerber'
25
+
26
+ gerber = Gerber.read('somefile.gerber')
27
+ gerber.write('outfile.gerber')
28
+ ```
29
+
30
+ License
31
+ -------
32
+
33
+ Copyright 2012-2013 Brandon Fosdick <bfoz@bfoz.net> and released under the BSD license.
34
+
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,2 +1,9 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs.push "lib"
7
+ t.test_files = FileList['test/**/*.rb']
8
+ t.verbose = true
9
+ end
@@ -2,16 +2,18 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "gerber"
5
- gem.version = 0
5
+ gem.version = 1
6
6
 
7
7
  gem.authors = ["Brandon Fosdick"]
8
8
  gem.email = ["bfoz@bfoz.net"]
9
9
  gem.description = %q{Tools for working with Gerber and Extended Gerber files}
10
- gem.summary = %q{Everything you need to read and write Gerber RS-274-D and Extended Gerber RS-274-X files}
10
+ gem.summary = %q{Everything you need to read and write Gerber RS-274-D and Extended Gerber RS-274X files}
11
11
  gem.homepage = "http://github.com/bfoz/gerber"
12
12
 
13
13
  gem.files = `git ls-files`.split($\)
14
14
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
15
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
16
  gem.require_paths = ["lib"]
17
+
18
+ gem.add_dependency 'geometry', '>= 6'
17
19
  end
@@ -1,3 +1,44 @@
1
- module Gerber
2
- # Your code goes here...
1
+ require 'geometry'
2
+ require 'units'
3
+ require_relative 'gerber/layer/parser'
4
+
5
+ =begin
6
+ Read and write {http://en.wikipedia.org/wiki/Gerber_Format Gerber} files (RS-274X)
7
+ =end
8
+ class Gerber
9
+ ParseError = Class.new(StandardError)
10
+
11
+ Arc = Geometry::Arc
12
+ Line = Geometry::Line
13
+ Point = Geometry::Point
14
+
15
+ attr_accessor :integer_places, :decimal_places
16
+ attr_accessor :zero_omission
17
+
18
+ attr_reader :apertures, :layers
19
+
20
+ def initialize
21
+ @apertures = []
22
+ @layers = []
23
+ @polarity = :positive
24
+ @units = nil
25
+ end
26
+
27
+ # Set the format used for coordinates
28
+ # @param [Number] integer_places The number of digits to the left of the decimal point
29
+ # @param [Number] decimal_places The number of digits to the right of the decimal point
30
+ def coordinate_format=(*args)
31
+ self.integer_places, self.decimal_places = args.flatten.map {|a| a.to_i }
32
+ end
33
+
34
+ # Read and parse the given file into a {Gerber} object
35
+ # @return [Gerber] The resulting {Gerber} object, or nil on failure
36
+ def self.read(filename)
37
+ File.open(filename) do |f|
38
+ Gerber::Parser.new.parse(f)
39
+ end
40
+ end
41
+
42
+ def self.write(filename, container)
43
+ end
3
44
  end
@@ -0,0 +1,34 @@
1
+ require 'geometry'
2
+
3
+ =begin
4
+ An Aperture definition for an Extended {Gerber} file
5
+ =end
6
+ class Gerber
7
+ class Aperture
8
+ attr_reader :name, :shape
9
+ attr_accessor :hole, :parameters, :rotation
10
+
11
+ def initialize(parameters)
12
+ raise ArgumentError unless parameters.is_a? Hash
13
+
14
+ if parameters.has_key? :circle
15
+ @shape = Geometry::Circle.new [0,0], :diameter => parameters[:circle]
16
+ parameters.delete :circle
17
+ elsif parameters.has_key? :obround
18
+ @shape = Geometry::Obround.new [0,0], parameters[:obround]
19
+ parameters.delete :obround
20
+ elsif parameters.has_key? :polygon
21
+ @shape = Geometry::RegularPolygon.new parameters[:sides], [0,0], :diameter => parameters[:polygon]
22
+ parameters.delete :polygon
23
+ elsif parameters.has_key? :rectangle
24
+ @shape = Geometry::Rectangle.new [0,0], parameters[:rectangle]
25
+ parameters.delete :rectangle
26
+ end
27
+ parameters.each {|k| self.instance_variable_set("@#{k.first}", k.last) }
28
+ end
29
+
30
+ def ==(other)
31
+ (self.hole == other.hole) && (self.parameters == other.parameters) && (self.rotation == other.rotation) && (self.shape == other.shape)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ require 'gerber'
2
+ require 'units'
3
+
4
+ =begin
5
+ A Gerber information layer (not to be confused with a PCB layer)
6
+ =end
7
+ class Gerber
8
+ class Layer
9
+ attr_accessor :geometry, :name, :polarity, :step, :repeat
10
+
11
+ def initialize(*args)
12
+ super
13
+
14
+ self.geometry = []
15
+ @polarity = :dark
16
+ @repeat = Vector[1,1]
17
+ @step = Vector[0,0]
18
+ @units = nil
19
+ end
20
+
21
+ # @group Accessors
22
+ def empty?
23
+ self.geometry.empty?
24
+ end
25
+
26
+ def set_inches
27
+ @units = 'inch'
28
+ end
29
+
30
+ def set_millimeters
31
+ @units = 'millimeters'
32
+ end
33
+ # @endgroup
34
+ end
35
+ end
@@ -0,0 +1,207 @@
1
+ require 'gerber/layer'
2
+
3
+ class Gerber
4
+ class Layer
5
+ class Parser
6
+ DCodeError = Class.new(ParseError)
7
+
8
+ attr_reader :current_aperture
9
+ attr_reader :coordinate_mode
10
+ attr_reader :layer
11
+ attr_reader :position
12
+ attr_reader :quadrant_mode
13
+
14
+ def initialize(*args)
15
+ super
16
+
17
+ @coordinate_mode = :absolute
18
+ @dcode = 2 # off
19
+ @gcode = 1 # linear interpolation
20
+ @layer = Gerber::Layer.new
21
+ @position = Point[0,0]
22
+ @quadrant_mode = :single
23
+ @repeat = Vector[1,1]
24
+ @step = Vector[0,0]
25
+ @units = nil
26
+ end
27
+
28
+ def is_valid_geometry(arg)
29
+ arg.kind_of?(Line) || arg.kind_of?(Point) || arg.kind_of?(Arc)
30
+ end
31
+
32
+ def <<(arg)
33
+ raise ParseError, "Must set an aperture before generating geometry" unless self.current_aperture
34
+ self.layer.geometry[current_aperture] << arg if is_valid_geometry(arg)
35
+ end
36
+
37
+ # @group Accessors
38
+ def current_aperture=(arg)
39
+ @current_aperture = arg
40
+ self.layer.geometry[arg] = [] unless self.layer.geometry[arg].is_a?(Array)
41
+ end
42
+
43
+ # @return [String] The name of the current {Layer}
44
+ def name
45
+ self.layer.name
46
+ end
47
+
48
+ # Set the name of the current {Layer}
49
+ # @param [String] name An ASCII string to set the name to
50
+ def name=(name)
51
+ self.layer.name = name
52
+ end
53
+
54
+ # @return [Symbol] The polarity setting of the current {Layer} (:dark or :clear)
55
+ def polarity
56
+ self.layer.polarity
57
+ end
58
+
59
+ # Set the polarity of the current {Layer}
60
+ # @param [Symbol] polarity Set the current polarity to either :clear or :dark
61
+ def polarity=(polarity)
62
+ self.layer.polarity = polarity
63
+ end
64
+
65
+ def set_inches
66
+ @units = 'inch'
67
+ end
68
+
69
+ def set_millimeters
70
+ @units = 'millimeters'
71
+ end
72
+ # @endgroup
73
+
74
+ def parse_dcode(s)
75
+ /D(\d{2,3})/ =~ s
76
+ dcode = $1.to_i
77
+ case dcode
78
+ when 1, 2, 3
79
+ @dcode = dcode
80
+ when 10...999
81
+ self.current_aperture = dcode
82
+ else
83
+ raise ParseError, "Invalid D Code #{dcode}"
84
+ end
85
+ end
86
+
87
+ def parse_gcode(gcode, x=nil, y=nil, i=nil, j=nil, dcode=nil)
88
+ gcode = gcode ? gcode.to_i : @gcode
89
+ dcode = dcode ? dcode.to_i : @dcode
90
+ case gcode
91
+ when 1, 55 # G55 is deprecated, but behaves like G01
92
+ parse_g1(x, y, dcode)
93
+ @dcode = dcode
94
+ @gcode = gcode
95
+ when 2
96
+ parse_g2(x, y, i, j, dcode)
97
+ @dcode = dcode
98
+ @gcode = gcode
99
+ when 3
100
+ parse_g3(x, y, i, j, dcode)
101
+ @dcode = dcode
102
+ @gcode = gcode
103
+ when 4 # G04 is used for single-line comments. Ignore the block and carry on.
104
+ when 36
105
+ p "enable outline fill"
106
+ when 37
107
+ p "disable outline fill"
108
+ when 54
109
+ raise DCodeError, "G54 requires a D code (found #{x}, #{y}, #{dcode})" unless dcode
110
+ self.current_aperture = dcode.to_i
111
+ when 70
112
+ set_inches
113
+ when 71
114
+ set_millimeters
115
+ when 74
116
+ @quadrant_mode = :single
117
+ when 75
118
+ @quadrant_mode = :multi
119
+ when 90
120
+ @coordinate_mode = :absolute
121
+ when 91
122
+ @coordinate_mode = :incremental
123
+ else
124
+ raise ParseError, "Unrecognized GCode #{gcode}"
125
+ end
126
+ end
127
+
128
+ def parse_g1(x, y, dcode)
129
+ point = Point[apply_units(x) || @position.x, apply_units(y) || @position.y]
130
+ case dcode
131
+ when 1
132
+ line = Geometry::Line[@position, point]
133
+ self << line
134
+ @position = point
135
+ when 2
136
+ @position = point
137
+ when 3
138
+ self << point
139
+ @position = point
140
+ else
141
+ raise DCodeError, "Invalid D parameter (#{dcode}) in G1"
142
+ end
143
+ end
144
+
145
+ def parse_g2(x, y, i, j, dcode)
146
+ raise DCodeError, "In G2 dcode must be either 1 or 2" unless [1, 2].include? dcode
147
+ if 1 == dcode
148
+ x, y, i, j = [x, y, i, j].map {|a| apply_units(a)}
149
+ startPoint = if self.quadrant_mode == :single
150
+ # start and end are swapped in clockwise mode (Geometry::Arc defaults to counterclockwise)
151
+ # i and j should have the same signs as the x and y components of the vector from the startpoint to the endpoint
152
+ if self.coordinate_mode == :absolute
153
+ delta = Point[x, y] - @position
154
+ i = i * (delta.x<=>0)
155
+ j = j * (delta.y<=>0)
156
+ Point[x, y]
157
+ elsif self.coordinate_mode == :incremental
158
+ i = i * (x<=>0)
159
+ j = j * (y<=>0)
160
+ @position + Point[x, y]
161
+ end
162
+ elsif @quadrant_mode == :multi
163
+ Point[x, y]
164
+ else
165
+ raise ParseError, "Unrecognized quadrant mode: #{self.quadrant_mode}"
166
+ end
167
+ arc = Geometry::Arc.new(@position + Point[i, j], startPoint, @position)
168
+ self << arc
169
+ @position = arc.first
170
+ end
171
+ end
172
+
173
+ def parse_g3(x, y, i, j, dcode)
174
+ raise DCodeError, "In G3 dcode must be either 1 or 2" unless [1, 2].include? dcode
175
+ if 1 == dcode
176
+ x, y, i, j = [x, y, i, j].map {|a| apply_units(a)}
177
+ endPoint = if self.quadrant_mode == :single
178
+ # i and j should have the same signs as the x and y components of the vector from the startpoint to the endpoint
179
+ if self.coordinate_mode == :absolute
180
+ delta = Point[x, y] - @position
181
+ i = i * (delta.x<=>0)
182
+ j = j * (delta.y<=>0)
183
+ Point[x, y]
184
+ elsif self.coordinate_mode == :incremental
185
+ i = i * (x<=>0)
186
+ j = j * (y<=>0)
187
+ @position + Point[x, y]
188
+ end
189
+ elsif @quadrant_mode == :multi
190
+ Point[x, y]
191
+ else
192
+ raise ParseError, "Unrecognized quadrant mode: #{self.quadrant_mode}"
193
+ end
194
+ arc = Geometry::Arc.new(@position + Point[i, j], @position, endPoint)
195
+ self << arc
196
+ @position = arc.last
197
+ end
198
+ end
199
+
200
+ def apply_units(a)
201
+ raise ParseError, "Units must be set before specifying coordinates" unless @units
202
+ return nil unless a
203
+ (@units == 'inch') ? a.inch : a.mm
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,275 @@
1
+ require 'geometry'
2
+ require 'gerber'
3
+ require 'units'
4
+ require_relative 'aperture'
5
+ require_relative 'layer'
6
+ require_relative 'layer/parser'
7
+
8
+ class Gerber
9
+ =begin
10
+ Read and parse {http://en.wikipedia.org/wiki/Gerber_Format Gerber} files (RS-274X)
11
+ =end
12
+ class Parser
13
+ attr_accessor :integer_places, :decimal_places
14
+ attr_accessor :zero_omission, :absolute
15
+
16
+ attr_reader :apertures, :layers
17
+ attr_reader :eof
18
+ attr_reader :total_places
19
+
20
+ def initialize
21
+ @apertures = []
22
+ @eof = false
23
+ @layers = []
24
+ @layer_parsers = []
25
+ @axis_mirror = {:a => 1, :b => 1} # 1 => not mirrored, -1 => mirrored
26
+ @axis_select = {:a => :x, :b => :y}
27
+ @offset = Point[0,0]
28
+ @polarity = :positive
29
+ @rotation = 0.degrees
30
+ @scale = Vector[0,0]
31
+ @symbol_mirror = {:a => 1, :b => 1} # 1 => not mirrored, -1 => mirrored
32
+ @units = nil
33
+
34
+ @new_layer_polarity = :dark
35
+ end
36
+
37
+ # Apply the configured units to a number
38
+ def apply_units(a)
39
+ raise ParseError, "Units must be set before specifying dimensions" unless @units
40
+ return nil unless a
41
+ (@units == 'inch') ? a.inch : a.mm
42
+ end
43
+ private :apply_units
44
+
45
+ # Set the format used for coordinates
46
+ # @param [Number] integer_places The number of digits to the left of the decimal point
47
+ # @param [Number] decimal_places The number of digits to the right of the decimal point
48
+ def coordinate_format=(*args)
49
+ self.integer_places, self.decimal_places = args.flatten.map {|a| a.to_i }
50
+ @total_places = self.decimal_places + self.integer_places
51
+ end
52
+
53
+ # The {Layer} currently being parsed
54
+ def current_layer
55
+ @layer_parsers.last || new_layer
56
+ end
57
+ private :current_layer
58
+
59
+ # Create and return a new {Layer::Parser}
60
+ def new_layer
61
+ (@layer_parsers << Layer::Parser.new).last.polarity = @new_layer_polarity
62
+ ('inch' == @units) ? @layer_parsers.last.set_inches : @layer_parsers.last.set_millimeters
63
+ @layer_parsers.last
64
+ end
65
+
66
+ # Assume that all dimensions are in inches
67
+ def set_inches
68
+ @units = 'inch'
69
+ current_layer.set_inches
70
+ end
71
+
72
+ # Assume that all dimensions are in millimeters
73
+ def set_millimeters
74
+ @units = 'millimeters'
75
+ current_layer.set_millimeters
76
+ end
77
+
78
+ # Parse the given IO stream
79
+ # @param [IO] input An IO-like object to parse
80
+ def parse(input)
81
+ input.each('*') do |block|
82
+ block.strip!
83
+ next if !block || block.empty?
84
+ raise ParseError, "Found blocks after M02" if self.eof
85
+ case block
86
+ when /^%AM/ # Special handling for aperture macros
87
+ parse_parameter((block + input.gets('%')).gsub(/[\n%]/,''))
88
+ when /^%[A-Z]{2}/
89
+ (block + input.gets('%')).gsub(/[\n%]/,'').gsub(/\* /,'').lines('*') {|b| parse_parameter(b)}
90
+ when /^D(\d{2,3})/
91
+ current_layer.parse_dcode(block)
92
+ when /^M0(0|1|2)/
93
+ mcode = $1
94
+ raise ParseError, "Invalid M code: #{m}" unless mcode
95
+ @eof = true if mcode.to_i == 2
96
+ when /^G54D(\d{2,3})/ # Deprecated G54 function code
97
+ current_layer.parse_gcode(54, nil, nil, nil, nil, $1)
98
+ when /^G70/
99
+ set_inches
100
+ when /^G71/
101
+ set_millimeters
102
+ when /^(G(\d{2}))?(X([\d\+-]+))?(Y([\d\+-]+))?(I([\d\+-]+))?(J([\d\+-]+))?(D0(1|2|3))?/
103
+ gcode, dcode = $2, $12
104
+ x, y, i, j = [$4, $6, $8, $10].map {|a| parse_coordinate(a) }
105
+ current_layer.parse_gcode(gcode, x, y, i, j, dcode)
106
+ else
107
+ raise ParseError,"Unrecognized block: \"#{block}\""
108
+ end
109
+ end
110
+
111
+ # FIXME apply any @rotation
112
+
113
+ @layers = @layer_parsers.map {|parser| parser.layer }.select {|layer| !layer.empty? }
114
+
115
+ gerber = Gerber.new
116
+ gerber.apertures.replace @apertures
117
+ gerber.coordinate_format = self.integer_places, self.decimal_places
118
+ gerber.layers.replace @layers
119
+ gerber.zero_omission = self.zero_omission
120
+ gerber
121
+ end
122
+
123
+ # Convert a string into a {Float} using the current coordinate formating setting
124
+ # @param [String] s The string to convert
125
+ # @return [Float] The resulting {Float}, or nil
126
+ def parse_coordinate(s)
127
+ return nil unless s # Ignore nil coordinates so that they can be handled later
128
+
129
+ sign = s.start_with?('-') ? '-' : '+'
130
+ s.sub!(sign,'')
131
+
132
+ if s.length < total_places
133
+ if( zero_omission == :leading )
134
+ s = s.rjust(total_places, '0')
135
+ elsif( zero_omission == :trailing )
136
+ s = s.ljust(total_places, '0')
137
+ end
138
+ end
139
+
140
+ current_layer.apply_units((sign + s).insert(sign.length + integer_places, '.').to_f)
141
+ end
142
+
143
+ # Convert a string into a {Float} and apply the appropriate {Units}
144
+ # @param [String] s The string to convert
145
+ # @return [Float] The resulting {Float} with units, or nil
146
+ def parse_float(s)
147
+ apply_units(s.to_f)
148
+ end
149
+
150
+ # Parse a set of parameter blocks
151
+ def parse_parameter(s)
152
+ directive = s[0,2]
153
+ case directive
154
+ when 'AD' # Section 4.1
155
+ dcode, type = s.match(/ADD(\d{2,3})(\w+)/).captures
156
+ dcode = dcode.to_i
157
+ raise ParseError unless (dcode >= 10) and (dcode <= 999)
158
+ case type
159
+ when 'C'
160
+ m = s.match(/C,(?<diameter>[\d.]+)(X(?<x>[\d.]+)(X(?<y>[\d.]+))?)?/)
161
+ aperture = Aperture.new(:circle => parse_float(m[:diameter]))
162
+ if( m[:x] )
163
+ x = parse_float(m[:x])
164
+ aperture.hole = m[:y] ? {:x => x, :y => parse_float(m[:y])} : x
165
+ end
166
+
167
+ when 'R'
168
+ m = s.match(/R,(?<x>[\d.]+)X(?<y>[\d.]+)(X(?<hole_x>[\d.]+)(X(?<hole_y>[\d.]+))?)?/)
169
+ aperture = Aperture.new(:rectangle => [parse_float(m[:x]), parse_float(m[:y])])
170
+ if( m[:hole_x] )
171
+ hole_x = parse_float(m[:hole_x])
172
+ aperture.hole = m[:hole_y] ? {:x => hole_x, :y => parse_float(m[:hole_y])} : hole_x
173
+ end
174
+
175
+ when 'O'
176
+ m = s.match(/O,(?<x>[\d.]+)X(?<y>[\d.]+)(X(?<hole_x>[\d.]+)(X(?<hole_y>[\d.]+))?)?/)
177
+ aperture = Aperture.new(:obround => [parse_float(m[:x]), parse_float(m[:y])])
178
+ if( m[:hole_x] )
179
+ hole_x = parse_float(m[:hole_x])
180
+ aperture.hole = m[:hole_y] ? {:x => hole_x, :y => parse_float(m[:hole_y])} : hole_x
181
+ end
182
+
183
+ when 'P'
184
+ m = s.match(/P,(?<diameter>[\d.]+)X(?<sides>[\d.]+)(X(?<rotation>[\d.]+)(X(?<hole_x>[\d.]+)(X(?<hole_y>[\d.]+))?)?)?/)
185
+ aperture = Aperture.new(:polygon => parse_float(m[:diameter]), :sides => m[:sides].to_i)
186
+ if( m[:rotation] )
187
+ aperture.rotation = m[:rotation].to_i.degrees
188
+ if( m[:hole_x] )
189
+ hole_x = parse_float(m[:hole_x])
190
+ aperture.hole = m[:hole_y] ? {:x => hole_x, :y => parse_float(m[:hole_y])} : hole_x
191
+ end
192
+ end
193
+
194
+ else # Special Aperture
195
+ captures = s.match(/#{type}(,([\d.]+)(X([\d.]+))*)?/).captures
196
+ parameters = captures.values_at(* captures.each_index.select {|i| i.odd?}).select {|p| p }
197
+ aperture = Aperture.new(:name=>type)
198
+ aperture.parameters = parameters.map {|p| parse_float(p) } if( parameters && (0 != parameters.size ) )
199
+ end
200
+ self.apertures[dcode] = aperture
201
+
202
+ # Section 4.2
203
+ when 'AM'
204
+ # macro_name = block.match(/AM(\w*)\*/)[0]
205
+ primitives = s.split '*'
206
+ macro_name = primitives.shift.sub(/AM/,'')
207
+ p "Aperature Macro: #{macro_name} => #{primitives}"
208
+ when 'SM' # Deprecated
209
+ /^SM(A(0|1))?(B(0|1))?/ =~ s
210
+ @symbol_mirror[:a] = ('1' == $1) ? -1 : 1
211
+ @symbol_mirror[:b] = ('1' == $2) ? -1 : 1
212
+
213
+ # Section 4.3 - Directive Parameters
214
+ when 'AS' # Deprecated
215
+ /^ASA(X|Y)B(X|Y)/ =~ s
216
+ raise ParseError, "The AS directive requires that both axes must be specified" unless $1 && $2
217
+ raise ParseError, "Axis Select directive can't map both data axes to the same output axis" if $1 == $2
218
+ @axis_select[:a] = $1.downcase.to_sym
219
+ @axis_select[:b] = $2.downcase.to_sym
220
+ when 'FS'
221
+ /^FS(L|T)(A|I)(N\d)?(G\d)?X(\d)(\d)Y(\d)(\d)(D\d)?(M\d)?/ =~ s
222
+ self.absolute = ($2 == 'A')
223
+ self.zero_omission = ($1 == 'L') ? :leading : (($1 == 'T') ? :trailing : nil)
224
+ xn, xm, yn, ym = $5, $6, $7, $8
225
+ raise ParseError, "X and Y coordinate formats must equal" unless (xn == yn) && (xm == ym)
226
+ self.coordinate_format = xn, xm
227
+ when 'MI' # Deprecated
228
+ /^MIA(0|1)B(0|1)/ =~ s
229
+ raise ParseError, "The MI directive requires that both axes be specified" unless $1 || $2
230
+ @axis_mirror[:a] = ('0' == $1) ? 1 : -1
231
+ @axis_mirror[:b] = ('0' == $2) ? 1 : -1
232
+ when 'MO'
233
+ /^MO(IN|MM)/ =~ s
234
+ set_inches if 'IN' == $1
235
+ set_millimeters if 'MM' == $1
236
+ when 'OF' # Deprecated
237
+ /^OF(A([\d.+-]+))?(B([\d.+-]+))?/ =~ s
238
+ @offset = Point[parse_float($2) || 0.0, parse_float($4) || 0.0]
239
+ when 'SF' # Deprecated
240
+ /^SF(A([\d.+-]+))?(B([\d.+-]+))?/ =~ s
241
+ @scale = Vector[parse_float($2) || 0.0, parse_float($4) || 0.0]
242
+
243
+ # Section 4.4 - Image Parameters
244
+ when 'IJ' # Deprecated
245
+ when 'IP' # Deprecated
246
+ /^IP(POS|NEG)/ =~ s
247
+ current_layer.polarity = ('NEG' == $1) ? :negative : :positive
248
+ when 'IR' # Deprecated
249
+ /^IR(0|90|180|270)/ =~ s
250
+ @rotation = $1.to_f.degrees
251
+
252
+ # Section 4.5 - Layer Specific Parameters
253
+ when 'KO' # Deprecated
254
+ /^KO(C|D)?(X([\d.+-]+)Y([\d.+-]+)I([\d.+-]+)J([\d.+-]+))?/ =~ s
255
+ polarity, x, y, i, j = $1, $3, $4, $5, $6
256
+ raise ParseError, "KO not supported"
257
+ when 'LN'
258
+ /^LN([[:print:]]+)\*/ =~ s
259
+ new_layer.name = $1
260
+ when 'LP'
261
+ /^LP(C|D)/ =~ s
262
+ @new_layer_polarity = ('C' == $1) ? :clear : :dark
263
+ current_layer.polarity = @new_layer_polarity
264
+ when 'SR'
265
+ /^SR(X(\d+))?(Y(\d+))?(I([\d.+-]+))?(J([\d.+-]+))?/ =~ s
266
+ x, y, i, j = $2, $4, parse_float($6), parse_float($8)
267
+ layer = new_layer
268
+ layer.step = Vector[i || 0, j || 0]
269
+ layer.repeat = Vector[x || 1, y || 1]
270
+ else
271
+ raise ParseError, "Unrecognized Parameter Type: '#{directive}'"
272
+ end
273
+ end
274
+ end
275
+ end