eaglecad 0 → 1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Gemfile +5 -1
  2. data/README.markdown +29 -0
  3. data/Rakefile +9 -0
  4. data/eaglecad.gemspec +4 -2
  5. data/lib/eaglecad.rb +15 -1
  6. data/lib/eaglecad/attribute.rb +47 -0
  7. data/lib/eaglecad/board.rb +184 -0
  8. data/lib/eaglecad/clearance.rb +31 -0
  9. data/lib/eaglecad/design_rules.rb +38 -0
  10. data/lib/eaglecad/deviceset.rb +113 -0
  11. data/lib/eaglecad/drawing.rb +124 -0
  12. data/lib/eaglecad/geometry.rb +259 -0
  13. data/lib/eaglecad/layer.rb +40 -0
  14. data/lib/eaglecad/library.rb +73 -0
  15. data/lib/eaglecad/package.rb +74 -0
  16. data/lib/eaglecad/part.rb +29 -0
  17. data/lib/eaglecad/schematic.rb +83 -0
  18. data/lib/eaglecad/sheet.rb +256 -0
  19. data/lib/eaglecad/symbol.rb +73 -0
  20. data/test/eaglecad.rb +12 -0
  21. data/test/eaglecad/board.rb +68 -0
  22. data/test/eaglecad/design_rules.rb +38 -0
  23. data/test/eaglecad/deviceset.rb +52 -0
  24. data/test/eaglecad/drawing.rb +94 -0
  25. data/test/eaglecad/library.rb +47 -0
  26. data/test/eaglecad/package.rb +60 -0
  27. data/test/eaglecad/schematic.rb +50 -0
  28. data/test/eaglecad/sheet.rb +24 -0
  29. data/test/eaglecad/symbol.rb +41 -0
  30. data/test/fixtures/board.xml +859 -0
  31. data/test/fixtures/demo1.sch +10932 -0
  32. data/test/fixtures/design_rules.xml +70 -0
  33. data/test/fixtures/deviceset.xml +683 -0
  34. data/test/fixtures/drawing_board.xml +910 -0
  35. data/test/fixtures/drawing_schematic.xml +10928 -0
  36. data/test/fixtures/hexapodu.brd +1863 -0
  37. data/test/fixtures/library.xml +175 -0
  38. data/test/fixtures/package.xml +28 -0
  39. data/test/fixtures/schematic.xml +10861 -0
  40. data/test/fixtures/sheet.xml +318 -0
  41. data/test/fixtures/symbol.xml +11 -0
  42. metadata +79 -5
  43. data/README.md +0 -29
@@ -0,0 +1,124 @@
1
+ require 'rexml/document'
2
+
3
+ require_relative 'board'
4
+ require_relative 'layer'
5
+ require_relative 'schematic'
6
+
7
+ module EagleCAD
8
+ class Drawing
9
+ attr_accessor :board, :schematic
10
+ attr_reader :layers
11
+
12
+ # Grid attributes
13
+ attr_accessor :distance, :unitdistance, :unit, :style, :multiple, :display, :altdistance, :altunitdist, :altunit
14
+
15
+ # Settings attributes
16
+ attr_accessor :always_vector_font, :vertical_text
17
+
18
+ # @param element [REXML::Element]
19
+ def self.from_xml(element)
20
+ self.new.tap do |drawing|
21
+ element.elements.each do |element|
22
+ case element.name
23
+ when 'board'
24
+ raise StandardError, "Drawing files must contain only one Board element" if drawing.board
25
+ drawing.board = Board.from_xml(element)
26
+
27
+ when 'grid'
28
+ element.attributes.each do |name, value|
29
+ case name
30
+ when 'altdistance' then drawing.altdistance = value.to_f
31
+ when 'altunit' then drawing.altunit = value.to_sym
32
+ when 'altunitdist' then drawing.altunitdist = value.to_sym
33
+ when 'display' then drawing.display = ('no' != value)
34
+ when 'distance' then drawing.distance = value.to_f
35
+ when 'unit' then drawing.unit = value.to_sym
36
+ when 'unitdist' then drawing.unitdistance = value.to_sym
37
+ when 'multiple' then drawing.multiple = value.to_i
38
+ when 'style' then drawing.style = value.to_sym
39
+ end
40
+ end
41
+
42
+ when 'layers'
43
+ element.elements.each {|element| drawing.layers.push Layer.from_xml(element) }
44
+
45
+ when 'schematic'
46
+ raise StandardError, "Drawing files must contain only one Schematic element" if drawing.schematic
47
+ drawing.schematic = Schematic.from_xml(element)
48
+
49
+ when 'settings'
50
+ element.elements.each do |element|
51
+ element.attributes.each do |name, value|
52
+ case name
53
+ when 'alwaysvectorfont' then drawing.always_vector_font = ('no' != value)
54
+ when 'verticaltext' then drawing.vertical_text = value.to_sym
55
+ end
56
+ end
57
+ end
58
+
59
+ else
60
+ raise StandardError, "Unrecognized Drawing element '#{element.name}'"
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def initialize()
67
+ @layers = []
68
+ self.vertical_text = :up
69
+
70
+ self.display = false
71
+ self.multiple = 1
72
+ self.style = :lines
73
+ end
74
+
75
+ # Generate XML for the {Drawing} element
76
+ # @return [REXML::element]
77
+ def to_xml
78
+ drawing_element = REXML::Element.new 'drawing'
79
+
80
+ settings = REXML::Element.new 'settings', drawing_element
81
+ settings.add_element 'setting', {'alwaysvectorfont' => (always_vector_font ? 'yes' : 'no')}
82
+ settings.add_element 'setting', {'verticaltext' => vertical_text}
83
+
84
+ grid_element = REXML::Element.new 'grid', drawing_element
85
+ grid_element.add_attributes({ 'altdistance' => altdistance,
86
+ 'altunit' => altunit,
87
+ 'altunitdist' => altunitdist,
88
+ 'display' => (display ? 'yes' : 'no'),
89
+ 'distance' => distance,
90
+ 'multiple' => multiple,
91
+ 'unit' => unit,
92
+ 'unitdist' => unitdistance,
93
+ 'style' => style,
94
+ })
95
+
96
+ layers_element = REXML::Element.new 'layers', drawing_element
97
+ layers.each {|layer| layers_element.add_element layer.to_xml }
98
+
99
+ drawing_element.add_element(board.to_xml) if board
100
+ drawing_element.add_element(schematic.to_xml) if schematic
101
+
102
+ drawing_element
103
+ end
104
+
105
+ # @param filename [String] The path to write the output to
106
+ def write(output)
107
+ document = REXML::Document.new('<?xml version="1.0" encoding="utf-8"?><!DOCTYPE eagle SYSTEM "eagle.dtd">')
108
+
109
+ eagle = REXML::Element.new('eagle')
110
+ eagle.add_attribute('version', '6.0')
111
+ eagle.add_element to_xml
112
+ document.add eagle
113
+
114
+ output = File.open(output, 'w') if output.is_a? String
115
+
116
+ # This is a hack to force REXML to output PCDATA text inline with the enclosing element. Eagle has problems with the extra newlines that REXML tends to add.
117
+ formatter = REXML::Formatters::Pretty.new(0)
118
+ formatter.compact = true
119
+ formatter.write(document, output)
120
+
121
+ output.close
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,259 @@
1
+ require 'rexml/document'
2
+
3
+ require 'geometry'
4
+
5
+ module EagleCAD
6
+ module Geometry
7
+ Point = ::Geometry::Point
8
+
9
+ class Circle < ::Geometry::Circle
10
+ attr_accessor :line_width
11
+
12
+ # Create a {Circle} from an {REXML::Element}
13
+ # @param [Element] element The {REXML::Element} to parse
14
+ def self.from_xml(element)
15
+ self.new center:Geometry.point_from(element, 'x', 'y'), radius:element.attributes['radius'].to_f, line_width:element.attributes['width'].to_f
16
+ end
17
+
18
+ def initialize(options={})
19
+ @line_width = options.delete(:line_width)
20
+ super options[:center], options[:radius]
21
+ end
22
+
23
+ # @return [REXML::Element]
24
+ def to_xml
25
+ REXML::Element.new('circle').tap {|element| element.add_attributes({'x' => Geometry.format(center.x), 'y' => Geometry.format(center.y), 'radius' => Geometry.format(radius), 'width' => line_width}) }
26
+ end
27
+ end
28
+
29
+ Hole = Struct.new :origin, :drill do
30
+ def self.from_xml(element)
31
+ Geometry::Hole.new Geometry.point_from(element, 'x', 'y'), element.attributes['drill']
32
+ end
33
+
34
+ # @return [REXML::Element]
35
+ def to_xml
36
+ REXML::Element.new('hole').tap {|element| element.add_attributes({'x' => Geometry.format(origin.x), 'y' => Geometry.format(origin.y), 'drill' => drill}) }
37
+ end
38
+ end
39
+
40
+ class Line < ::Geometry::TwoPointLine
41
+ attr_accessor :cap, :curve, :line_width
42
+
43
+ # Create a {Line} from an {REXML::Element}
44
+ # @param [Element] element The {REXML::Element} to parse
45
+ def self.from_xml(element)
46
+ self.new(from:Geometry.point_from(element, 'x1', 'y1'), to:Geometry::point_from(element, 'x2', 'y2'), line_width:element.attributes['width'].to_f, cap: element.attributes['cap'], curve: element.attributes['curve'].to_f)
47
+ end
48
+
49
+ def initialize(options={})
50
+ @cap = options.delete :cap
51
+ @curve = options.delete :curve
52
+ @line_width = options.delete(:line_width)
53
+ super options[:from], options[:to]
54
+ end
55
+
56
+ # @return [REXML::Element]
57
+ def to_xml
58
+ REXML::Element.new('wire').tap do |element|
59
+ element.add_attributes({'x1' => Geometry.format(first.x), 'y1' => Geometry.format(first.y), 'x2' => Geometry.format(last.x), 'y2' => Geometry.format(last.y), 'width' => line_width})
60
+ element.add_attribute('cap', cap) unless 'round' == cap
61
+ element.add_attribute('curve', curve) unless 0 == curve
62
+ end
63
+ end
64
+ end
65
+
66
+ Pad = Struct.new :diameter, :drill, :name, :origin, :rotation, :shape do
67
+ def self.from_xml(element)
68
+ origin = Geometry.point_from(element, 'x', 'y')
69
+ Geometry::Pad.new(element.attributes['diameter'], element.attributes['drill'], element.attributes['name'], origin, element.attributes['rot'], element.attributes['shape'])
70
+ end
71
+
72
+ # @return [REXML::Element]
73
+ def to_xml
74
+ REXML::Element.new('pad').tap do |element|
75
+ element.add_attributes({'name' => name, 'x' => Geometry.format(origin.x), 'y' => Geometry.format(origin.y), 'diameter' => diameter, 'drill' => drill, 'shape' => shape})
76
+ element.add_attribute('rot', rotation) unless 'R0' == rotation
77
+ end
78
+ end
79
+ end
80
+
81
+ Pin = Struct.new :direction, :function, :length, :name, :origin, :swaplevel, :rotation, :visible do
82
+ def self.from_xml(element)
83
+ origin = Geometry.point_from(element, 'x', 'y')
84
+ Geometry::Pin.new(element.attributes['direction'], element.attributes['function'], element.attributes['length'], element.attributes['name'], origin, element.attributes['swaplevel'], element.attributes['rot'], element.attributes['visible'])
85
+ end
86
+
87
+ # @return [REXML::Element]
88
+ def to_xml
89
+ REXML::Element.new('pin').tap do |element|
90
+ element.add_attributes({'name' => name,
91
+ 'x' => Geometry.format(origin.x),
92
+ 'y' => Geometry.format(origin.y),
93
+ 'direction' => direction,
94
+ 'function' => function,
95
+ 'length' => length,
96
+ 'swaplevel' => swaplevel,
97
+ 'rot' => rotation,
98
+ 'visible' => visible})
99
+ end
100
+ end
101
+ end
102
+
103
+ class Polygon < ::Geometry::Polygon
104
+ attr_accessor :line_width
105
+
106
+ # Create a {Polygon} from an {REXML::Element}
107
+ # @param [Element] element The {REXML::Element} to parse
108
+ def self.from_xml(element)
109
+ width = element.attributes['width']
110
+ vertices = element.elements.map {|vertex| Geometry::point_from(vertex, 'x', 'y') }
111
+ self.new(*vertices, line_width:width)
112
+ end
113
+
114
+ def initialize(*args)
115
+ options, args = args.partition {|a| a.is_a? Hash}
116
+ options = options.reduce({}, :merge)
117
+
118
+ @line_width = options.delete(:line_width)
119
+
120
+ super *args
121
+ end
122
+
123
+ # @return [REXML::Element]
124
+ def to_xml
125
+ REXML::Element.new('polygon').tap do |element|
126
+ element.add_attribute 'width', line_width
127
+ vertices.each {|vertex| element.add_element('vertex', {'x' => Geometry.format(vertex.x), 'y' => Geometry.format(vertex.y)}) }
128
+ end
129
+ end
130
+ end
131
+
132
+ class Rectangle < ::Geometry::Rectangle
133
+ # Create a {Rectangle} from an {REXML::Element}
134
+ # @param [Element] element The {REXML::Element} to parse
135
+ def self.from_xml(element)
136
+ first = Geometry.point_from(element, 'x1', 'y1')
137
+ last = Geometry.point_from(element, 'x2', 'y2')
138
+ self.new(first, last)
139
+ end
140
+
141
+ # @return [REXML::Element]
142
+ def to_xml
143
+ REXML::Element.new('rectangle').tap {|element| element.add_attributes({'x1' => Geometry.format(origin.x),
144
+ 'y1' => Geometry.format(origin.y),
145
+ 'x2' => Geometry.format(max.x),
146
+ 'y2' => Geometry.format(max.y)}) }
147
+ end
148
+ end
149
+
150
+ class SMD < ::Geometry::SizedRectangle
151
+ attr_accessor :cream, :name, :roundness, :rotation, :stop, :thermals
152
+
153
+ # Create a {SMD} from an {REXML::Element}
154
+ # @param [Element] element The {REXML::Element} to parse
155
+ def self.from_xml(element)
156
+ size = Size[element.attributes['dx'].to_f, element.attributes['dy'].to_f]
157
+ SMD.new(origin:Geometry.point_from(element, 'x', 'y'), size:size).tap do |smd|
158
+ smd.cream = ('no' != element.attributes['cream'])
159
+ smd.name = element.attributes['name']
160
+ smd.rotation = element.attributes['rot']
161
+ smd.roundness = element.attributes['roundness'].to_i
162
+ smd.stop = ('no' != element.attributes['stop'])
163
+ smd.thermals = ('no' != element.attributes['thermals'])
164
+ end
165
+ end
166
+
167
+ # @return [REXML::Element]
168
+ def to_xml
169
+ REXML::Element.new('smd').tap do |element|
170
+ element.add_attributes({'name' => name,
171
+ 'x' => Geometry.format(origin.x),
172
+ 'y' => Geometry.format(origin.y),
173
+ 'dx' => Geometry.format(size.width),
174
+ 'dy' => Geometry.format(size.height)})
175
+ element.add_attribute('cream', 'no') unless cream
176
+ element.add_attribute('rot', rotation) if rotation
177
+ element.add_attribute('roundness', Geometry.format(roundness)) unless 0 == roundness
178
+ element.add_attribute('stop', 'no') unless stop
179
+ element.add_attribute('thermals', 'no') unless thermals
180
+ end
181
+ end
182
+ end
183
+
184
+ class Text
185
+ attr_accessor :align, :distance, :origin, :font, :layer, :ratio, :rotation, :size, :text
186
+
187
+ def self.from_xml(element)
188
+ Geometry::Text.new(Geometry.point_from(element, 'x', 'y'), element.attributes['layer'], element.attributes['size'].to_f, element.text).tap do |object|
189
+ object.align = element.attributes['align'] || object.align
190
+ object.distance = element.attributes['distance'] || object.distance
191
+ object.font = element.attributes['font'] || object.font
192
+ object.ratio = element.attributes['ratio'] || object.ratio
193
+ object.rotation = element.attributes['rot'] || object.rotation
194
+ end
195
+ end
196
+
197
+ def initialize(origin, layer, size, text, options={})
198
+ @origin = origin
199
+ @layer = layer
200
+ @size = size
201
+ @text = text
202
+
203
+ @align = options['align'] || 'bottom-left'
204
+ @distance = options['distance'] || 50
205
+ @font = options['font'] || 'proportional'
206
+ @ratio = options['ratio'] || 8
207
+ @rotation = options['rot'] || 'R0'
208
+ end
209
+
210
+ # @return [REXML::Element]
211
+ def to_xml
212
+ REXML::Element.new('text').tap do |element|
213
+ element.add_attributes({'x' => Geometry.format(origin.x), 'y' => Geometry.format(origin.y), 'layer' => layer, 'size' => Geometry.format(size)})
214
+ element.add_attribute('align', align)
215
+ element.add_attribute('distance', distance)
216
+ element.add_attribute('font', font)
217
+ element.add_attribute('ratio', ratio)
218
+ element.add_attribute('rot', rotation)
219
+ element.text = text
220
+ end
221
+ end
222
+ end
223
+
224
+ def self.from_xml(element)
225
+ case element.name
226
+ when 'circle'
227
+ Geometry::Circle.from_xml(element)
228
+ when 'hole'
229
+ Geometry::Hole.from_xml(element)
230
+ when 'pad'
231
+ Geometry::Pad.from_xml(element)
232
+ when 'pin'
233
+ Geometry::Pin.from_xml(element)
234
+ when 'polygon'
235
+ Geometry::Polygon.from_xml(element)
236
+ when 'rectangle'
237
+ Geometry::Rectangle.from_xml(element)
238
+ when 'smd'
239
+ Geometry::SMD.from_xml(element)
240
+ when 'text'
241
+ Geometry::Text.from_xml(element)
242
+ when 'wire'
243
+ Geometry::Line.from_xml(element)
244
+ end
245
+ end
246
+
247
+ # Create a {Point} from the given {REXML::Element} using the passed attribute names
248
+ # @param [REXML::Element] element The {REXML::Element} to parse
249
+ # @param [String] x_name The name of the attribute containing the X coordinate
250
+ # @param [String] y_name The name of the attribute containing the Y coordinate
251
+ def self.point_from(element, x_name='x', y_name='y')
252
+ Point[element.attributes[x_name].to_f, element.attributes[y_name].to_f]
253
+ end
254
+
255
+ def self.format(value)
256
+ "%g" % value
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,40 @@
1
+ require 'rexml/document'
2
+
3
+ module EagleCAD
4
+ class Layer
5
+ attr_accessor :active, :color, :fill, :name, :number, :visible
6
+
7
+ def self.from_xml(element)
8
+ self.new(element.attributes['name'], element.attributes['number'], element.attributes['color'], element.attributes['fill']).tap do |layer|
9
+ element.attributes.each do |name, value|
10
+ case name
11
+ when 'active' then layer.active = ('no' != value)
12
+ when 'visible' then layer.visible = ('no' != value)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(name, number, color, fill)
19
+ @active = true
20
+ @color = color
21
+ @fill = fill
22
+ @name = name
23
+ @number = number
24
+ @visible = true
25
+ end
26
+
27
+ # @return [REXML::Element]
28
+ def to_xml
29
+ element = REXML::Element.new 'layer'
30
+ element.add_attributes({'number' => number,
31
+ 'name' => name,
32
+ 'color' => color,
33
+ 'fill' => fill,
34
+ })
35
+ element.add_attribute('active', active ? 'yes' : 'no')
36
+ element.add_attribute('visible', visible ? 'yes' : 'no')
37
+ element
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,73 @@
1
+ require 'rexml/document'
2
+
3
+ require_relative 'deviceset'
4
+ require_relative 'package'
5
+ require_relative 'symbol'
6
+
7
+ module EagleCAD
8
+ class Library
9
+ attr_accessor :description, :name, :packages, :symbols, :device_sets
10
+
11
+ # Create a new {Library} from an {REXML::Element}
12
+ # @param [REXML::Element] element The {REXML::Element} to parse
13
+ def self.from_xml(element)
14
+ Library.new(name:element.attributes['name']).tap do |library|
15
+ library.description = element.elements['description']
16
+
17
+ element.elements.each do |element|
18
+ case element.name
19
+ when 'devicesets'
20
+ element.elements.each {|symbol| library.push DeviceSet.from_xml(symbol) }
21
+ when 'packages'
22
+ element.elements.each {|package| library.push Package.from_xml(package) }
23
+ when 'symbols'
24
+ element.elements.each {|symbol| library.push Symbol.from_xml(symbol) }
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def initialize(options={})
31
+ options.each {|k,v| send("#{k}=", v) }
32
+
33
+ @device_sets = []
34
+ @packages = {}
35
+ @symbols = []
36
+ end
37
+
38
+ def push(arg)
39
+ case arg
40
+ when DeviceSet
41
+ @device_sets.push arg
42
+ when Package
43
+ @packages[arg.name] = arg
44
+ when Symbol
45
+ @symbols.push arg
46
+ end
47
+ end
48
+
49
+ # Generate XML for the {Library} element
50
+ # @return [REXML::Element]
51
+ def to_xml
52
+ REXML::Element.new('library').tap do |element|
53
+ element.add_attribute 'name', name
54
+
55
+ # Packages must be output before devicesets or Eagle will fail to load the file
56
+ element.add_element('packages').tap do |packages_element|
57
+ packages.each {|name, package| packages_element.add_element package.to_xml }
58
+ end
59
+
60
+ # Symbols must be output before devicessets or Eagle will fail to load the file
61
+ element.add_element('symbols').tap do |symbols_element|
62
+ symbols.each {|symbol| symbols_element.add_element symbol.to_xml }
63
+ end
64
+
65
+ if device_sets and device_sets.count
66
+ element.add_element('devicesets').tap do |devicesets_element|
67
+ device_sets.each {|deviceset| devicesets_element.add_element deviceset.to_xml }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end