diff --git a/convertpcb.pl b/convertpcb.pl index f2922a4..365fd36 100755 --- a/convertpcb.pl +++ b/convertpcb.pl @@ -35,8 +35,8 @@ # Wrong recordtype errors for Regions6 # Correct positioning for Cones, Cylinders, ... - -my $annotate=0; +my $kicad6 = 1; +my $annotate=1; my $absoluteWRLpath=0; @@ -912,6 +912,12 @@ ($$) $xmove=0; $ymove=0; # Enable to align GPBB to the Gerber Imports +# TE0703 +$xmove=63 + mil2mm(30.7 - 22.03); $ymove=112 - 0.300 - mil2mm(7.5 - 7.1); + +$xmove = $ymove = 0; + +print "# xmove=", $xmove, " ymove=", $ymove, "\n"; our %componentatx=(); our %pads=(); @@ -954,9 +960,11 @@ ($$$$) #print "Koordinaten:\n"; #print "x:$x1 y:$y1 dir:$dir dir2:$dir2\n"; my $nettext=($net>1)?"(net $net) (net_name \"$netname\")":""; + my $priority = $net > 1 ? "(priority 100)" : ""; print OUT < 1) { - print OUT " (segment (start $x1 $y1) (end $x2 $y2) (width $width) (layer $layer) (net 1))\n"; + print OUT " (segment (start $x1 $y1) (end $x2 $y2) (width $width) (layer $layer) (net $net))\n"; } # Automated verification against the Gerbers @@ -1546,6 +1554,9 @@ ($$) @files=glob('"*/Root Entry/Board6/Data.dat"') } + +my @board_outline; + # Now we start handling all the PCB files that were unpacked by unpack.pl already: my $filecounter=0; foreach my $filename(@files) @@ -1645,6 +1656,8 @@ ($$) my %activelayer=(); my %layernext=(); my %layerprev=(); + + # At first we extract Layer-information and other Board-related information from the Board file HandleBinFile($filename,"",0,0,sub { my %d=%{$_[0]}; @@ -1688,7 +1701,19 @@ ($$) #print "Version: $d{$_}\n"; $version=$d{$_}; } + + if (/^VX(\d+)$/) { + $board_outline[$1] ||= []; + $board_outline[$1][0] = $d{$_}; + } elsif (/^VY(\d+)$/) { + $board_outline[$1] ||= []; + $board_outline[$1][1] = $d{$_}; + } } + + ($xmove, $ymove) = (mil2mm($d{ORIGINX}), mil2mm($d{ORIGINY})); + print "xmove now: $xmove y: $ymove\n"; + }); # Board # We search for the first and last layer @@ -2372,6 +2397,13 @@ ($$) } + + print OUT "# Board outline\n"; + for (1 .. @board_outline - 1) { + print OUT "(gr_line (start ", -$xmove + mil2mm($board_outline[$_ - 1][0]), " ", $ymove - mil2mm($board_outline[$_ - 1][1]), ") (end ", -$xmove + mil2mm($board_outline[$_][0]), " ", $ymove - mil2mm($board_outline[$_][1]), ") (layer Edge.Cuts) (width 0.05))\n"; + } + + our %shapes=(); mkdir "wrlshp"; @@ -2651,169 +2683,137 @@ ($$) my $count=0; HandleBinFile("$short/Root Entry/Vias6/Data.dat","\x03",0,0, sub { - my $value=$_[1]; - $rawbinary{"Via"}{$_[3]}=$_[1]; - print OUT "#Vias#".escapeCRLF($_[3]).": ".bin2hexLF($value)."\n" if($annotate); - my $debug=($count<100); - my $x=sprintf("%.5f",-$xmove+bmil2mm(substr($value,13,4))); - assertdata("Via",$_[3],"X",bmil2(substr($value,13,4))); - my $y=sprintf("%.5f",+$ymove-bmil2mm(substr($value,17,4))); - assertdata("Via",$_[3],"Y",bmil2(substr($value,17,4))); - my $width=bmil2mm(substr($value,21,4)); - assertdata("Via",$_[3],"DIAMETER",bmil2(substr($value,21,4))); - my $HOLESIZE=bmil2mm(substr($value,25,4)); - assertdata("Via",$_[3],"HOLESIZE",bmil2(substr($value,25,4))); - - my $layer1="F.Cu"; # mapLayer(unpack("C",substr($value,0,1))); # || "F.Cu"; # Since Novena does not have any Blind or Buried Vias - my $layer2="B.Cu"; # mapLayer(unpack("C",substr($value,1,1))); # || "B.Cu"; - my $net=unpack("s",substr($value,3,2))+2; - assertdata("Via",$_[3],"NET",unpack("s",substr($value,3,2))); - - my $CAGV=unpack("C",substr($value,40,1)); # AUTOGENERATED, CAGV=CCSV=CENV ?!? - assertdata("Via",$_[3],"CAGV",$CAGV); - my $CCSV=unpack("C",substr($value,40,1)); # AUTOGENERATED - assertdata("Via",$_[3],"CCSV",$CCSV); - #my $CEN=unpack("C",substr($value,23,1)); # AUTOGENERATED Seems to be wrong - #assertdata("Via",$_[3],"CEN",$CEN); - my $CENV=unpack("C",substr($value,40,1)); # AUTOGENERATED - assertdata("Via",$_[3],"CENV",$CENV); - - my $UNIONINDEX=unpack("C",substr($value,70,1)); # AUTOGENERATED - assertdata("Via",$_[3],"UNIONINDEX",$UNIONINDEX); - - - #print "Layer: $layer1 -> $layer2\n"; - #print "Koordinaten:\n" if($debug); - #print "x:$x y:$y width:$width\n" if($debug); - my $addparams=""; - $addparams.="(drill $HOLESIZE) " if($HOLESIZE); - print OUT " (via (at $x $y) (size $width) (layers $layer1 $layer2) (net $net)$addparams)\n"; - - - # The following was an experimental automatic reverse-engineering try. The code is disabled now. - if(0) # $count>19000 && !($count%50)) - { - #print OUT " (segment (start $x1 $y1) (end $x2 $y2) (width $width) (layer $layer) (net 1))\n"; - my @foundlayers=(); - my $firstlayer=""; - my $lastlayer=""; - foreach my $layer(0 .. 9) - { - my $founds=0; - my $foundpos=""; - foreach my $gerber (@{$g[$layer]}) - { - #(via (at 57.09996 -56.09996) (size 0.4572) (layers F.Cu B.Cu)) - if($gerber=~m/\(via \(at (-?\d+\.?\d*) (-?\d+\.?\d*)\) \(size (-?\d+\.?\d*)\) \(layers (\w+\.?\w*) (\w+\.?\w*)\)\)/) - { - my ($gx,$gy,$size,$glayer1,$glayer2)=($1,$2,$3,$4,$5); - if(near($x,$gx) && near($y,$gy)) - { - $foundpos=$gerber; - $founds++; - } - } + my $value=$_[1]; + my $line = $_[3]; + local * p = sub { + my ($name, $off, $mm, $move, $neg) = @_; + my $mil = bmil2(substr($value, $off, 4)); + assertdata("Via", $line, uc($name), $mil); + if (defined $move) { + return sprintf("%.5f", $move + ($neg ? -1 : 1) * bmil2mm(substr($value, $off, 4))); } - if($founds==1) - { - $foundlayers[$layer]=$foundpos; - $firstlayer=$layer if($firstlayer eq ""); - $lastlayer=$layer; - } - print "Layer$layer:$founds\n"; + return sprintf("%.5f", bmil2mm(substr($value, $off, 4))) if $mm; + return $mil; + }; + + $rawbinary{"Via"}{$_[3]}=$_[1]; + print OUT "#Vias#".escapeCRLF($_[3]).": ".bin2hexLF($value)."\n" if($annotate); + my $debug=($count<100); + + my ($x, $y, $width, $HOLESIZE) = + (p("x", 13, 1, -$xmove), + p("y", 17, 1, $ymove, 1), + p("diameter", 21, 1), + p("holesize", 25, 1)); + + if (00) { + my $x=sprintf("%.5f",-$xmove+bmil2mm(substr($value,13,4))); + assertdata("Via",$_[3],"X",bmil2(substr($value,13,4))); + my $y=sprintf("%.5f",+$ymove-bmil2mm(substr($value,17,4))); + assertdata("Via",$_[3],"Y",bmil2(substr($value,17,4))); + + my $width=bmil2mm(substr($value,21,4)); + assertdata("Via",$_[3],"DIAMETER",bmil2(substr($value,21,4))); + my $HOLESIZE=bmil2mm(substr($value,25,4)); + assertdata("Via",$_[3],"HOLESIZE",bmil2(substr($value,25,4))); } - #print "count: $count Firstlayer: $firstlayer Lastlayer: $lastlayer ".bin2hex($value)."\n"; # if(!($firstlayer==0 && $lastlayer==9)); - #print "We found ".scalar(@found)." matches layer:$foundlayer!\n"; - #if(scalar(@found)==1) - { - #print "$count: Exactly 1 match found for layer $foundlayer:\n"; - #if(!defined($widths{$foundlayer}) || scalar(@{$widths{$foundlayer}})<15) - { - #push @{$widths{$foundlayer}},bin2hex($value); - #print "Gerber: $found[0]\n"; - #print " (segment (start $x1 $y1) (end $x2 $y2) (width $width) (layer B.Paste) (net 1))\n"; - #print OUT " (segment (start $x1 $y1) (end $x1 2000) (width $width) (layer B.Paste) (net 1))\n"; - #print OUT " (segment (start $x2 $y2) (end $x2 2000) (width $width) (layer B.Paste) (net 1))\n"; - #print "DEBUG: ".bin2hexLF($value)."\n\n"; - } + + my $layer1="F.Cu"; # mapLayer(unpack("C",substr($value,0,1))); # || "F.Cu"; # Since Novena does not have any Blind or Buried Vias + my $layer2="B.Cu"; # mapLayer(unpack("C",substr($value,1,1))); # || "B.Cu"; + my $net=unpack("s",substr($value,3,2))+2; + assertdata("Via",$_[3],"NET",unpack("s",substr($value,3,2))); + + undef * p; + + local * p = sub { + my ($name, $off) = @_; + + my $ret = unpack("C", substr($value, $off, 1)); + assertdata("Via", $line, uc($name), $ret); + $ret; + }; + + my ($CAGV, $CCSV, $CEN, $CENV, $UNIONINDEX) = + (p("cagv", 40), + p("ccsv", 40), + p("cen", 23), + p("cenv", 40), + p("unionindex", 70) + ); + + if (00) { + my $CAGV=unpack("C",substr($value,40,1)); # AUTOGENERATED, CAGV=CCSV=CENV ?!? + assertdata("Via",$_[3],"CAGV",$CAGV); + my $CCSV=unpack("C",substr($value,40,1)); # AUTOGENERATED + assertdata("Via",$_[3],"CCSV",$CCSV); + #my $CEN=unpack("C",substr($value,23,1)); # AUTOGENERATED Seems to be wrong + #assertdata("Via",$_[3],"CEN",$CEN); + my $CENV=unpack("C",substr($value,40,1)); # AUTOGENERATED + assertdata("Via",$_[3],"CENV",$CENV); + my $UNIONINDEX=unpack("C",substr($value,70,1)); # AUTOGENERATED + assertdata("Via",$_[3],"UNIONINDEX",$UNIONINDEX); } - } - $count++; - }); - - $count=0; - - - HandleBinFile("$short/Root Entry/Polygons6/Data.dat","",0,0, sub - { - my %d=%{$_[0]}; - print OUT "#Polygons#".escapeCRLF($_[3]).": ".escapeCRLF($_[1])."\n" if($annotate); - my $counter=$_[3]; - my $width=mil2mm($d{'TRACKWIDTH'}||1); - my $layer=mapLayer($d{'LAYER'}) || "F.Paste"; - my $pourindex=$d{'POURINDEX'}||0; - $pourindex-=1000 if($pourindex>=1000); - $pourindex-=100 if($pourindex>=100); - if(defined($pourindex) && ( $pourindex<0 || $pourindex>100)) - { - print STDERR "WARNING: Pourindex $pourindex out of the expected range (0 .. 100)\n"; - } - my $net=($d{'NET'}||-1)+2; my $netname=$netnames{$net}; - #print "Polygon $_[3] has net $net\n"; - my $maxpoints=0; - foreach(keys %d) - { - if(m/^SA(\d+)/) - { - $maxpoints=$1 if($1>$maxpoints); - } - } - - #print "Polygontype: $d{'POLYGONTYPE'} maxpoints:$maxpoints\n"; + + #print "Layer: $layer1 -> $layer2\n"; + #print "Koordinaten:\n" if($debug); + #print "x:$x y:$y width:$width\n" if($debug); + my $addparams=""; + $addparams.=" (drill $HOLESIZE)" if($HOLESIZE); + print OUT " (via (at $x $y) (size $width) (layers $layer1 $layer2) (net $net)$addparams)\n"; + + + # The following was an experimental automatic reverse-engineering try. The code is disabled now. + if(0) # $count>19000 && !($count%50)) + { + #print OUT " (segment (start $x1 $y1) (end $x2 $y2) (width $width) (layer $layer) (net 1))\n"; + my @foundlayers=(); + my $firstlayer=""; + my $lastlayer=""; + foreach my $layer(0 .. 9) + { + my $founds=0; + my $foundpos=""; + foreach my $gerber (@{$g[$layer]}) + { + #(via (at 57.09996 -56.09996) (size 0.4572) (layers F.Cu B.Cu)) + if($gerber=~m/\(via \(at (-?\d+\.?\d*) (-?\d+\.?\d*)\) \(size (-?\d+\.?\d*)\) \(layers (\w+\.?\w*) (\w+\.?\w*)\)\)/) + { + my ($gx,$gy,$size,$glayer1,$glayer2)=($1,$2,$3,$4,$5); + if(near($x,$gx) && near($y,$gy)) + { + $foundpos=$gerber; + $founds++; + } + } + } + if($founds==1) + { + $foundlayers[$layer]=$foundpos; + $firstlayer=$layer if($firstlayer eq ""); + $lastlayer=$layer; + } + print "Layer$layer:$founds\n"; + } + #print "count: $count Firstlayer: $firstlayer Lastlayer: $lastlayer ".bin2hex($value)."\n"; # if(!($firstlayer==0 && $lastlayer==9)); + #print "We found ".scalar(@found)." matches layer:$foundlayer!\n"; + #if(scalar(@found)==1) + { + #print "$count: Exactly 1 match found for layer $foundlayer:\n"; + #if(!defined($widths{$foundlayer}) || scalar(@{$widths{$foundlayer}})<15) + { + #push @{$widths{$foundlayer}},bin2hex($value); + #print "Gerber: $found[0]\n"; + #print " (segment (start $x1 $y1) (end $x2 $y2) (width $width) (layer B.Paste) (net 1))\n"; + #print OUT " (segment (start $x1 $y1) (end $x1 2000) (width $width) (layer B.Paste) (net 1))\n"; + #print OUT " (segment (start $x2 $y2) (end $x2 2000) (width $width) (layer B.Paste) (net 1))\n"; + #print "DEBUG: ".bin2hexLF($value)."\n\n"; + } + } + } + $count++; + }); - #return if($d{'POLYGONTYPE'} eq "Polygon"); - if($d{'POLYGONTYPE'} eq "Split Plane" || $d{'HATCHSTYLE'} eq "Solid") - { - my $thermalgap=$rules{'PolygonConnect.AIRGAP'} || "0.508"; - my $thermalbridgewidth=$rules{'PolygonConnect.RELIEFCONDUCTORWIDTH'} || "0.508"; - my $nettext=($net>1)?"(net $net) (net_name \"$netname\")":""; - my $priority=defined($pourindex)?"\n (priority ".(100-$pourindex).")":""; - my $minthickness='0.254000'; - $minthickness = $thermalbridgewidth - 0.001 if ($thermalbridgewidth le $minthickness); - print OUT <=1000); + $pourindex-=100 if($pourindex>=100); + + push @polygon_nets, $net + 2; + push @polygon_prio, 100 - $pourindex; + }); + + my $regioncount = 1; + my %has_subpoly_region; + HandleBinFile("$short/Root Entry/Regions6/Data.dat","\x0b",0,0, sub { my $value=$_[1]; + my $line = $_[3]; + $rawbinary{"Region"}{$_[3]}=$value; my $rt=unpack("C",substr($value,18,1)); @@ -2883,30 +2906,65 @@ ($$) { $linebreaks{$tpos}=3; $starts[$_]=$tpos+2; - $lengths[$_]=unpack("s",msubstr($value,$tpos,2,"len[$_]")); - $lengths[$_]=16*unpack("C",msubstr($value,$tpos+2,1)."verts")+4 if($_==3); + if (length($value) < $tpos + 2) { + print OUT "#fmterr invalid data\n"; + } + + $lengths[$_]=unpack("s",msubstr($value,$tpos,2,"len[$_]")); + + if ($_ == 0) { + if ($lengths[$_] > 512) { + print OUT "#fmterr record length adjusted by -512\n"; + $lengths[$_] -= 512; + } elsif ($lengths[$_] == 0x1c) { + print OUT "#fmterr record length adjusted by -16\n"; + $lengths[$_] -= 16; + } elsif ($lengths[$_] != 12) { + print OUT "#fmterr record length odd. ignoring this region\n"; + return; + } + } + + + + #$lengths[$_]=16*unpack("C",msubstr($value,$tpos+2,1)."verts")+4 if($_==3); + $lengths[$_]=16*unpack("S",msubstr($value,$tpos + 2,2))+4 if($_==3); $contents[$_]=substr($value,$starts[$_],$lengths[$_]); #print "contents[$_] $lengths[$_]: ".bin2hex($contents[$_])."\n"; # ".bin2hex(substr($value,$starts[$_]+$lengths[$_]))."\n"; $tpos+=2+$lengths[$_]; - $tpos+=1 if($_==1); + $tpos+=1 if($_==1 && $lengths[1] == 0); } $linebreaks{$tpos}=1; + print OUT "#Regions: starts: ", join(",", @starts), " lengths: ", join(",", @lengths), "\n" if $annotate; print OUT "#Regions#0:".escapeCRLF($_[3]).": ".bin2hexLF(substr($value,0,1000))."\n" if($annotate); my $unknownheader=substr($value,0,18); # I do not know yet, what the information in the header could mean - my $POLYGON=unpack("s",substr($value,5,2)); # AUTOGENERATED - assertdata("Region",$_[3],"POLYGON",$POLYGON) if($POLYGON>=0); + local * p = sub { + my ($name, $off) = @_; + + my $ret = unpack("s", substr($value, $off, 2)); + assertdata("Region", $line, uc($name), $ret); + $ret; + }; + + my ($POLYGON, $component, $HOLECOUNT, $HOLE0VERTEXCOUNT) = + (p("polygon", 5), + p("component", 7), + p("holecount", 14), + p("hole0vertexcount", 14)); - my $component=unpack("s",substr($value,7,2)); - assertdata("Region",$_[3],"COMPONENT",$component) if($component>0); + if (00) { + my $POLYGON=unpack("s",substr($value,5,2)); # AUTOGENERATED + assertdata("Region",$_[3],"POLYGON",$POLYGON) if($POLYGON>=0); + my $component=unpack("s",substr($value,7,2)); + assertdata("Region",$_[3],"COMPONENT",$component) if($component>0); + my $HOLECOUNT=unpack("s",substr($value,14,2)); # AUTOGENERATED, possibly other format + assertdata("Region",$_[3],"HOLECOUNT",$HOLECOUNT); + my $HOLE0VERTEXCOUNT=unpack("s",substr($value,14,2)); # AUTOGENERATED + assertdata("Region",$_[3],"HOLE0VERTEXCOUNT",$HOLE0VERTEXCOUNT); + } - my $HOLECOUNT=unpack("s",substr($value,14,2)); # AUTOGENERATED, possibly other format - assertdata("Region",$_[3],"HOLECOUNT",$HOLECOUNT); - - my $HOLE0VERTEXCOUNT=unpack("s",substr($value,14,2)); # AUTOGENERATED - assertdata("Region",$_[3],"HOLE0VERTEXCOUNT",$HOLE0VERTEXCOUNT); - my $textlen=unpack("l",substr($value,18,4)); my $text=substr($value,22,$textlen);$text=~s/\x00$//; #assertdata("Region",$_[3],"TEXT",$text); @@ -2935,17 +2993,51 @@ ($$) my $net=unpack("s",substr($value,3,2))+2; assertdata("Fill",$_[3],"NET",unpack("s",substr($value,3,2))) if($net>1); - my $netname=$netnames{$net}; - my $nettext=($net>1)?"(net $net \"$netname\")":""; + my $priority = "(priority 100)"; #my $layer=mapLayer(unpack("C",substr($content,0,1))) || "Cmts.User"; my $layer=defined($d{'V7_LAYER'})?mapLayer($d{'V7_LAYER'}):"Eco1.User"; + $layer = "Eco1.User" unless defined $layer; + + my $subpoly = $d{'SUBPOLYINDEX'}; + + my $pad_connections = "thru_hole_only"; + my $min_thickness = ""; + if ($subpoly >= 0) { + my $master = unpack("S", substr($value, 5, 2)); + + $has_subpoly_region{$master} = 1; + + if ((! defined $net || $net == 1) && defined $polygon_nets[$master]) { + $net = $polygon_nets[$master]; + } + if (defined $polygon_prio[$master]) { + $priority = "(priority " . ($polygon_prio[$master] + 1) . ")"; + } + } + $pad_connections = "yes"; + $min_thickness = "(min_thickness 0.0254)"; + my $netname=$netnames{$net}; + my $keepout = ""; + if ($net == 1) { + $net = 0; + $netname = ""; + $keepout = "(keepout (copperpour allowed) (vias allowed) (tracks allowed))"; + } + + my $island_removal = $kicad6 ? "(island_removal_mode 1) (island_area_min 0)" : ""; + my $nettext=($net>=0)?"(net $net) (net_name \"$netname\")":""; + print OUT <=1000); + $pourindex-=100 if($pourindex>=100); + if(defined($pourindex) && ( $pourindex<0 || $pourindex>100)) + { + print STDERR "WARNING: Pourindex $pourindex out of the expected range (0 .. 100)\n"; + } + my $net=($d{'NET'}||-1)+2; + + my $keepout = ""; + + if ($has_subpoly_region{$count}) { + $net = 0; + #$keepout = "(keepout (copperpour not_allowed))"; + $keepout = "(keepout (copperpour allowed) (vias allowed) (tracks allowed))"; + } + + my $netname=$netnames{$net}; + + #print "Polygon $_[3] has net $net\n"; + my $maxpoints=0; + foreach(keys %d) + { + if(m/^SA(\d+)/) + { + $maxpoints=$1 if($1>$maxpoints); + } + } + + #print "Polygontype: $d{'POLYGONTYPE'} maxpoints:$maxpoints\n"; + + #return if($d{'POLYGONTYPE'} eq "Polygon"); + + if($d{'POLYGONTYPE'} eq "Split Plane" || $d{'HATCHSTYLE'} eq "Solid") + { + my $thermalgap=$rules{'PolygonConnect.AIRGAP'} || "0.508"; + my $thermalbridgewidth=$rules{'PolygonConnect.RELIEFCONDUCTORWIDTH'} || "0.508"; + my $nettext=($net>=1)?"(net $net) (net_name \"$netname\")":""; + + my $priority=defined($pourindex)?"\n (priority ".(100-$pourindex).")":""; #"\n (priority 100)"; + + my $minthickness='0.254000'; + $minthickness = $thermalbridgewidth - 0.001 if ($thermalbridgewidth le $minthickness); + print OUT <wrlshapes.kicad_pcb";