Last active
September 2, 2023 13:10
-
-
Save eflukx/0c202fe245f27f34ed2e8718fbf8293c to your computer and use it in GitHub Desktop.
Small script to parse the .xml BOM files exported by KiCad. This allows for easy handling and transforming in a (production) pipeline using Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'pry' | |
require 'json' | |
require 'nokogiri' | |
require 'yaml' | |
require 'hashie' | |
class GroupedComponents | |
@@DEFAULT_FIELDS = %w(refs cnt value footprint) | |
def self.csv_header *properties, separator: ',' | |
line = @@DEFAULT_FIELDS + properties | |
separator ? line.join(separator) : line | |
end | |
attr_reader :refs, :cnt, :value, :footprint, :properties | |
def self.from_components components, group_by: [:ref_des, :footprint, :value] | |
grouped = components.group_by { |c| group_by.map { |gb| c.send(gb) } } | |
grouped.values.map do |group| | |
new(group) | |
end | |
end | |
def initialize group | |
@refs = group.map(&:ref) | |
ref_component = group.first # use the first component in the group tol fill all other fields/properties | |
@value = ref_component.value | |
@footprint = ref_component.footprint | |
@properties = ref_component.properties | |
end | |
def cnt | |
@refs.count | |
end | |
def csv_line *fields, separator: ',' | |
line = default_field_values + (properties.fetch_values(*fields) { '' }) | |
separator ? line.join(separator) : line | |
end | |
def default_field_values | |
@@DEFAULT_FIELDS.map do |fieldname| | |
if fieldname == 'refs' | |
send(fieldname).join(' ') | |
else | |
send(fieldname) rescue '' | |
end | |
end | |
end | |
end | |
class Component | |
@@DEFAULT_FIELDS = %w(ref ref_des ref_num value footprint) | |
def self.from_xml xml_component | |
new ComponentHelper.component_to_hash(xml_component) | |
end | |
def self.csv_header *properties, separator: ',' | |
line = @@DEFAULT_FIELDS + properties | |
separator ? line.join(separator) : line | |
end | |
def method_missing(symbol, *args) | |
@properties.send(symbol, *args) || super | |
end | |
attr_reader :ref, :ref_des, :ref_num, :value, :footprint, :properties | |
def initialize h | |
@value, @footprint = h.fetch_values :value, :footprint | |
@ref, @ref_des, @ref_num = h.fetch(:ref, '').match(/(\D*)(\d*)/).to_a | |
@properties = Hashie::Mash.new h.fetch(:properties, {}) | |
end | |
def csv_line *fields, separator: ',' | |
line = default_field_values + (properties.fetch_values(*fields) { '' }) | |
separator ? line.join(separator) : line | |
end | |
def default_field_values | |
@@DEFAULT_FIELDS.map { |fieldname| send(fieldname) rescue '' } | |
end | |
end | |
module ComponentHelper | |
extend self | |
def get_components doc | |
get_xml_components(doc).map { |xc| Component.from_xml xc } | |
end | |
def get_components_as_hash doc | |
components = get_xml_components doc | |
components.map { |c| component_to_hash c } | |
end | |
def get_xml_components doc | |
doc.at('components').children.reject { |c| c.is_a? Nokogiri::XML::Text } | |
end | |
def component_to_hash comp | |
reference = comp.attribute('ref').value | |
value = comp.xpath('value').text | |
footprint = comp.xpath('footprint').text | |
{ ref: reference, value: value, footprint: footprint, properties: self.component_properties(comp) } | |
end | |
def component_properties c | |
c.xpath('property').map(&:values).to_h.reject { |_k, v| v.empty? } | |
end | |
end | |
file = ARGV[0] | |
doc = Nokogiri::XML(File.open(file)) do |config| | |
config.options = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NOBLANKS | |
end | |
components = ComponentHelper.get_components(doc) | |
property_names = components.inject(Set.new) { |s, c| s.merge c.properties.keys } | |
comp_hash = ComponentHelper.get_components_as_hash(doc) | |
binding.pry | |
def collapsed cpnts | |
refs = cpnts.inject([]) { |refs, c| refs << c.ref } | |
end | |
# puts comp_hash.to_yaml | |
# puts JSON.pretty_generate comp_hash | |
fields = ['Manuf', 'PartNr', 'LCSC Part #', 'Note', 'PartComnt'] | |
# puts Component.csv_header *fields | |
# puts components.reject { |c| c.properties.Place == "NO" }.map { |c| c.csv_line *fields } | |
grouped = GroupedComponents.from_components components.reject { |c| c.properties.Place == "NO" } | |
puts GroupedComponents.csv_header *fields | |
puts grouped.map { |grouped| grouped.csv_line *fields } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment