diff --git a/old-boom/CHARACTERISTICS b/old-boom/CHARACTERISTICS new file mode 100644 index 0000000..7eab6ab --- /dev/null +++ b/old-boom/CHARACTERISTICS @@ -0,0 +1,140 @@ +*** This is an older draft of the concept - differs sometimes from the way + things are done in gta02-core. *** + + +BOM matching +============ + +BOMs are matched with inventories in the following way: + +- a .lst file with the BOM is generated by KiCad + +- using a ruleset, component characteristics are translated to a + canonical format and default values may be used for unspecified + characteristics + +- part catalogs are searched for matches with the canonical component + descriptions. This yields a list of supplier-specific part numbers + for each component. + + Parts can be characterized by either specifying their properties or + by equating them to another part. E.g., a Digi-Key part may be + defined as an NXP part which in turn is equivalent to a TI part. + +- this list is then matched against inventories, using a suitable + optimization strategy (e.g., prioritize inventories and try to + pick as many suitable components as possible higher priority ones + before moving to lower priority ones) + + E.g., local stock could be the first-level inventory, followed by + more distant warehouses, followed by distributors, followed by + manufacturers. + + Inventories could also include pricing information. + +- TBD: it would be good if parameters gathered in the matching process + could be fed back into KiCad (as some sort of annotations, similar + to the expanded view of schematic symbols), such that under-specifed + parts yielding mismatches can be spotted by manual review. + + +Catalog +======= + +A catalog contains part characteristics and the reference number(s) +assigned to them. + + +Basic syntax +------------ + +Catalog entries consist of "words" in the sense that each word does +not contain any whitespace and words are separated from each other by +whitespace. Whitespace can be included in a word if it is enclosed in +double quotes. + +Each entry begins in the first column of a line. If an entry needs +more than one line, the words on the continuation line(s) must be +indented by whitespace. + +Trailing whitespace is ignored, and so are comments beginning with a +hash mark. Blank lines end any entry and are also ignored. + +Each catalog entry begins with the part number followed by a part type +designator. + +Characteristics have the form =, where +the fields follow the pattern outlined below. The value is some +description of the value of that characteristic, typically a number +and a unit (e.g., 4.7uF) or a name (e.g., X5R). + +Numbers use a decimal point where necessary. Mantissas are normalized +such that they fall into the range 1 <= n < 1000. E.g., instead of +0.1uF, write 100nF. There is no space between number and unit. The +Omega of Ohm is written as "R". + + +Fields +------ + +Each + +General fields +- - - - - - - + +FP Footprint +H Height (overrides any height implied by footprint) +TOL Tolerance, with percent sign. Split tolerances are indicated as n/m% +DSC Free-format description + + +Resistors +- - - - - + +RES Part type designator +R Resistance, with unit +P Maximum power dissipation +V Maximum volatage + + +Capacitors +- - - - - + +CAP Part type designator +C Capacitance, with unit +M Material, e.g., TANT, NP0, X5R, etc. +V Maximum voltage +ESR ESR, with unit + + +Inductors +-- - - - + +Diodes +- - - + +DIODE Regular diode +STKY Schottky diode + +Vf Maximum forward voltage +Vr Maximum reverse voltage +If Maximum forward current +Ir Maximum reverse current +C Capacitance + +LED Ligh-emitting diode + +COL Color, multiple colors are separated by /, e.g., blue/red +ARRAY If multiple diodes form an array, this parameter describes + its structure: CA = common anode, CC = common cathode, + SEQ = tap-A-C-tap-A-C-tap sequence + +ZENER Zener diode + +Vz Zener voltage + +TVS Transient voltage suppressor + +Vac Working voltage, AC +Vdc Working voltage, DC +E Energy diff --git a/old-boom/Makefile b/old-boom/Makefile new file mode 100644 index 0000000..45afe28 --- /dev/null +++ b/old-boom/Makefile @@ -0,0 +1,19 @@ +UPLOAD=werner@sita.openmoko.org:public_html/gta02-core/ +COPY=rsync -e ssh --progress + +.PHONY: all xpdf spotless upload + +all: + @echo "make what ? xpdf, upload, or spotless ?" 1>&2 + +workflow.pdf: workflow.fig + fig2dev -L pdf $< >$@ || { rm -f $@; exit 1; } + +xpdf: workflow.pdf + xpdf workflow.pdf + +upload: workflow.pdf + $(COPY) workflow.pdf $(UPLOAD)/bom-workflow.pdf + +spotless: + rm -f workflow.pdf diff --git a/old-boom/README b/old-boom/README new file mode 100644 index 0000000..df91b4c --- /dev/null +++ b/old-boom/README @@ -0,0 +1,337 @@ +The BOM processing system +========================= + +The BOM processing system takes a bill of material generated by +KiCad and converts it in various steps into a "shopping list" +that can be used to order from various providers. + + +Introduction +============ + +The following sections describe how to use the basic elements of +the BOM processing system. + + +A simple BOM translation +------------------------ + +KiCad identifies components by a so-called component reference, +e.g., R1001, U5, etc. In addition to this, each component can have +various parameters, such as a "value", its footprint, and further +user-defined items. These parameters can be shown in the schematics +(e.g., the value usually is) or they can be hidden (e.g., the +footprint). + +At the end of the process, we want a "shopping list" that can be +used to order items or to find them in an inventory or catalog. +Components in the shopping list are identified by a part number. + +... +- BOM +- inventory +- ID matching + + +Equivalences +------------ + +A single component can be associated with multiple part numbers. +For example, a chip its manufacturer calls "XYZ-R1" may be listed in +a distributor's catalog with a completely different order number, +such as "20-1234-8". The BOM processing system therefore +distinguishes multiple so-called name spaces. A name space is +identified by a (unique) name and a part number is generally +qualified by the name of the name space. + +E.g., if the manufacturer is called "ACME" and the distributor of +electronical components calls itself "DIST-EL", the part in our +example may have the equivalent names "ACME XYZ-R1" and "DIST-EL +20-1234-8". + +... +- revise .inv + +example.equ: + +#INV +DIST-EL 20-1234-8 +#EQU +ACME XYZ-R1 DIST-EL 20-1234-8 + + +Adding stock and cost +--------------------- + +- .inv, more fields +- quanta + +Substituting component names +---------------------------- + +- intro to .sub +- ad-hoc fixes + + +Selecting characteristics +------------------------- + +- .sub +- .chr +- syntax +... + + +Generating characteristics +-------------------------- + +- .gen + + +Advanced topics +=============== + +- generating .inv files +- different presentations (e.g., CT, TR, ...) +- component substitution (one-way equivalence) +- problem reports +- hiding known problems (while sourcing) + + +File formats +============ + +The BOM processing system uses a large number of different files to +store information retrieved from the BOM, inventories, intermediate +results, etc. The following sections describe the various formats. + + +Part characteristics (.chr) +--------------------------- + +A part characteristics file lists the parameters of components. +This information is then matched with the parameters specified in +the schematics. + +The part characteristics file begins with a line containing only +#CHR + +After this, each line contains the manufacturer (namespace), the +part number, and a list of parameter=value entries. Fields are +separated by spaces. + +Long lines can be wrapped by indenting the continuation lines. + +Blank lines and comments (#) are ignored. + + +Substitutions (.sub) +-------------------- + +A substitutions file specifies rules for translating component +parameters in schematics to part characteristics. + +A substitution rule consists of zero or more conditions and zero or +more assignments. The conditions are of the form field=pattern. The +field can be a per-component fields KiCad provides or any parameter +set by substitutions. + +KiCad fields are named as follows: + +KiCad field Field name +----------- ---------- +Reference REF (*) +Value VAL +Footprint FP +Field1 F1 +... ... + +(*) As a shortcut, REF= can be omitted. + +Note that fields with a user-defined name currently still only appear +as F1, F2, etc. + +The special field name FN can be used to look for a match in all of +F1, F2, ... This way, it's sufficient to use a consistent syntax for +additional parameters, without having to assign also a fixed location +for them. If more than one field matches, the first match is taken. + +Field names are case-insensitive. + +The pattern is uses a notation similar to filename globbing. There +are the following special constructs: + +- * matches a string of any length +- ? matches a single character +- (...) matches the pattern between the parentheses and records the + string matched +- $X marks a value in nXn notation, e.g., 4u7 or 100R. Such values + are converted to SI-like notation. + +A rule is applied when all conditions are fulfilled. In this case, +assignments of the form field=value are executed. Strings obtained +in the match can be included in a value as follows: + +- $field and ${field} are replaced by the respective field +- $field:n and ${field:n} are replaced by the n-th (...) pattern in + the match of the respective field + +If a rule ends with an exclamation mark, the substitution process stops +after the rule is applied. Otherwise, further rules are processed. + +Examples: + +R* val=$R -> R=$val + +This rule translates the values of all resistors to SI notation. + +D* FN=(*)Vdc -> T=TSV Vdc=FN:1 + +This rule sets the parameters T and Vdc for Zeners acting as TSVs. + +If a set of rules has a common set of conditions or assignments, the +more compact block notation can be used instead of repeating them for +each rule: + +common-conditions -> common-assignments { + rule-specific-conditions -> rule-specific-assignments + ... +} + +Rules in a block only match if both the common and the rule-specific +conditions are met. Then the common and the rule-specific assignments +are performed. If a condition or an assignment appears both in the +common and the rule-specific part, only the latter is used. + +Long lines can be wrapped by indenting the continuation lines. Note +that { and ! are also considered to be part of the same line as the +rest of the rule. In particular, the following construct wouldn't +work: + +X=Y +{ + ... +} + +With proper indentation, this would: + +X=Y + { + ... +} + + +Characteristics generation (.gen) +--------------------------------- + +The substitution mechanism can also be used to automatically generate +characteristics from part numbers, e.g., for resistors or capacitors. + +.gen files are exactly .sub files, with the exception that the only +field used is the REF field and that it contains the part number. + +Once the rule set has been processed, all fields (except REF) whose +name doesn't begin with an underscore are placed in the characteristics +entry as parameters. + +An entry is only produced if the rule set is explicitly terminated. + + +Parts list (.par) +------------------ + +A parts file lists the parts that are suitable for a given BOM item. +The file begins with a line containing only +#PAR + +After this, each line contains the component reference, a space, and +then one or more namespace part-number groups, separated by spaces as +well. + +Blank lines and comments (#) are ignored. + + +Order list (.ord) +----------------- + +An order file lists the quantities to order from inventories, along +with the cost and the component references the item is used for. The +file begins with a line containing only +#ORD + +After this, each line contains the supplier (namespace), the part +number, the number of items to order, the currency code, the cost, +and one or more component references. + +Blank lines and comments (#) are ignored. + + +Equivalence (.equ) +------------------ + +Equivalence files establish equivalences between parts numbers in the +same or in different name spaces. An equivalence file begins with a +line containing only +#EQU + +After this, each line consists of the following four space-separated +fields: + +namespace-1 part-number-1 namespace-2 part-number-2 + +Blank lines and comments (#) are ignored. + + +Inventory (.inv) +---------------- + +Inventory files list inventory and component cost. An inventory file +begins with a line containing only +#INV + +After this, each line contains the namespace and the part number, +followed by the number of items in stock, the currency code, and one +or more pricing entries. + +Each pricing entry consists of two fields: the number of items in an +order, and the per item price at that quantity. A sequence of +increasing order sizes indicates that they are quanta. A sequence of +decreasing order sizes indicates that smaller quanta are possible +after a previous larger threshold has been met. + +Example: + +... USD 1 0.5 10 0.4 100 0.2 + +Means that an order of at least 170 units would be made either as +2 * 100 units, costing USD 40, or as 1 * 100 + 7 * 10 units, costing +USD 20 + USD 28 = USD 48. + +If the entry is + +... USD 1 0.5 10 0.4 100 0.2 1 0.2 + +Then the USD 0.2 per unit cost would apply to any any quantity of at +least 100 units. So a 170 units order would cost USD 34. + +Blank lines and comments (#) are ignored. + +The number of items in stock and the pricing data can be omitted. We +call this "virtual inventory". In this case, the numer of items in +stock and the price default to large numbers (e.g., 999999). Virtual +inventory is used to suppress warnings for parts that have not been +sourced yet, but where sourcing is in progress. + + +Description (.dsc) +------------------ + +A description file contains plain text descriptions of parts. The file +begins with a like containing only +#DSC + +Each line contains the name space, a space, the part number, another +space, and the description. The description can contain any printable +character and ends with a newline. + +Blank lines and comments (#) are ignored. diff --git a/old-boom/annotate b/old-boom/annotate new file mode 100755 index 0000000..599b157 --- /dev/null +++ b/old-boom/annotate @@ -0,0 +1,222 @@ +#!/usr/bin/perl + +require "parser.pl"; +require "misc.pl"; + + +$H = 50; # character height +$W = $H*0.9; # character width +$L = $H+20; # line skip + + +sub normalize +{ + my @t = @_; + + # convert from (x0, y0, w, h) to (x0, y0, x1, y1) + $t[2] += $t[0]; + $t[3] = $t[1]-$t[3]; + return ($t[0], $t[3], $t[2], $t[1]); +} + + +# +# 2x2 matrix inversion +# http://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_2.C3.972_matrices +# + +sub invert +{ + my @m = @_; + my $f = 1/($m[0]*$m[3]-$m[1]*$m[2]); + return ($f*$m[3], -$f*$m[1], -$f*$m[2], $f*$m[0]); +} + + +sub block +{ + my @t = &normalize(@_); + push(@block, [ @t ]); + $wnl .= "Wire Notes Line\n\t$t[0] $t[1] $t[2] $t[3]\n"; +} + + +sub pass +{ + my @t = &normalize(@_); + + for (@block) { + my @b = @{ $_ }; + next if $t[0] > $b[2]; + next if $t[2] < $b[0]; + next if $t[1] > $b[3]; + next if $t[3] < $b[1]; + return 0; + } + return 1; +} + + +sub put +{ + local ($x0, $y0, $ref, @s) = @_; + + my $h = @s*$L; + my $w = 0; + for (@s) { + my $t = $W*length $_; + $w = $t if $t > $w; + } + my $a = 270; + my $r = 100; + my $x, $y; + my $ym = $y0-$h+$H/2; + for ($i = 0; $i != 128; $i++) { + $x = int($x0+$r*cos($a/180*3.14159)); + $y = int($ym+$r*sin($a/180*3.14159)); + last if &pass($x, $y, $w, $h); + $a += 22.5; + $r += $L/8; + } + warn "no place found for \"$s[0]\"" if $i == 128; + + my @m = &invert( @{ $m{$ref} }); + &block($x, $y+$H/2, $w, $h); + my $n = 10; + for my $s (reverse @s) { + my $dx = $x-$x0; + my $dy = $y-$y0; + my $sx = $x0+$dx*$m[0]+$dy*$m[1]; + my $sy = $y0+$dx*$m[2]+$dy*$m[3]; + ($hv, $hj, $vj) = ("H", "L", "C") if $m[0] == 1; + ($hv, $hj, $vj) = ("H", "R", "C") if $m[0] == -1; + ($hv, $hj, $vj) = ("V", "C", "B") if $m[1] == 1; + ($hv, $hj, $vj) = ("V", "C", "T") if $m[1] == -1; + $s =~ s/~/-/g; + print "F $n \"$s\" $hv $sx $sy $H 0000 $hj ${vj}NN\n"; + $y -= $L; + $n++; + } +} + + +sub dsc_parts +{ + local ($ref) = @_; + my @p = @{ $parts{$ref} }; + my @f = (); + while (@p) { + my @id = splice(@p, 0, 2); + my $id = "$id[0] $id[1]"; + my $dsc = &dsc_find($id); + push(@f, &dsc) if defined $dsc; + } + return @f; +} + + +sub dsc_order +{ + local ($ref) = @_; + my @f = (); + for my $id (keys %order) { + my @p = @{ $order{$id} }; + for (splice(@p, 3)) { + push(@f, &dsc_find($id)) if $_ eq $ref; + } + } + return @f; +} + + +sub usage +{ + print STDERR "usage: $0 [-s/from/to/ ...] ...\n"; + exit(1); +} + + +while ($ARGV[0] =~ /^-s/) { + &usage unless &dsc_xlat_arg($'); + shift @ARGV; +} +&usage if $ARGV[0] =~ /^-./; + +&parse; + + +# +# pass 1: find the orientation of all parts +# + +for (@eeschema) { + $ref = $1 if /^L \S+ (\S+)/; + undef $ref if /^\$EndComp/; + next unless /^\s+(-?[01])\s+(-?[01])\s+(-?[01])\s+(-?[01])\s*$/; + my @m = split(/\s+/); + shift @m; + $m{$ref} = [ @m ]; +} + + +# +# pass 2: block the spaces occupied by fields +# + +for (@eeschema) { + $ref = $1 if /^L \S+ (\S+)/; + if (/^P (\d+) (\d+)/) { + $x0 = $1; + $y0 = $2; + } + next unless /^F /; + die "$_" unless + /^F \d+ "([^"]*)" ([HV]) (\d+) (\d+) (\d+) +(\d+) ([LC]) (C)/; + ($s, $hv, $x, $y, $size, $flag, $hj, $vj) = + ($1, $2, $3, $4, $5, $6, $7, $8); + $dx = $x-$x0; + $dy = $y-$y0; + $x = $x0+$dx*$m{$ref}[0]+$dy*$m{$ref}[1]; + $y = $y0+$dx*$m{$ref}[2]+$dy*$m{$ref}[3]; + next if $flag != 0; + $w = $size*0.8*length $s; + # we don't need to consider H/V + &block($hj eq "L" ? $x : $x-$w/2, $y+$size/2, $w, $size); +} + +# +# pass 3: +# + +for (@eeschema) { + undef @f if /^\$Comp/; + if (/^L \S+ (\S+)/) { + $ref = $1; + push(@f, &dsc_order($ref)) if %order; + push(@f, &dsc_parts($ref)) if %parts; + } + if (/^P (\d+) (\d+)/) { + $x = $1; + $y = $2; + } + if (/^\s+/) { + my %seen; + my @u = (); + for (@f) { + next if $seen{$_}; + push(@u, $_); + $seen{$_} = 1; + } + undef @f; + # $m{$ref}[0] == 1 OK + # $m{$ref}[0] == -1 OK + # $m{$ref}[1] == 1 OK + # $m{$ref}[1] == -1 OK (small deviations found) + &put($x, $y, $ref, @u) if 1 || $m{$ref}[1] == -1; + } + if (/\$EndSCHEMATC/) { + # uncomment for debugging +# print $wnl; + } + print "$_\n"; +} diff --git a/old-boom/bom2part b/old-boom/bom2part new file mode 100755 index 0000000..5161705 --- /dev/null +++ b/old-boom/bom2part @@ -0,0 +1,129 @@ +#!/usr/bin/perl + +require "parser.pl"; +require "match.pl"; +require "misc.pl"; + + +sub issue +{ + print shift(@_), " ", join(" ", @_, &eq(@_)), "\n"; +} + + +sub scale +{ + local ($v, $m) = @_; + + return $v*1e-12 if $m eq "p"; + return $v*1e-9 if $m eq "n"; + return $v*1e-6 if $m eq "u"; + return $v*1e-3 if $m eq "m"; + return $v*1e3 if $m eq "k"; + return $v*1e6 if $m eq "M"; + return $v*1e9 if $m eq "G"; + return $v if $m eq ""; + die "unknown multiplier \"$m\""; +} + + +sub compat +{ + local ($a, $b) = @_; # $a = part char., $b = component spec. + + return 1 if $a eq $b; + return 0 unless $a =~ /^([0-9.]+)([GMkmunp]?)/; + my ($av, $am, $au) = ($1, $2, $'); + return 0 unless $b =~ /^(>|>=|<|<=)([0-9.]+)([GMkmunp]?)/; + my ($rel, $bv, $bm, $bu) = ($1, $2, $3, $'); + return 0 if $au ne $bu; + $av = &scale($av, $am); + $bv = &scale($bv, $bm); + return $av > $bv if $rel eq ">"; + return $av >= $bv if $rel eq ">="; + return $av < $bv if $rel eq "<"; + return $av <= $bv if $rel eq "<="; + die; +} + + +if ($ARGV[0] eq "-d") { + $debug = 1; + shift @ARGV; +} +&parse; + +$total = 0; +$bad = 0; + +print "#PAR\n"; +for $ref (keys %cmp) { + @f = @{ $cmp{$ref} }; + $total++; + + print STDERR "REF $ref\n" if $debug; + + # if we're lucky, we get a direct ID match + + if (defined $id{$f[0]}) { + print STDERR "FIRST ID\n" if $debug; + &issue($ref, $id{$f[0]}); + next; + } + + # no such luck. Let's roll up our sleeves and to the substitutions. + + undef %field; + $field{"REF"} = $ref; + $field{"VAL"} = $f[0]; + if ($f[1] eq "") { + print STDERR "warning: $ref ($f[0]) has no footprint\n"; + } else { + $field{"FP"} = $f[1]; + } + for (my $i = 1; $i != 10; $i++) { + $field{"F$i"} = $f[$i+1]; + } + &apply_rules(); + + # try our luck again + + if (defined $id{$field{"VAL"}}) { + print STDERR "SECOND ID\n" if $debug; + &issue($ref, $id{$field{"VAL"}}); + next; + } + + # still nothing. Let's match characteristics then. + + my @p = (); + COMP: for my $c (keys %chr) { + print STDERR "PART $c\n" if $debug; + for (keys %field) { + next if $_ eq "REF" || $_ eq "VAL" || $_ =~ /^F\d$/; + next if $field{$_} eq ""; + print STDERR " $_=",$field{$_}," " if $debug; + if (!defined $chr{$c}{$_}) { + print STDERR "NO FIELD\n" if $debug; + next COMP; + next; + } + if (&compat($chr{$c}{$_}, $field{$_})) { + print STDERR "== $chr{$c}{$_}\n" if $debug; + } else { + print STDERR "!= $chr{$c}{$_}\n" if $debug; + next COMP; + } + } + push(@p, $c); + } + if (@p) { + &issue($ref, @p); + next; + } + + print STDERR "unmatched: $ref (", join(", ", @f), ")\n"; + $bad++; +# print join("#", ($ref, @f)), " -> $id{$f[0]}\n"; +} +print STDERR "$bad/$total unmatched\n" if $bad; diff --git a/old-boom/boom b/old-boom/boom new file mode 100755 index 0000000..62ed192 --- /dev/null +++ b/old-boom/boom @@ -0,0 +1,26 @@ +#!/usr/bin/perl + + +sub usage +{ + print STDERR "usage: $0 command [arg ...]\n"; + exit(1); +} + + +&usage unless @ARGV; + +($d = $0) =~ s|/[^/]*$||; +if ($d eq "") { + $p = "/"; +} elsif ($d =~ /^\//) { + $p = "$d"; +} else { + chomp($cwd = `pwd`); + $p = "$cwd/$d"; +} + +$cmd = shift @ARGV; +$cmd = "$p/$cmd" unless $cmd =~ m|/|; +exec("perl", "-I", $p, $cmd, @ARGV); +die "exec perl: $!"; diff --git a/old-boom/gen2chr b/old-boom/gen2chr new file mode 100755 index 0000000..342452e --- /dev/null +++ b/old-boom/gen2chr @@ -0,0 +1,64 @@ +#!/usr/bin/perl + +require "parser.pl"; +require "match.pl"; + + +sub translate +{ + local ($r) = @_; + + undef %field; + $field{"REF"} = $r; + &match_set_error($r); + if (!&apply_rules()) { + print "$id{$r}\n" if $negate; + return; + } + return if $negate; + print $id{$r}; + for (sort keys %field) { + next if $_ =~ /^_/; + next if $_ eq "REF"; + print " $_=$field{$_}"; + } + print "\n"; +} + + +sub usage +{ + print STDERR "usage: $0 [-d|-n] key file ...\n"; + print STDERR " $0 -q query file ...\n"; + exit(1); +} + +while ($ARGV[0] =~ /^-/) { + if ($ARGV[0] eq "-d") { + $debug = 1; + } elsif ($ARGV[0] eq "-n") { + $negate = 1; + } elsif ($ARGV[0] eq "-q") { + $query = 1; + } else { + &usage; + } + shift @ARGV; +} + +$key = shift @ARGV; +&usage unless defined $key; + +&parse; + +if ($query) { + $debug = 1; + &translate($key); + exit(0); +} + +print "#CHR\n"; +for (keys %id) { + next unless $id{$_} eq "$key $_"; + &translate($_); +} diff --git a/old-boom/match.pl b/old-boom/match.pl new file mode 100755 index 0000000..fccf35d --- /dev/null +++ b/old-boom/match.pl @@ -0,0 +1,210 @@ +#!/usr/bin/perl + +use re 'eval'; + + +# +# "sub" populates the following global variables: +# +# $end[rule-number] = 0 / 1 +# $match[rule-number]{field}[0] = original-pattern +# $match[rule-number]{field}[1] = RE1 +# $match[rule-number]{field}[2] = RE2 +# $action[rule-number]{field} = value +# +# $match_stack[depth]{field}[0] = original-pattern +# $match_stack[depth]{field}[1] = RE1 +# $match_stack[depth]{field}[2] = RE2 +# $action_stack[depth]{field} = value +# $may_cont = 0 / 1 +# $last +# $last_action +# + +# +# $cvn_from{internal-handle} = index +# $cvn_to{internal-handle} = index +# $cvn_unit{internal-handle} = unit-name +# $cvn_num = internal-handle +# $found{field-or-subfield} = string + + +# +# We convert each input pattern into two regular expressions: the first matches +# units in the nXn notation, e.g., 4u7 or 100R. The second matches them in SI +# notation (sans space). +# +# When matching (sub_match), we first apply the first expression. Each time we +# encounter a unit ($R, $F, etc.), __cvn is called. __cvn stores the index of +# the unit in %cvn_from and %cvn_to. +# +# We then pick these substrings from the input string and convert the units to +# SI notation. At the same time, we normalize the mantissa. Once done, we run +# the second expression. This one always matches (hopefully :-) +# +# All (...) ranges in the original pattern have been replaced with named +# capture buffers in the second expression, so all these subfields are now +# gathered in the $+ array. (The same also happened in the first pass, but we +# ignore it.) +# +# Finally, when expanding a value (sub_expand), we look for $field and +# $field:index, and expand accordingly. +# + + +sub __cvn +{ + local ($num) = @_; + + $cvn_from{$num} = $-[$#-]; + $cvn_to{$num} = $+[$#+]; +} + + +sub sub_match +{ + local ($s, $field, $m1, $m2) = @_; + + # + # Perform the first match and record where we saw $ patterns. + # + undef %cvn_from; + undef %cvn_to; + return undef unless $s =~ $m1; + + # + # Convert the unit patterns to almost-SI notation. (We don't put a space + # after the number, but the rest is SI-compliant.) + # + my $off = 0; + for (keys %cvn_from) { + my $unit = $cvn_unit{$_}; + my $from = $cvn_from{$_}+$off; + my $len = $cvn_to{$_}-$cvn_from{$_}; + die unless substr($s, $from, $len) =~ + /(\d+)$unit(\d*)|(\d+)([GMkmunpf])(\d*)/; + + # + # Normalize to \d+.\d* + # + my $v = "$1$3.$2$5"; + my $exp = $4 eq "" ? " " : $4; + + # + # Remove leading zeroes. + # + $v =~ s/^0*(\d+)/\1/; + + # + # Mantissa must be < 1000. + # Do the math as string operation to avoid rounding errors. + # + while ($v =~ /(\d+)(\d{3})\./) { + $v = "$1.$2$'"; + $exp =~ tr/GMk munpf/TGMk munp/; + } + + # + # Mantissa must be >= 1. + # + while ($v =~ /\b0\.(\d+)/) { + if (length $1 < 3) { + $v = $1.("0" x (3-length $1))."."; + } else { + $v = substr($1, 0, 3).".".substr($1, 3); + } + $exp =~ tr/GMk munpf/Mk munpa/; + } + + # + # Remove trailing zeroes + # + $v =~ s/(\.[1-9]*)0*/\1/; + + $exp =~ s/ //; + $v =~ s/\.$//; + $v = $v.$exp.$unit; + $off += length($v)-$len; + substr($s, $from, $len, $v); + } + + # + # Run the second match on the string to process any (...) patterns + # + $found{$field} = $s; + die $m2 unless $s =~ $m2; + for (keys %+) { + $found{$_} = $+{$_}; + } + return $s; +} + + +sub sub_expand +{ + local ($s) = @_; + + while ($s =~ /^([^\$]*)\$([A-Za-z_]\w*)(:(\d+))?|^([^\$]*)\${([A-Za-z_]\w*)(:(\d+))?}/) { + my $name = "$2$6"; + $name .= "__$4$8" if defined($4) || defined($8); + if (!defined $found{$name}) { + die "don't know \"$name\"". + (defined $__match_error ? + " (processing \"$__match_error\")" : ""); + } + $s = $1.$5.$found{$name}.$'; + } + return $s; +} + + +# +# return 0 if all rules have been exhausted, 1 if there was an explicit halt. +# + +sub apply_rules +{ + RULE: for (my $i = 0; $i <= $#match; $i++) { + print STDERR "RULE #$i\n" if $debug; + %found = %field; + FIELD: for my $f (keys %{ $match[$i] }) { + my @f = $f ne "FN" ? ($f) : + ("F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"); + for (@f) { + print STDERR " MATCH $_=$match[$i]{$f}[0] " if $debug; + if (!defined $found{$_}) { + print STDERR "NO FIELD\n" if $debug; + next; + } + print STDERR "FIELD $found{$_} " if $debug; + if (!defined &sub_match($found{$_}, $f, + $match[$i]{$f}[1], $match[$i]{$f}[2])) { + print STDERR "MISS\n" if $debug; + next; + } + print STDERR "MATCH\n" if $debug; + next FIELD; + } + next RULE; + } + for (keys %{ $action[$i] }) { + my $s = &sub_expand($action[$i]{$_}); + print STDERR " SET $_=$action[$i]{$_} => $s\n" if $debug; + $field{$_} = $s; + } + if ($end[$i]) { + print STDERR " END\n" if $debug; + return 1; + } + } + return 0; +} + + +sub match_set_error +{ + $__match_error = $_[0]; +} + + +return 1; diff --git a/old-boom/misc.pl b/old-boom/misc.pl new file mode 100755 index 0000000..acc657f --- /dev/null +++ b/old-boom/misc.pl @@ -0,0 +1,95 @@ +#!/usr/bin/perl + + +# +# determine the equivalent parts, taking into account that %eq is transitive +# + +sub eq +{ + my %seen; + my @p = @_; # parts to consider + my @r = (); # new equivalences we've found + my $skip = @p; + + while (@p) { + my $p = shift @p; + next if $seen{$p}; + $seen{$p} = 1; + push(@r, $p) if $skip-- <= 0; + push(@p, @{ $eq{$p} }); + } + return @r; +} + + +# +# When looking for a description, we also consider equivalent parts. +# +# Furthermore, some descriptions may just be pointers to other descriptions. +# Users can add regular expressions that are used to extract references from +# a description, which are then looked up as well. +# + +sub __dsc_lookup +{ + local ($id) = @_; + + for ($id, &eq($id)) { + return $dsc{$_} if defined $dsc{$_}; + } + return undef; +} + + +sub dsc_find +{ + my $id = $_[0]; + LOOKUP: while (1) { + my $dsc = &__dsc_lookup($id); + return undef unless defined $dsc; + for (my $i = 0; $i <= $#xlat_from; $i++) { +# @@@ this is UUUUHHHGLLEEEEE !!! Why can't I just expand $to[$i] ? + next + unless ($id = $dsc) =~ s/^.*$xlat_from[$i].*$/$xlat_to[$i] $1/; + next LOOKUP if defined &__dsc_lookup($id); + } + return $dsc; + } + return undef; +} + + +sub dsc_xlat +{ + local ($from, $to) = @_; + push(@xlat_from, $from); + push(@xlat_to, $to); +} + + +sub dsc_xlat_arg +{ + return undef unless $_[0] =~ /^(.)([^\1]*)\1([^\1]*)\1$/; + &dsc_xlat($2, $3); + return 1; +} + + +# +# Lexical ordering of component references +# + +sub cmp_cref +{ + local ($a, $b) = @_; + local ($as, $an, $bs, $bn); + + return $a cmp $b unless ($as, $an) = $a =~ /^([[:alpha:]]+)(\d*)$/; + return $a cmp $b unless ($bs, $bn) = $b =~ /^([[:alpha:]]+)(\d*)$/; + return $as cmp $bs unless $as eq $bs; + return $an <=> $bn +} + + +return 1; diff --git a/old-boom/pardup.pl b/old-boom/pardup.pl new file mode 100755 index 0000000..09b5778 --- /dev/null +++ b/old-boom/pardup.pl @@ -0,0 +1,16 @@ +#!/usr/bin/perl +while (<>) { + @f = split(/\s+/); + $ref = shift @f; + for ($i = 0; $i != @f; $i++) { + next unless $f[$i] eq "FIC" || $f[$i] eq "MISSING" || + $f[$i] eq "DIGI-KEY"; + splice(@f, $i, 2); + $i--; + } + next if @f < 3; + push(@{ $multi{join(" ", @f)} }, $ref); +} +for (sort keys %multi) { + print "$_ -- ", join(" ", @{ $multi{$_} }), "\n"; +} diff --git a/old-boom/parser.pl b/old-boom/parser.pl new file mode 100755 index 0000000..d532eb6 --- /dev/null +++ b/old-boom/parser.pl @@ -0,0 +1,496 @@ +#!/usr/bin/perl + +use re 'eval'; +use IO::File; + + +# +# "sanitize" converts all "special" characters to underscores. This is used to +# avoid part names that could conflict with other uses of meta-characters, such +# as spaces or hash signs. +# + +sub sanitize +{ + local (*s) = @_; + my $ok = '[^-a-zA-Z0-9._%,:()=+\/]'; + + print STDERR "converting special character(s) in $s\n" if $s =~ /$ok/; + $s =~ s/$ok/_/g; +} + + +sub skip +{ + # do nothing +} + + +# +# "bom" populates the following global variable: +# +# $cmp{component-reference}[0] = value +# $cmp{component-reference}[1] = footprint +# $cmp{component-reference}[2] = field1 +# ... +# + +sub bom +{ + if (/^#End Cmp/) { + $mode = *skip; + return; + } + die unless /^\|\s+(\S+)\s+/; + my $ref = $1; + my @f = split(/\s*;\s*/, $'); + next if $f[0] eq "NC"; + for (@f) { + s/\s+$//; + &sanitize(\$_); + } + $cmp{$ref} = [ @f ]; +} + + +# +# "equ" populates the following global variables: +# +# $id{item-number} = "namespace item-number" +# This is used for heuristics that look up parts commonly referred to by +# their part number. +# +# $eq{"namespace0 item-number0"}[] = ("namespace1 item-number1", ...) +# List of all parts a given part is equivalent to. +# + +sub equ +{ + my @f = split(/\s+/); + &sanitize(\$f[1]); + &sanitize(\$f[3]); + my $a = "$f[0] $f[1]"; + my $b = "$f[2] $f[3]"; + $id{$f[1]} = $a; + $id{$f[3]} = $b; + push @{ $eq{$a} }, $b; + push @{ $eq{$b} }, $a; +} + + +# +# "inv" populates the following global variables: +# +# $id{item-number} = "namespace item-number" +# This is used for heuristics that look up parts commonly referred to by +# their part number. +# +# $inv{"namespace item-number"}[0] = items-in-stock +# $inv{"namespace item-number"}[1] = currency +# $inv{"namespace item-number"}[2] = order-quantity +# $inv{"namespace item-number"}[3] = unit-price +# [2] and [3] may repeat. +# + +sub inv +{ + my @f = split(/\s+/); + &sanitize(\$f[1]); + my $id = "$f[0] $f[1]"; + shift @f; + my $ref = shift @f; + die "duplicate inventory entry for \"$id\"" if defined $inv{$id}; + $id{$ref} = $id; + $inv{$id} = [ @f ]; + $inv{$id}[0] = 999999 unless defined $inv{$id}[0]; + $inv{$id}[1] = "N/A" unless defined $inv{$id}[1]; + $inv{$id}[2] = 1 unless defined $inv{$id}[2]; + $inv{$id}[3] = 999999 unless defined $inv{$id}[3]; +} + + +# +# "par" populates the following global variables: +# +# $parts{component-ref}[0] = namespace +# $parts{component-ref}[1] = item-number +# [0] and [1] may repeat +# +# $want{"namespace item"} = number of times we may use the part. If multiple +# parts are eligible for a component, each of them is counted as desirable +# for each component. +# +# $comps{"namespace item"}{component-ref} = 1 +# Set of components a part may be used for. +# + +sub par +{ + my @f = split(/\s+/); + my $ref = shift @f; + $parts{$ref} = [ @f ]; + while (@f) { + my @id = splice(@f, 0, 2); + my $id = "$id[0] $id[1]"; + $want{$id}++; + $comps{$id}{$ref} = 1; + } +} + + +# +# "chr" populates the following global variable: +# +# $chr{"namespace item-number"}{parameter} = value +# +# $last is used internally for continuation lines. +# + +sub chr +{ + my @f; + if (/^\s+/) { + @f = split(/\s+/, $'); + } else { + @f = split(/\s+/); + my $ref = shift @f; + my $num = shift @f; + $last = "$ref $num"; + } + for (@f) { + die "\"=\" missing in $_" unless /=/; + $chr{$last}{uc($`)} = $'; + } +} + + +# +# "sub" populates the following global variables: +# +# $end[rule-number] = 0 / 1 +# $match[rule-number]{field}[0] = original-pattern +# $match[rule-number]{field}[1] = RE1 +# $match[rule-number]{field}[2] = RE2 +# $action[rule-number]{field} = value +# +# $match_stack[depth]{field}[0] = original-pattern +# $match_stack[depth]{field}[1] = RE1 +# $match_stack[depth]{field}[2] = RE2 +# $action_stack[depth]{field} = value +# $may_cont = 0 / 1 +# $last +# $last_action +# + +# +# $cvn_from{internal-handle} = index +# $cvn_to{internal-handle} = index +# $cvn_unit{internal-handle} = unit-name +# $cvn_num = internal-handle +# $found{field-or-subfield} = string + + +sub sub_pattern +{ + local ($field, $p) = @_; + my $n = 0; + $p =~ s/\./\\./g; + $p =~ s/\+/\\+/g; + $p =~ s/\?/./g; + $p =~ s/\*/.*/g; + my $tmp = ""; + while ($p =~ /^([^\(]*)\(/) { + $n++; + $tmp .= "$1(?'${field}__$n'"; + $p = $'; + } + $p = "^".$tmp.$p."\$"; + my $q = $p; + while ($p =~ /^([^\$]*)\$(.)/) { + $p = "$1(\\d+$2\\d*|\\d+[GMkmunpf$2]\\d*)(?{ &__cvn($cvn_num); })$'"; + $cvn_unit{$cvn_num} = $2; + die unless $q =~ /^([^\$]*)\$(.)/; + $q = "$1(\\d+(\.\\d+)?[GMkmunpf]?$2)$'"; + $cvn_num++; + } + return ($p, $q); +} + + +sub sub_value +{ + return $_[0]; +} + + +sub sub +{ + /^(\s*)/; + my $indent = $1; + my @f = split(/\s+/, $'); + my $f; + my $in = 0; # indentation level + while (length $indent) { + my $c = substr($indent, 0, 1, ""); + if ($c eq " ") { + $in++; + } elsif ($c eq "\t") { + $in = ($in+8) & ~7; + } else { + die; + } + } + if ($may_cont && $in > $last) { + pop(@match); + pop(@action); + pop(@end); + } else { + $match_stack[0] = undef; + $action_stack[0] = undef; + $last_action = 0; + $last = $in; + } + if (!$last_action) { + while (@f) { + $f = shift @f; + last if $f eq "->" || $f eq "{" || $f eq "}" || $f eq "!"; + if ($f =~ /=/) { + $match_stack[0]{uc($`)} = [ $', &sub_pattern(uc($`), $') ]; + } else { + $match_stack[0]{"REF"} = [ &sub_pattern("REF", $f) ]; + } + } + $last_action = 1 if $f eq "->"; + } + if ($last_action) { + while (@f) { + $f = shift @f; + last if $f eq "{" || $f eq "!"; + die unless $f =~ /=/; + $action_stack[0]{uc($`)} = &sub_value($'); + } + } + $may_cont = 0; + if ($f eq "{") { + unshift(@match_stack, undef); + unshift(@action_stack, undef); + die "items following {" if @f; + } elsif ($f eq "}") { + shift @match_stack; + shift @action_stack; + die "items following }" if @f; + } else { + die "items following !" if @f && $f eq "!"; + push(@end, $f eq "!"); + $may_cont = $f ne "!"; + my $n = $#end; + push(@match, undef); + push(@action, undef); + for my $m (reverse @match_stack) { + for (keys %{ $m }) { + $match[$n]{$_} = $m->{$_}; + } + } + for my $a (reverse @action_stack) { + for (keys %{ $a }) { + $action[$n]{$_} = $a->{$_}; + } + } + } +} + + +# +# "ord" populates the following global variables: +# +# $order{"namespace item-number"}[0] = quantity to order +# $order{"namespace item-number"}[1] = currency +# $order{"namespace item-number"}[2] = total cost in above currency +# $order{"namespace item-number"}[3] = component reference +# ... +# + +sub ord +{ + my @f = split(/\s+/); + my @id = splice(@f, 0, 2); + @{ $order{"$id[0] $id[1]"} } = @f; +} + + +# +# "dsc" populates the following global variable: +# +# $dsc{"namespace item-number"} = description +# + +sub dsc +{ + my @f = split(/\s+/); + my @id = splice(@f, 0, 2); + $dsc{"$id[0] $id[1]"} = join(" ", @f); +} + + +# +# "eeschema" populates the following global variable: +# +# $eeschema[] = line +# + + +sub eeschema +{ + push(@eeschema, $_[0]); + if ($_[0] =~ /^\$EndSCHEMATC/) { + $mode = *skip; + undef $raw; + } +} + + +sub babylonic +{ + if ($_[0] =~ /^#/) { + $hash++; + if ($hash == 2) { + $mode = *skip; + undef $raw; + } + return; + } + &bom($_[0]) if $hash == 1; +} + + +sub dirname +{ + local ($name) = @_; + + return $name =~ m|/[^/]*$| ? $` : "."; +} + + +sub rel_path +{ + local ($cwd, $path) = @_; + + return $path =~ m|^/| ? $path : "$cwd/$path"; +} + + +sub parse_one +{ + local ($name) = @_; + + my $file = new IO::File->new($name) || die "$name: $!"; + my $dir = &dirname($name); + + while (1) { + $_ = <$file>; + if (!defined $_) { + $file->close(); + return unless @inc; + $file = pop @inc; + $dir = pop @dir; + next; + } + if (/^\s*include\s+(.*?)\s*$/) { + push(@inc, $file); + push(@dir, $dir); + $name = &rel_path($dir, $1); + $dir = &dirname($name); + $file = new IO::File->new($name) || die "$name: $!"; + next; + } + chop; + +# ----- KiCad BOM parsing. Alas, the BOM is localized, so there are almost no +# reliable clues for the parser. Below would be good clues for the English +# version: + if (0 && /^#Cmp.*order = Reference/) { + $mode = *bom; + next; + } + if (0 && /^#Cmp.*order = Value/) { + $mode = *skip; + next; + } + if (0 && /^eeschema \(/) { # hack to allow loading in any order + $mode = *skip; + next; + } +# ----- now an attempt at a "generic" version: + if (/^eeschema \(/) { + $mode = *babylonic; + $hash = 0; + $raw = 1; + next; + } +# ----- + if (/^EESchema Schematic/) { + $mode = *eeschema; + $raw = 1; + die "only one schematic allowed" if defined @eeschema; + &eeschema($_); + next; + } + if (/^#EQU\b/) { + $mode = *equ; + next; + } + if (/^#INV\b/) { + $mode = *inv; + next; + } + if (/^#PAR\b/) { + $mode = *par; + next; + } + if (/^#CHR\b/) { + $mode = *chr; + undef $last; + next; + } + if (/^#(SUB|GEN)\b/) { + $mode = *sub; + undef $last; + undef $last_action; + undef $may_cont; + next; + } + if (/^#ORD\b/) { + $mode = *ord; + next; + } + if (/^#DSC\b/) { + $mode = *dsc; + next; + } + if (/^#END\b\(/) { # for commenting things out + $mode = *skip; + next; + } + if (!$raw) { + s/#.*//; + next if /^\s*$/; + } + &$mode($_); + } +} + + +sub parse +{ + $mode = *skip; + for (@ARGV) { + &parse_one($_); + } +} + +# +# in case user calls directly &parse_one and not &parse +# +$mode = *skip; + +return 1; diff --git a/old-boom/part2order b/old-boom/part2order new file mode 100755 index 0000000..26584f7 --- /dev/null +++ b/old-boom/part2order @@ -0,0 +1,133 @@ +#!/usr/bin/perl + +require "parser.pl"; +require "misc.pl"; + +$mult = shift(@ARGV); +&parse; + + +sub number +{ + local ($id) = @_; + + my $s = $inv{$id}[0]; + my $n = $want{$id}*$mult; + return $n < $s ? $n : $s; + +} + + +# +# The heuristics here aren't very nice. We give zero-cost stock priority over +# any other stock, when we go by stock size up to the quantity we need. The +# idea is to exhause local stock (zero-cost) first, then try to obtain the +# parts with as few orders as possible. +# +# It would be better to have some sort of priority, so that we can express a +# preference among stock we already own. Also, if non-zero-cost stock has widly +# different prices, the smallest order cost may not be a good indicator of +# which source we prefer. +# +# Furthermore, the algorithm doesn't consider the number of sources we use in +# total or things like lead time, shipping cost, customs, etc. +# + +sub rank +{ + local ($a, $b) = @_; + + my $na = &number($a); # min(number wanted, available) + my $nb = &number($b); + my $pa = $inv{$a}[3]; # per unit price for smallest quantum + my $pb = $inv{$b}[3]; + +#print STDERR "a=$a b=$b na=$na nb=$nb pa=$pa pb=$pb\n"; + return 1 if $na && !$pa && $pb; + return -1 if $nb && $pa && !$pb; + return $na <=> $nb if $na != $nb; + return $pb <=> $pa; +} + + +for (keys %parts) { + $parts++; +} + +print "#ORD\n"; +for (sort { &rank($b, $a) } keys %want) { + my $n = &number($_); + $n -= $n % $mult; + next unless $n; + my @f = @{ $inv{$_} }; + my $max = shift @f; + my $currency = shift @f; + my @qty; + my @price; + my %index; + my $best_qty; + my $best_price = undef; + while (@f) { + my $q = shift @f; + my $p = shift @f; + if (defined $index{$q}) { + $price[$index{$q}] = $p; + } else { + push(@qty, $q); + push(@price, $p); + $index{$q} = $#qty; + # @@@ this fails if smaller quantities following a large quantity + # differ from the quantities preceding them. E.g., 1 10 100 25 + # wouldn't yield correct results. + } + for (my $i = $#qty; $i >= 0; $i--) { + my $order = 0; + my $price = 0; + my $left = $n; + for (my $j = $#qty; $j >= $i; $j--) { + while ($left >= ($j == $i ? 1 : $qty[$j])) { + $left -= $qty[$j]; + $order += $qty[$j]; + $price += $price[$j]*$qty[$j]; + } + } + next if $order > $max; + if (!defined $best_price || $price < $best_price) { + $best_price = $price; + $best_qty = $order; + } + } + } + next if !defined $best_price; + print "$_ $best_qty $currency $best_price"; + my $id = $_; + while (keys %{ $comps{$id} }) { + last if $best_qty < $mult; + $best_qty -= $mult; + my $ref = (sort { &cmp_cref($a, $b); } keys %{ $comps{$id} })[0]; +#print STDERR "$id: $ref + ", join("|", keys %{ $comps{$id} }), "\n"; + my @f = @{ $parts{$ref} }; + while (@f) { + my @id2 = splice(@f, 0, 2); + my $id2 = "$id2[0] $id2[1]"; + $want{$id2}--; + delete $comps{$id2}{$ref}; + } + print " $ref"; + } + print "\n"; +} + +for my $id (sort { $want{$b} <=> $want{$a} } keys %want) { + next unless $want{$id}; + print STDERR "$id"; + for (&eq($id)) { +# next unless $want{$_}; + die "\n$_ ($want{$_}) vs. $id want ($want{$id})" + unless $want{$_} == $want{$id}; + print STDERR " $_"; + $want{$_} = 0; + } + print STDERR ": want $want{$id}\n"; + $want{$id} = 0; +} diff --git a/old-boom/prettyord b/old-boom/prettyord new file mode 100755 index 0000000..c6bf0a6 --- /dev/null +++ b/old-boom/prettyord @@ -0,0 +1,131 @@ +#!/usr/bin/perl + +require "parser.pl"; +require "misc.pl"; + + +sub usage +{ + print STDERR <<"END"; +usage: $0 [-c] [-f|-t] [-r] [-s/from/to/ ...] ... + + -c generate CSV output (default: generate formatted text) + -f generate SMT fab output (default: generate shopping list) + -r sort by component reference (default: sort by part number) + -s/from/to/ substitute description and treat result as reference + -t print the total number of items and the total cost. + -t cannot be combined with -c or -f. +END + exit(1); +} + +$shop = 1; +$by_pn = 1; +while ($ARGV[0] =~ /^-./) { + if ($ARGV[0] =~ /^-s/) { + &usage unless &dsc_xlat_arg($'); + } elsif ($ARGV[0] eq "-c") { + $csv = 1; + } elsif ($ARGV[0] eq "-f") { + $shop = 0; + } elsif ($ARGV[0] eq "-r") { + $by_pn = 0; + } elsif ($ARGV[0] eq "-t") { + $total = 1; + } else { + &usage; + } + shift @ARGV; +} + +&usage if $total && !$shop; +&usage if $total && $csv; + +&parse; + +$out[0][0] = "Pos"; +$out[1][0] = "Qty"; +$out[2][0] = "P/N"; +$out[3][0] = "Description"; + +if ($shop) { + $out[4][0] = "Value"; + $out[5][0] = ""; +} else { + $out[4][0] = "Ref"; +} + +for (sort { $by_pn ? $a cmp $b : &cmp_cref($order{$a}[3], $order{$b}[3]) } + keys %order) { + push(@{ $out[0] }, ++$n); + push(@{ $out[1] }, $shop ? $order{$_}[0] : @{ $order{$_} }-3); + @f = split(/\s+/, $_); + push(@{ $out[2] }, $shop ? $f[1] : "$f[0] $f[1]"); + my $dsc = &dsc_find($_); + print STDERR "$_: no description\n" unless defined $dsc; + push(@{ $out[3] }, defined $dsc ? $dsc : "???"); + if ($shop) { + push(@{ $out[4] }, $order{$_}[1]); + push(@{ $out[5] }, sprintf("%.2f", $order{$_}[2])); + $price{$order{$_}[1]} += $order{$_}[2]; + } else { + my @r = @{ $order{$_} }; + push(@{ $out[4] }, join(", ", @r[3..$#r])); + } +} + +if ($csv) { + for ($i = 0; $i <= $#{ $out[0] }; $i++) { + for ($j = 0; $j <= $#out; $j++) { + print "," if $j; + if ($i && $j < 2) { + print $out[$j][$i]; + } else { + my $s = $out[$j][$i]; + $s =~ s/"/''/g; + print "\"$s\""; + } + } + print "\n"; + } + exit(0); +} + +for (@out) { + push(@max, 0); + if (length $_->[0]) { + $max[$last_pos] = $last_len if defined $last_pos; + $last_pos = $#max; + $last_len = length $_->[0]; + } +} +$i = 0; +for (@out) { + $first = 1; + for (@{ $_ }) { + next if $first-- > 0; + $max[$i] = length $_ if length $_ > $max[$i]; + } + $i++; +} + +for ($i = 0; $i <= $#{ $out[0] }; $i++) { + $l = ""; + for ($j = 0; $j != 6; $j++) { + my $s = $out[$j][$i];; + $l .= $s if $j == 2 || $j == 3 || $j == 4; + $l .= " " x ($max[$j]-length $s); + $l .= $s if $j == 0 || $j == 1 || $j == 5; + $l .= " " unless $j == 5; + } + $l =~ s/\s*$//; + print "$l\n"; +} + +if ($total) { + print "$n item".($n == 1 ? "" : "s"); + for (sort keys %price) { + print ", $_ $price{$_}"; + } + print "\n"; +} diff --git a/old-boom/testsub b/old-boom/testsub new file mode 100755 index 0000000..22a7cdf --- /dev/null +++ b/old-boom/testsub @@ -0,0 +1,45 @@ +#!/usr/bin/perl + +require "parser.pl"; +require "match.pl"; + + +sub usage +{ + print STDERR "usage: $0 [-d] file.sub|field ...\n\n"; + print STDERR " fields: ref value [footprint user-field ...]\n"; +} + + +for (@ARGV) { + if ($_ eq "-d") { + $debug = 1; + next; + } + &usage if /^-/; + if (/\.sub$/) { + &parse_one($_); + } else { + push(@f, $_); + } +} + +&usage unless @f >= 2; + +$field{"REF"} = shift @f; +$field{"VAL"} = shift @f; +$field{"FP"} = shift @f; + +for (my $i = 1; $i != 10; $i++) { + $field{"F$i"} = $f[$i-1]; +} + +&apply_rules; + +for (sort keys %field) { + if ($field{$_} =~ / /) { + print "$_ = \"$field{$_}\"\n"; + } else { + print "$_ = $field{$_}\n"; + } +} diff --git a/old-boom/workflow.fig b/old-boom/workflow.fig new file mode 100644 index 0000000..09d9f94 --- /dev/null +++ b/old-boom/workflow.fig @@ -0,0 +1,147 @@ +#FIG 3.2 Produced by xfig version 3.2.5a +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +6 450 5850 3150 6525 +4 0 0 50 -1 14 12 0.0000 4 180 2700 450 6030 Source file (in SVN)\001 +4 0 0 50 -1 12 12 0.0000 4 150 1890 450 6255 Generated file\001 +4 0 0 50 -1 18 12 0.0000 4 210 1830 450 6480 Program (in SVN)\001 +-6 +6 450 7875 6480 9000 +4 0 0 50 -1 12 12 0.0000 4 105 540 450 8055 .csv\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 8280 .inv\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 8505 .equ\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 8730 .par\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 8955 .ord\001 +4 0 0 50 -1 1 12 0.0000 4 195 2520 1350 8280 inventory with stock and cost\001 +4 0 0 50 -1 1 12 0.0000 4 195 3045 1350 8055 GTA02 EE component stock at FIC\001 +4 0 0 50 -1 1 12 0.0000 4 195 5130 1350 8505 part number equivalences, e.g., manufacturer vs. distributor\001 +4 0 0 50 -1 1 12 0.0000 4 195 2910 1350 8730 component to part number(s) map\001 +4 0 0 50 -1 1 12 0.0000 4 195 5025 1350 8955 list of parts to order, with price and component references\001 +-6 +6 450 7425 3465 7875 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 7605 .chr\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 7830 .sub\001 +4 0 0 50 -1 1 12 0.0000 4 195 1695 1350 7605 part characteristics\001 +4 0 0 50 -1 1 12 0.0000 4 195 2085 1350 7830 parameter substitutions\001 +-6 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 4 + 1 1 2.00 60.00 60.00 + 7875 2700 7875 2925 6525 2925 6525 4725 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 4 + 1 1 2.00 60.00 60.00 + 9000 2700 9000 5850 6525 5850 6525 6075 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 4 + 1 1 2.00 60.00 60.00 + 3150 1350 3150 5850 6075 5850 6075 6075 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 2700 6300 4725 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 2025 6300 2475 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 1350 6300 1800 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 4 + 1 1 2.00 60.00 60.00 + 4950 1350 4950 4275 6075 4275 6075 4725 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 3 + 1 1 2.00 60.00 60.00 + 3150 4500 5850 4500 5850 4725 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 4 + 1 1 2.00 60.00 60.00 + 8100 4275 8100 4500 6750 4500 6750 4725 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 + 11700 1575 11700 3150 10170 3150 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 4950 6300 5400 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 5625 6300 6075 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 6300 6300 6750 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6300 6975 6300 7425 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 6525 7650 6525 8100 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 9000 1350 9000 1800 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 3 + 1 1 2.00 60.00 60.00 + 9000 1575 7875 1575 7875 1800 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 3 + 1 1 2.00 60.00 60.00 + 9000 1575 10125 1575 10125 1800 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 7875 2025 7875 2475 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 9000 2025 9000 2475 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 7875 2925 7875 3375 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 3 + 1 1 2.00 60.00 60.00 + 8955 3150 8325 3150 8325 3375 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 8100 3600 8100 4050 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 10125 2025 10125 2475 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 4 + 1 1 2.00 60.00 60.00 + 10125 2700 10125 7200 6750 7200 6750 7425 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 2.00 60.00 60.00 + 8955 4500 8100 4500 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 9045 3150 10080 3150 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 + 11700 4050 11700 4500 10170 4500 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 10080 4500 9045 4500 +4 0 0 50 -1 2 16 0.0000 4 255 3345 450 675 BOM Processing - Workflow\001 +4 1 0 50 -1 12 12 0.0000 4 195 1890 6300 2655 gta02-core.lst\001 +4 1 0 50 -1 18 12 0.0000 4 165 1035 6300 1980 eeschema\001 +4 1 0 50 -1 14 12 0.0000 4 150 675 6300 1305 *.sch\001 +4 1 0 50 -1 18 12 0.0000 4 210 990 6300 4905 bom2part\001 +4 1 0 50 -1 18 12 0.0000 4 210 1110 6300 6255 part2order\001 +4 1 0 50 -1 12 12 0.0000 4 195 1890 6300 6930 gta02-core.ord\001 +4 1 0 50 -1 12 12 0.0000 4 195 1890 6300 5580 gta02-core.par\001 +4 1 0 50 -1 14 12 0.0000 4 195 1890 4950 1305 gta02-core.sub\001 +4 1 0 50 -1 14 12 0.0000 4 195 1890 3105 1305 gta02-core.inv\001 +4 1 0 50 -1 18 12 0.0000 4 210 975 6525 7605 prettyord\001 +4 1 0 50 -1 18 12 0.0000 4 165 1095 7875 1980 fic2vendor\001 +4 1 0 50 -1 18 12 0.0000 4 165 675 9000 1980 fic2inv\001 +4 1 0 50 -1 18 12 0.0000 4 210 855 8100 3555 gen2chr\001 +4 1 0 50 -1 12 12 0.0000 4 150 675 8100 4230 *.chr\001 +4 1 0 50 -1 12 12 0.0000 4 195 945 7875 2655 fic.equ\001 +4 1 0 50 -1 12 12 0.0000 4 150 945 9000 2655 fic.inv\001 +4 1 0 50 -1 18 12 0.0000 4 165 735 10125 1980 fic2dsc\001 +4 1 0 50 -1 14 12 0.0000 4 195 2700 9000 1305 inventory-fic-ee.csv\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 7155 .lst\001 +4 0 0 50 -1 1 12 0.0000 4 195 2220 1350 7155 BOM generated by KiCad\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 6930 .sch\001 +4 0 0 50 -1 1 12 0.0000 4 195 2010 1350 6930 schematics (for KiCad)\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 7380 .gen\001 +4 0 0 50 -1 1 12 0.0000 4 195 2745 1350 7380 characteristics generation rules\001 +4 0 0 50 -1 12 12 0.0000 4 150 540 450 9180 .dsc\001 +4 0 0 50 -1 1 12 0.0000 4 195 2685 1350 9180 Textual component description\001 +4 1 0 50 -1 14 12 0.0000 4 180 675 11700 1305 *.gen\001 +4 1 0 50 -1 14 12 0.0000 4 150 675 11700 3780 *.chr\001 +4 1 0 50 -1 14 9 0.0000 4 135 1440 11700 4005 (acx, misc, ...)\001 +4 1 0 50 -1 14 9 0.0000 4 135 1800 11700 1530 (darfon, ralec, ...)\001 +4 1 0 50 -1 12 12 0.0000 4 150 945 10125 2655 fic.dsc\001