eaglecad 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.
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