# encoding: utf-8 # ruby: 2.1.0 =begin Rakefile to manage hardware projects uses Lepton EDA for schematic and pcb-rnd for board layouts. Rakefile instead of Makefile for better text file parsing capabilities. =end require 'rake/clean' require 'csv' # to export BOM and costs # ================= # project variables # ================= # common name used for file names name = "usb_hub" # additional schematic sheets sheets = ["usb_hub-dfp1", "usb_hub-dfp2", "usb_hub-dfp3", "usb_hub-dfp4", "usb_hub-dfp5", "usb_hub-dfp6", "usb_hub-dfp7"] # project version, read from "version" file raise "define project version in 'version' file" unless File.exist? "version" version = IO.read("version").split("\n")[0] # current date for stamping output date = Time.now.strftime("%Y-%m-%d") # revision based on number of changes on schematic or board layout and current git commit changes = `git log --pretty=oneline "#{name}.sch" "#{name}.lht" | wc -l`.chomp.to_i commit = `git rev-parse --short HEAD`.chomp revision = "#{changes} (#{commit})" # path to qeda" qeda = "qeda" # ========== # main tasks # ========== desc "main building task" task :default => [:print, :fabrication, :bom, :pnp] desc "print schematic and layout (as pdf)" prints = [ "#{name}.sch.pdf", "#{name}.brd.pdf", "#{name}.brd-top.svg", "#{name}.brd-bottom.svg" ] unless sheets.empty? then prints += sheets.collect{|sheet| "#{sheet}.sch.pdf"} prints << "#{name}.sch-all.pdf" end task :print => prints CLEAN.include([ "#{name}.versioned.sch", "#{name}.versioned.lht" ] + sheets.collect{|sheet| "#{sheet}.versioned.sch"}) CLOBBER.include(prints) desc "generate fabrication gerbers (as archive)" gerbers = [ "#{name}.brd.asb", "#{name}.brd.ast", "#{name}.brd.gbl", "#{name}.brd.gbo", "#{name}.brd.gbp", "#{name}.brd.gbs", "#{name}.brd.gko", "#{name}.brd.gtl", "#{name}.brd.gto", "#{name}.brd.gtp", "#{name}.brd.gts", "#{name}.brd.xln", "#{name}.brd.g2l", "#{name}.brd.g3l" ] fab = [ "#{name}.brd.zip" ] task :fabrication => fab CLEAN.include(gerbers) CLOBBER.include(fab) desc "generate symbols and footprints from parts" task :library do sh "#{qeda} config output geda" sh "#{qeda} generate ." sh "#{qeda} config output coraleda" sh "#{qeda} generate ." end desc "export BOMs from schematic" boms = [ "#{name}.bom.csv" ] task :bom => boms CLOBBER.include(boms) desc "export PnP placement" pnps = [ "#{name}.cpl.csv" ] task :pnp => pnps CLOBBER.include(pnps) # =============== # file generation # =============== desc "generate schematic with version information all symbols embedded" rule ".versioned.sch" => ".sch" do |t| sh "cp #{t.source} #{t.name}" sh "lepton-embed --embed #{t.name} 2> /dev/null" sh "sed --in-place 's/\\$version\\$/#{version}/' #{t.name}" sh "sed --in-place 's/\\$date\\$/#{date}/' #{t.name}" sh "sed --in-place 's/\\$revision\\$/#{revision}/' #{t.name}" end desc "generate board layout with version information" rule ".versioned.lht" => ".lht" do |t| sh "cp #{t.source} #{t.name}" sh "sed --in-place 's/\\$version\\$/#{version}/' #{t.name}" sh "sed --in-place 's/\\$date\\$/#{date}/' #{t.name}" sh "sed --in-place 's/\\$revision\\$/#{revision}/' #{t.name}" end desc "generate printable version (PDF) of schematic" rule ".sch.pdf" => ".versioned.sch" do |t| sh "lepton-cli export --color --paper=iso_a4 --layout=landscape --output=#{t.name} #{t.source} 2> /dev/null" end desc "generate grouped printable version (PDF) of schematic" rule ".sch-all.pdf" => ["#{name}.sch.pdf"] + sheets.collect{|sheet| "#{sheet}.sch.pdf"} do |t| sh "mutool merge -o #{t.name} " + t.prerequisites * " " end desc "generate printable version (PostScript) of board layout" rule ".brd.ps" => ".versioned.lht" do |t| sh "pcb-rnd -x ps --ps-color --media A4 --psfile #{t.name} #{t.source} 2> /dev/null" end desc "generate printable version (PDF) of board layout" rule ".brd.pdf" => ".brd.ps" do |t| sh "ps2pdf -sPAPERSIZE=a4 -dEPSCrop #{t.source} #{t.name}" end desc "generate photo realistic picture from layout (top side)" rule ".brd-top.svg" => ".versioned.lht" do |t| sh "pcb-rnd -x svg --photo-mode --outfile #{t.name} #{t.source} 2> /dev/null" end desc "generate photo realistic picture from layout (bottom side)" rule ".brd-bottom.svg" => ".versioned.lht" do |t| sh "pcb-rnd -x svg --photo-mode --flip --outfile #{t.name} #{t.source} 2> /dev/null" end desc "archive gerbers" rule ".brd.zip" => ".versioned.lht" do |t| base = File.basename(t.source, ".versioned.lht") puts base sh "pcb-rnd -x cam gerber:JLC_PCB --outfile #{base}.brd #{t.source} 2> /dev/null" sh "zip --quiet #{t.name} #{base}.brd.xln #{base}.brd.a* #{base}.brd.g*" end desc "generate BOM file from schematic" rule ".bom.csv" => ".sch" do |t| attributes = ["device", "value", "description", "footprint", "manufacturer", "mpn", "datasheet", "lcsc", "digikey"] bom_data = bom2(t.prerequisites[0], attributes) CSV.open(t.name, "wb") do |csv| all_attributes = ["refdes","qty"] + attributes csv << all_attributes bom_data.each do |line| csv << all_attributes.collect{|attribute| line[attribute]} end end end desc "generate pick-and-place file from board" rule ".cpl.csv" => [".versioned.lht", "mass_prop.sh", "pnp_fab.tab"] do |t| sh "./mass_prop.sh #{t.prerequisites[0]} pnp_fab.tab" # add fab placement offsets sh "pcb-rnd -x XY --xyfile #{t.name} --xy-unit mm --format 'JLCPCB' --vendor jlcpcb #{t.prerequisites[0]}" # export XY file in JLCPCB format end # ================ # helper functions # ================ # generate gnetlist bom2 and parse them # arguments: schematic=schematic to use, attributes=attributes to use for generating bom2 # returns an array of hash. key is the attribute name, value is the attribute value def bom2(schematic, attributes) to_return = [] # force attributes to be an array attributes = case attributes when String [attributes] when Array attributes else [attributes.to_s] end # generate bom2 list = `lepton-netlist --backend bom2 --backend-option attribs=#{attributes*','} --quiet --output - #{schematic} 2> /dev/null` list = list.each_line {|l| '"' + l + '"' + '\n' } list.gsub!(/^(.+)/, '"\1') list.gsub!(/(.+)$/, '\1"') list.gsub!(/(?!http):(?!\/\/)/, '\1":"\2') # protect the values between ':' (such as URLs) # parse bom2 csv = CSV.parse(list, col_sep: ":", quote_char: '"') if csv.empty? then $stderr.puts "no parts found for BOM" return [] end csv[1..-1].each do |row| line = {} row.each_index do |col| line[csv[0][col]] = row[col] unless row[col] == "unknown" end to_return << line end return to_return end