kicad 0.8.1 → 0.9.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 679cc2e86c98081d38458ec0503b64fc176dec709a412bcc8fcee6ff1ccc9fbc
4
- data.tar.gz: 5c4c4d2e0fa767c8c9c841f3235f7124c6f81b4aef66841e24c0e652b17dad52
3
+ metadata.gz: 2a91d99c4c831dbc121717ef0609e05c8fe07c20d1b2044bfddda163c75c6620
4
+ data.tar.gz: 9110254e21b4e2913160100ec75f2887ea327107a0fefb257a23e77a9dfba362
5
5
  SHA512:
6
- metadata.gz: ae34e7b3091ed815b836ca0f5f3f9f73ba5c622411279122f436949bb1a9e372c5d440795ba1f494cec5f5033a51f65578405c574ff70591a3db42640d5d7011
7
- data.tar.gz: 8cf7ebfc425f27b4bd89389db45c6f3528236dd2c534b930a3102f5d06931ab383883db724d898f7eb67d518fdb5127aa384d91df74ef6fe03f59269f050d7d1
6
+ metadata.gz: 44681ff57b45bebeea66e8a0dc92932e8219354dd2545e2dd4a9b4feb614aa7d2d528aff3946875c4373ff701e95015961e7865d0cc12a527404b84d29ae4b74
7
+ data.tar.gz: f2af698d30cd4b24a700a03a937f7a7a6c9913250c63aa99b3d75828191bb7d589d6fa06acd4a342f66c7d8a585d48ed8487aa0d1059e69a70262b2ce0da54cc
data/README.md CHANGED
@@ -17,10 +17,18 @@ Parse, load, modify and rewrite Kicad (s-epression) files into a convenient tree
17
17
 
18
18
  ## Development
19
19
 
20
- After checking out the repo, run `bundle` to install dependencies. Then, run `rake spec` to run the tests.
20
+ After checking out the repo, run `bundle` to install dependencies.
21
21
 
22
22
  To install this gem onto your local machine from local source code, run `rake install`.
23
23
 
24
+ ## Resources
25
+
26
+ KiCad uses a version of the Cadence SPECCTRA Design Language, defined in https://cdn.hackaday.io/files/1666717130852064/specctra.pdf
27
+
28
+ KiCad's documentation of this is at https://dev-docs.kicad.org/en/file-formats/sexpr-intro/
29
+
30
+ A related Rust library that was not consulted while building this is https://github.com/adom-inc/kicad_lib/tree/main/kicad_sexpr
31
+
24
32
  ## Contributing
25
33
 
26
34
  Bug reports and pull requests are welcome on GitHub at https://github.com/cjheath/kicad-rb
data/lib/kicad/ast.rb CHANGED
@@ -11,136 +11,390 @@ module KiCad
11
11
  def emit depth = 0
12
12
  "\t"*depth +
13
13
  '(' +
14
- @values.map{|v| value(v) }*' ' +
14
+ @values.map{|v| String === v ? v.inspect : v.to_s }*' ' +
15
15
  (@children.size == 0 ? '' : "\n" + @children.map{|c| c.emit(depth+1) }*''+"\t"*depth) +
16
16
  ")\n"
17
17
  end
18
18
 
19
- def value(v)
20
- case v
21
- when ::Symbol
22
- v.to_s
23
- when String
24
- v.inspect
25
- when Float, Integer
26
- v.to_s
27
- else
28
- "Internal error"
19
+ # Define setter and getter methods for each value type this class allows
20
+ def self.value_types vts
21
+ i = 1 # @values[0] is always the class symbol
22
+ vts.each do |k, v|
23
+ # puts "#{self.name} attribute #{k.to_s} => #{v.inspect}"
24
+ # puts "attr_accessor #{self.name}.#{k} (values #{v.inspect}) is stored in @values[#{i}]"
25
+ begin
26
+ o = i # Avoid capturing i after the loop ends
27
+ define_method(:"#{k}") do
28
+ # puts "accessing #{self.class.name}.#{k} as @values[#{o}]"
29
+ @values[o]
30
+ end
31
+ define_method(:"#{k}=") do |v|
32
+ # puts "setting #{self.class.name}.#{k} as @values[#{o}]"
33
+ # REVISIT: Check valid data type matching v
34
+ @values[o] = v
35
+ end
36
+ end
37
+ i = i+1
38
+ end
39
+ end
40
+
41
+ def children_of_type *cts # cts is an array of AST class symbols or strings
42
+ class_names = cts.flatten.map{|k| 'KiCad::AST::'+self.class.to_class_name(k)}
43
+ @children.filter{|h| class_names.include?(h.class.name) }
44
+ end
45
+
46
+ # Define methods for each child type this class allows
47
+ def self.child_types *cts
48
+ # puts "#{self.name} allows child types #{cts.inspect}"
49
+ cts.each do |c|
50
+ if Array === c
51
+ define_method(:"all_#{c[0].to_s}") do
52
+ # puts "Looking for all [#{class_names*', '}] in #{@children.map{|q| q.class.name}.inspect}"
53
+ children_of_type(c)
54
+ end
55
+ # REVISIT: Allow deleting and adding instances to the array
56
+ # new_child = KiCad.parse('(some new node)').value
57
+ # @children.append(new_child)
58
+ else
59
+ class_name = 'KiCad::AST::'+to_class_name(c)
60
+ define_method(:"#{c}") do
61
+ # puts "Looking for first #{class_name} in #{@children.map{|q| q.class.name}.inspect}"
62
+ a = children_of_type(c)
63
+ puts "Choosing first #{self.class.name}.#{c} of #{a.size}" if a.size > 1
64
+ a.first
65
+ end
66
+ # Allow deleting this instance
67
+ define_method(:"unset_#{c.to_s}") do
68
+ child = send(:"#{c}")
69
+ @children = @children - [child] if child
70
+ child ? true : nil
71
+ end
72
+ end
29
73
  end
30
74
  end
75
+
76
+ def self.to_class_name sym
77
+ sym.to_s.gsub(/\A[a-z]|_[a-z]/) {|from| from[-1].upcase }
78
+ end
79
+
80
+ def self.to_symbol class_name
81
+ class_name.to_s.gsub(/[A-Z]/) {|from| '_'+from[-1].downcase }.sub(/\A_/,'')
82
+ end
31
83
  end
32
84
 
33
85
  class KicadSymbolLib < Node
34
- def initialize values, children
35
- super
36
- end
86
+ child_types :version, :generator, :generator_version, [:symbol]
37
87
  end
38
88
 
39
- # Uncomment or add whatever class you need to customise:
89
+ class Generator < Node
90
+ end
40
91
 
41
- class Symbol < Node
92
+ class GeneratorVersion < Node
42
93
  end
43
94
 
44
- =begin
95
+ class Version < Node
96
+ end
97
+
98
+ # Uncomment or add whatever class you need to customise:
99
+
100
+ # Position Identifier
45
101
  class At < Node
102
+ value_types :x => Float, :y => Float, :angle => Float
46
103
  end
47
104
 
48
- class Center < Node
105
+ # Coordinate Point List
106
+ class Pts < Node
107
+ value_types({})
108
+ child_types [:xy]
49
109
  end
50
110
 
51
- class Circle < Node
111
+ class Xy < Node
112
+ value_types :x => Float, :y => Float
52
113
  end
53
114
 
54
- class Effects < Node
115
+ # Stroke Definition
116
+ class Stroke < Node
117
+ child_types :width, :type, :color
55
118
  end
56
119
 
57
- class EmbeddedFonts < Node
120
+ class Width < Node
121
+ value_types :width => Float
58
122
  end
59
123
 
60
- class End < Node
124
+ class Type < Node
125
+ value_types :type => [:dash, :dash_dot, :dash_dot_dot, :dot, :default, :solid]
61
126
  end
62
127
 
63
- class ExcludeFromSim < Node
128
+ class Color < Node
129
+ value_types :r => Integer, :g => Integer, :b => Integer, :a => Integer
64
130
  end
65
131
 
66
- class Fill < Node
132
+ # Text Effects
133
+ class Effects < Node
134
+ child_types :font, :justify, :hide
67
135
  end
68
136
 
69
137
  class Font < Node
138
+ child_types :face, :size, :thickness, :bold, :italic, :line_spacing
70
139
  end
71
140
 
72
- class Generator < Node
141
+ class Face < Node
142
+ value_types :face_name => String
73
143
  end
74
144
 
75
- class GeneratorVersion < Node
145
+ class Size < Node
146
+ value_types :height => Float, :width => Float
76
147
  end
77
148
 
78
- class Hide < Node
149
+ class Thickness < Node
150
+ value_types :thickness => Float
79
151
  end
80
152
 
81
- class InBom < Node
153
+ class Bold < Node
154
+ value_types :bold => [:no, :yes]
82
155
  end
83
156
 
84
- class Length < Node
157
+ class Italic < Node
158
+ value_types :bold => [:no, :yes]
85
159
  end
86
160
 
87
- class Name < Node
161
+ class LineSpacing < Node
162
+ value_types :line_spacing => Float
88
163
  end
89
164
 
90
- class Number < Node
165
+ class Justify < Node
166
+ value_types :justify => [[:right, :left, :top, :bottom, :mirror]] # Can have multiple
91
167
  end
92
168
 
93
- class Offset < Node
169
+ class Hide < Node
170
+ value_types :hide => [:no, :yes]
94
171
  end
95
172
 
96
- class OnBoard < Node
173
+ # Page Settings
174
+ class Paper < Node
175
+ # REVISIT: Either paper_size or width/height
176
+ value_types :paper_size => [:A0, :A1, :A2, :A3, :A4, :A5, :A, :B, :C, :D, :E],
177
+ :width => Float, :height => Float,
178
+ :portrait => [:portrait]
97
179
  end
98
180
 
99
- class Pin < Node
181
+ # Title Block
182
+ class TitleBlock < Node
183
+ child_types :title, :date, :rev, :company, # All take one string as value
184
+ [:comment] # N (1..9) and String # REVISIT: Implement Comment
100
185
  end
101
186
 
102
- class PinNames < Node
187
+ # Properties
188
+ class Property < Node
189
+ value_types :key => String, :value => String
190
+ child_types :at, :effects
191
+
192
+ # Set or clear (hide) on the property_node
193
+ def hide=(h = true)
194
+ v = (h ? :yes : :no)
195
+ if !effects
196
+ # puts "No effects yet"
197
+ prop = KiCad.parse(%Q{(effects(hide #{v}))})&.value
198
+ @children.append(prop) if prop
199
+ elsif (existing = effects.hide)
200
+ # puts "Effects and hide already"
201
+ existing.hide = v
202
+ else
203
+ # Create new (hide) node:
204
+ # puts "Effects but no hide"
205
+ prop = KiCad.parse(%Q{(hide #{v})})&.value
206
+ @children.append(prop) if prop
207
+ end
208
+ end
103
209
  end
104
210
 
105
- class PinNumbers < Node
211
+ # Universally Unique Identifier
212
+ class Uuid < Node
213
+ value_types :uuid => String
106
214
  end
107
215
 
108
- class Polyline < Node
216
+ # Images
217
+ class Image < Node
218
+ child_types :at, :scale, :layer, :uuid, :data
109
219
  end
110
220
 
111
- class Property < Node
221
+ class Data < Node
222
+ value_types :data => String # REVISIT: Base64 data - not encoded as a string I think?
112
223
  end
113
224
 
114
- class Pts < Node
225
+ class Circle < Node
226
+ child_types :center, :radius, :stroke, :fill
227
+ end
228
+
229
+ class Center < Xy
230
+ # value_types :x => Float, :y => Float
115
231
  end
116
232
 
117
233
  class Radius < Node
234
+ value_types :radius => Float
235
+ end
236
+
237
+ class Fill < Node
238
+ value_types :fill => [:no, :yes]
239
+ end
240
+
241
+ class Arc < Node
242
+ child_types :start, :mid, :end, :stroke, :fill
243
+ end
244
+
245
+ class Start < Xy
246
+ # value_types :x => Float, :y => Float
247
+ end
248
+
249
+ class Mid < Xy
250
+ # value_types :x => Float, :y => Float
251
+ end
252
+
253
+ class End < Xy
254
+ # value_types :x => Float, :y => Float
118
255
  end
119
256
 
120
257
  class Rectangle < Node
258
+ child_types :start, :end, :stroke, :fill
121
259
  end
122
260
 
123
- class Size < Node
261
+ class Polyline < Node
262
+ child_types :pts, :stroke, :fill
124
263
  end
125
264
 
126
- class Start < Node
265
+ class Text < Node
266
+ value_types :text => String
267
+ child_types :at, :effects
127
268
  end
128
269
 
129
- class Stroke < Node
270
+ class Offset < Xy
271
+ # value_types :x => Float, :y => Float
130
272
  end
131
273
 
132
- class Type < Node
274
+ # Symbol Pins
275
+ class Pin < Node
276
+ value_types :electrical_type => [
277
+ :input, # Pin is an input.
278
+ :output, # Pin is an output.
279
+ :bidirectional, # Pin can be both input and output.
280
+ :tri_state, # Pin is a tri-state output.
281
+ :passive, # Pin is electrically passive.
282
+ :free, # Not internally connected.
283
+ :unspecified, # Pin does not have a specified electrical type.
284
+ :power_in, # Pin is a power input.
285
+ :power_out, # Pin is a power output.
286
+ :open_collector, # Pin is an open collector output.
287
+ :open_emitter, # Pin is an open emitter output.
288
+ :no_connect, # Pin has no electrical connection.
289
+ ], :graphic_style => [
290
+ :line,
291
+ :inverted,
292
+ :clock,
293
+ :inverted_clock,
294
+ :input_low,
295
+ :clock_low,
296
+ :output_low,
297
+ :edge_clock_high,
298
+ :non_logic
299
+ ]
300
+ child_types :length, :name, :number
133
301
  end
134
302
 
135
- class Version < Node
303
+ class Length < Node
304
+ value_types :length => Float
136
305
  end
137
306
 
138
- class Width < Node
307
+ class Name < Node
308
+ value_types :name => String
309
+ child_types :effects
139
310
  end
140
311
 
141
- class Xy < Node
312
+ class Number < Node
313
+ value_types :number => String
314
+ child_types :effects
315
+ end
316
+
317
+ class Polygon < Node
318
+ child_types :pts, :stroke, :fill
319
+ end
320
+
321
+ # Symbols
322
+ class Symbol < Node
323
+ value_types :id => String
324
+ child_types :extends, :exclude_from_sym, :pin_numbers, :pin_names, :in_bom, :on_board,
325
+ [:property],
326
+ [:shape, :circle, :rectangle, :text, :polyline, :arc], # REVISIT: Lots more types of graphic items here...
327
+ [:pin],
328
+ [:symbol], # Child symbols (units) embedded in a parent
329
+ :unit_name,
330
+ :embedded_fonts
331
+
332
+ # Get or set Property values by key, or as a Hash
333
+ def property_node k
334
+ self.all_property.detect{|p| p.key == k}
335
+ end
336
+
337
+ def property k = nil
338
+ if k
339
+ property_node(k)&.value
340
+ else # Return all properties as a Hash
341
+ Hash[*self.all_property.map{|p| [p.key, p.value] }.flatten]
342
+ end
343
+ end
344
+
345
+ def [](k)
346
+ property[k]
347
+ end
348
+
349
+ def []=(k, v)
350
+ puts "Setting property #{k} to #{v.inspect}"
351
+ if (p = property_node)
352
+ p.send(:"#{self.class.to_symbol k}=", v)
353
+ else # Create new Property using the parser:
354
+ prop = KiCad.parse(%Q{
355
+ (property "#{k}" #{String === v ? v.inspect : v.to_s}
356
+ (at 0 0 0)
357
+ (effects(font(size 1.27 1.27)))
358
+ (hide yes)
359
+ )
360
+ })&.value
361
+ @children.append(prop) if prop
362
+ end
363
+ end
364
+ end
365
+
366
+ class Extends < Node
367
+ value_types :library_id => String
368
+ end
369
+
370
+ class PinNumbers < Node
371
+ value_types :hide => [:no, :yes]
372
+ end
373
+
374
+ class PinNames < Node
375
+ child_types :offset
376
+ value_types :hide => [:no, :yes]
377
+ end
378
+
379
+ class InBom < Node
380
+ value_types :in_bom => [:no, :yes]
381
+ end
382
+
383
+ class OnBoard < Node
384
+ value_types :on_board => [:no, :yes]
385
+ end
386
+
387
+ class UnitName < Node
388
+ value_types :name => String
389
+ end
390
+
391
+ class EmbeddedFonts < Node
392
+ value_types :embedded_fonts => [:no, :yes]
393
+ end
394
+
395
+ class ExcludeFromSim < Node
396
+ value_types :exclude_from_sim => [:no, :yes]
142
397
  end
143
- =end
144
398
 
145
399
  end
146
400
  end
data/lib/kicad/grammar.rb CHANGED
@@ -33,19 +33,23 @@ module KiCad
33
33
 
34
34
  module Node2
35
35
  def s1
36
- elements[1]
36
+ elements[0]
37
37
  end
38
38
 
39
- def values
39
+ def s2
40
40
  elements[2]
41
41
  end
42
42
 
43
- def nodes
43
+ def values
44
44
  elements[3]
45
45
  end
46
46
 
47
- def s2
48
- elements[5]
47
+ def nodes
48
+ elements[4]
49
+ end
50
+
51
+ def s3
52
+ elements[6]
49
53
  end
50
54
  end
51
55
 
@@ -74,84 +78,88 @@ module KiCad
74
78
  end
75
79
 
76
80
  i0, s0 = index, []
77
- if (match_len = has_terminal?('(', false, index))
78
- r1 = true
79
- @index += match_len
80
- else
81
- terminal_parse_failure('\'(\'')
82
- r1 = nil
83
- end
81
+ r1 = _nt_s
84
82
  s0 << r1
85
83
  if r1
86
- r2 = _nt_s
84
+ if (match_len = has_terminal?('(', false, index))
85
+ r2 = true
86
+ @index += match_len
87
+ else
88
+ terminal_parse_failure('\'(\'')
89
+ r2 = nil
90
+ end
87
91
  s0 << r2
88
92
  if r2
89
- s3, i3 = [], index
90
- loop do
91
- i4, s4 = index, []
92
- r5 = _nt_value
93
- s4 << r5
94
- if r5
95
- r6 = _nt_s
96
- s4 << r6
97
- end
98
- if s4.last
99
- r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
100
- r4.extend(Node0)
101
- else
102
- @index = i4
103
- r4 = nil
104
- end
105
- if r4
106
- s3 << r4
107
- else
108
- break
109
- end
110
- end
111
- if s3.empty?
112
- @index = i3
113
- r3 = nil
114
- else
115
- r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
116
- end
93
+ r3 = _nt_s
117
94
  s0 << r3
118
95
  if r3
119
- s7, i7 = [], index
96
+ s4, i4 = [], index
120
97
  loop do
121
- i8, s8 = index, []
122
- r9 = _nt_node
123
- s8 << r9
124
- if r9
125
- r10 = _nt_s
126
- s8 << r10
98
+ i5, s5 = index, []
99
+ r6 = _nt_value
100
+ s5 << r6
101
+ if r6
102
+ r7 = _nt_s
103
+ s5 << r7
127
104
  end
128
- if s8.last
129
- r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
130
- r8.extend(Node1)
105
+ if s5.last
106
+ r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
107
+ r5.extend(Node0)
131
108
  else
132
- @index = i8
133
- r8 = nil
109
+ @index = i5
110
+ r5 = nil
134
111
  end
135
- if r8
136
- s7 << r8
112
+ if r5
113
+ s4 << r5
137
114
  else
138
115
  break
139
116
  end
140
117
  end
141
- r7 = instantiate_node(SyntaxNode,input, i7...index, s7)
142
- s0 << r7
143
- if r7
144
- if (match_len = has_terminal?(')', false, index))
145
- r11 = true
146
- @index += match_len
147
- else
148
- terminal_parse_failure('\')\'')
149
- r11 = nil
118
+ if s4.empty?
119
+ @index = i4
120
+ r4 = nil
121
+ else
122
+ r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
123
+ end
124
+ s0 << r4
125
+ if r4
126
+ s8, i8 = [], index
127
+ loop do
128
+ i9, s9 = index, []
129
+ r10 = _nt_node
130
+ s9 << r10
131
+ if r10
132
+ r11 = _nt_s
133
+ s9 << r11
134
+ end
135
+ if s9.last
136
+ r9 = instantiate_node(SyntaxNode,input, i9...index, s9)
137
+ r9.extend(Node1)
138
+ else
139
+ @index = i9
140
+ r9 = nil
141
+ end
142
+ if r9
143
+ s8 << r9
144
+ else
145
+ break
146
+ end
150
147
  end
151
- s0 << r11
152
- if r11
153
- r12 = _nt_s
148
+ r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
149
+ s0 << r8
150
+ if r8
151
+ if (match_len = has_terminal?(')', false, index))
152
+ r12 = true
153
+ @index += match_len
154
+ else
155
+ terminal_parse_failure('\')\'')
156
+ r12 = nil
157
+ end
154
158
  s0 << r12
159
+ if r12
160
+ r13 = _nt_s
161
+ s0 << r13
162
+ end
155
163
  end
156
164
  end
157
165
  end
data/lib/kicad/grammar.tt CHANGED
@@ -3,7 +3,7 @@ require 'kicad/ast'
3
3
  module KiCad
4
4
  grammar SExpr
5
5
  rule node
6
- '(' s values:( value s)+ nodes:(node s)* ')' s
6
+ s '(' s values:( value s)+ nodes:(node s)* ')' s
7
7
  { def value
8
8
  klass_name = values.elements[0].value.value
9
9
  klass = KiCad::AST::Node
data/lib/kicad/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module KiCad
2
- VERSION = "0.8.1"
2
+ VERSION = "0.9.3"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kicad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clifford Heath
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2025-05-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  - !ruby/object:Gem::Version
115
115
  version: '0'
116
116
  requirements: []
117
- rubygems_version: 3.6.8
117
+ rubygems_version: 3.6.2
118
118
  specification_version: 4
119
119
  summary: Load and rewrite Kicad s-expression files into a tree structure for scripting
120
120
  test_files: []