mirror of
https://github.com/telavivmakers/at-tami.git
synced 2024-05-25 11:56:54 +03:00
added gerbmerge oldie
This commit is contained in:
parent
19619bba10
commit
b3d2a87d1b
BIN
gerber/gerbmerge-win32-2014.7z
Normal file
BIN
gerber/gerbmerge-win32-2014.7z
Normal file
Binary file not shown.
677
gerber/gerbmerge/1x1/proj1.GBL
Normal file
677
gerber/gerbmerge/1x1/proj1.GBL
Normal file
|
@ -0,0 +1,677 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.1266*%
|
||||
%ADD11C,0.0660*%
|
||||
%ADD12R,0.0460X0.0630*%
|
||||
%ADD13R,0.0250X0.0080*%
|
||||
%ADD14C,0.0700*%
|
||||
%ADD15C,0.0356*%
|
||||
%ADD16C,0.0160*%
|
||||
D10*
|
||||
X001780Y008742D03*
|
||||
X008780Y008742D03*
|
||||
D11*
|
||||
X001280Y001242D03*
|
||||
X001280Y002242D03*
|
||||
X001280Y003242D03*
|
||||
X001280Y004242D03*
|
||||
X001280Y005242D03*
|
||||
X001280Y006242D03*
|
||||
X001280Y007242D03*
|
||||
X009280Y006242D03*
|
||||
X009280Y005242D03*
|
||||
X009280Y002242D03*
|
||||
X009280Y001242D03*
|
||||
D12*
|
||||
X009080Y007242D03*
|
||||
X008480Y007242D03*
|
||||
D13*
|
||||
X008780Y007242D03*
|
||||
D14*
|
||||
X007720Y001662D03*
|
||||
X002350Y007422D03*
|
||||
D15*
|
||||
X004218Y005514D03*
|
||||
X005230Y005692D03*
|
||||
X004780Y004142D03*
|
||||
X006297Y003232D03*
|
||||
X007228Y003820D03*
|
||||
X007225Y004371D03*
|
||||
X007880Y007292D03*
|
||||
X003318Y002464D03*
|
||||
X002447Y003035D03*
|
||||
D16*
|
||||
X006297Y003232D01*
|
||||
X006025Y003508D02*
|
||||
X002849Y003346D01*
|
||||
X004600Y003796D01*
|
||||
X004703Y003754D01*
|
||||
X004857Y003754D01*
|
||||
X005000Y003813D01*
|
||||
X005109Y003922D01*
|
||||
X005168Y004065D01*
|
||||
X005168Y004219D01*
|
||||
X005131Y004310D01*
|
||||
X005435Y005357D01*
|
||||
X005450Y005363D01*
|
||||
X005559Y005472D01*
|
||||
X005618Y005615D01*
|
||||
X005618Y005769D01*
|
||||
X005559Y005912D01*
|
||||
X005450Y006021D01*
|
||||
X005307Y006080D01*
|
||||
X005153Y006080D01*
|
||||
X005010Y006021D01*
|
||||
X004901Y005912D01*
|
||||
X004842Y005769D01*
|
||||
X004842Y005615D01*
|
||||
X004879Y005524D01*
|
||||
X004575Y004477D01*
|
||||
X004560Y004471D01*
|
||||
X004451Y004362D01*
|
||||
X004449Y004356D01*
|
||||
X001649Y003636D01*
|
||||
X001586Y003700D01*
|
||||
X001484Y003742D01*
|
||||
X001586Y003784D01*
|
||||
X001738Y003936D01*
|
||||
X001820Y004135D01*
|
||||
X001820Y004349D01*
|
||||
X001738Y004548D01*
|
||||
X001586Y004700D01*
|
||||
X001484Y004742D01*
|
||||
X001586Y004784D01*
|
||||
X001738Y004936D01*
|
||||
X001820Y005135D01*
|
||||
X001820Y005349D01*
|
||||
X001738Y005548D01*
|
||||
X001586Y005700D01*
|
||||
X001484Y005742D01*
|
||||
X001586Y005784D01*
|
||||
X001738Y005936D01*
|
||||
X001820Y006135D01*
|
||||
X001820Y006349D01*
|
||||
X001738Y006548D01*
|
||||
X001586Y006700D01*
|
||||
X001443Y006759D01*
|
||||
X001476Y006769D01*
|
||||
X001547Y006806D01*
|
||||
X001612Y006853D01*
|
||||
X001669Y006910D01*
|
||||
X001716Y006975D01*
|
||||
X001753Y007046D01*
|
||||
X001777Y007123D01*
|
||||
X001790Y007202D01*
|
||||
X001790Y007224D01*
|
||||
X001298Y007224D01*
|
||||
X001298Y007260D01*
|
||||
X001262Y007260D01*
|
||||
X001262Y007752D01*
|
||||
X001240Y007752D01*
|
||||
X001161Y007739D01*
|
||||
X001084Y007715D01*
|
||||
X001013Y007678D01*
|
||||
X000990Y007662D01*
|
||||
X000990Y008152D01*
|
||||
X001070Y008032D01*
|
||||
X001396Y007815D01*
|
||||
X001780Y007738D01*
|
||||
X001951Y007772D01*
|
||||
X001946Y007767D01*
|
||||
X001897Y007700D01*
|
||||
X001859Y007625D01*
|
||||
X001833Y007546D01*
|
||||
X001820Y007464D01*
|
||||
X001820Y007442D01*
|
||||
X002330Y007442D01*
|
||||
X002330Y007926D01*
|
||||
X002490Y008032D01*
|
||||
X002707Y008358D01*
|
||||
X002784Y008742D01*
|
||||
X002707Y009126D01*
|
||||
X002490Y009452D01*
|
||||
X002369Y009532D01*
|
||||
X008191Y009532D01*
|
||||
X008070Y009452D01*
|
||||
X007853Y009126D01*
|
||||
X007776Y008742D01*
|
||||
X007853Y008358D01*
|
||||
X008070Y008032D01*
|
||||
X008396Y007815D01*
|
||||
X008636Y007767D01*
|
||||
X008163Y007767D01*
|
||||
X008041Y007645D01*
|
||||
X007957Y007680D01*
|
||||
X007803Y007680D01*
|
||||
X007660Y007621D01*
|
||||
X007551Y007512D01*
|
||||
X007492Y007369D01*
|
||||
X007492Y007215D01*
|
||||
X007551Y007072D01*
|
||||
X007660Y006963D01*
|
||||
X007803Y006904D01*
|
||||
X007957Y006904D01*
|
||||
X008040Y006938D01*
|
||||
X008040Y006840D01*
|
||||
X008163Y006717D01*
|
||||
X008797Y006717D01*
|
||||
X008827Y006747D01*
|
||||
X009045Y006747D01*
|
||||
X009045Y007045D01*
|
||||
X009115Y007115D01*
|
||||
X009115Y007207D01*
|
||||
X009115Y007277D01*
|
||||
X009115Y007369D01*
|
||||
X009045Y007439D01*
|
||||
X009045Y007737D01*
|
||||
X008827Y007737D01*
|
||||
X008818Y007746D01*
|
||||
X009164Y007815D01*
|
||||
X009490Y008032D01*
|
||||
X009558Y008135D01*
|
||||
X009558Y006711D01*
|
||||
X009406Y006774D01*
|
||||
X009421Y006783D01*
|
||||
X009454Y006816D01*
|
||||
X009478Y006858D01*
|
||||
X009490Y006903D01*
|
||||
X009490Y007207D01*
|
||||
X009115Y007207D01*
|
||||
X009115Y006758D01*
|
||||
X008974Y006700D01*
|
||||
X008822Y006548D01*
|
||||
X008740Y006349D01*
|
||||
X008740Y006135D01*
|
||||
X008822Y005936D01*
|
||||
X008974Y005784D01*
|
||||
X009117Y005725D01*
|
||||
X009084Y005715D01*
|
||||
X009013Y005678D01*
|
||||
X008948Y005631D01*
|
||||
X008891Y005574D01*
|
||||
X008844Y005509D01*
|
||||
X008807Y005438D01*
|
||||
X008783Y005361D01*
|
||||
X008770Y005282D01*
|
||||
X008770Y005260D01*
|
||||
X009262Y005260D01*
|
||||
X009262Y005224D01*
|
||||
X009298Y005224D01*
|
||||
X009298Y004732D01*
|
||||
X009320Y004732D01*
|
||||
X009399Y004745D01*
|
||||
X009476Y004769D01*
|
||||
X009547Y004806D01*
|
||||
X009558Y004814D01*
|
||||
X009558Y002711D01*
|
||||
X009387Y002782D01*
|
||||
X009173Y002782D01*
|
||||
X008974Y002700D01*
|
||||
X008822Y002548D01*
|
||||
X008740Y002349D01*
|
||||
X008740Y002135D01*
|
||||
X008822Y001936D01*
|
||||
X008974Y001784D01*
|
||||
X009117Y001725D01*
|
||||
X009084Y001715D01*
|
||||
X009013Y001678D01*
|
||||
X008948Y001631D01*
|
||||
X008891Y001574D01*
|
||||
X008844Y001509D01*
|
||||
X008807Y001438D01*
|
||||
X008783Y001361D01*
|
||||
X008770Y001282D01*
|
||||
X008770Y001260D01*
|
||||
X009262Y001260D01*
|
||||
X009262Y001224D01*
|
||||
X008770Y001224D01*
|
||||
X008770Y001202D01*
|
||||
X008783Y001123D01*
|
||||
X008807Y001046D01*
|
||||
X008844Y000975D01*
|
||||
X008860Y000952D01*
|
||||
X001744Y000952D01*
|
||||
X001820Y001135D01*
|
||||
X001820Y001349D01*
|
||||
X001738Y001548D01*
|
||||
X001586Y001700D01*
|
||||
X001484Y001742D01*
|
||||
X001586Y001784D01*
|
||||
X001738Y001936D01*
|
||||
X001820Y002135D01*
|
||||
X001820Y002349D01*
|
||||
X001738Y002548D01*
|
||||
X001586Y002700D01*
|
||||
X001484Y002742D01*
|
||||
X001586Y002784D01*
|
||||
X001738Y002936D01*
|
||||
X001795Y003075D01*
|
||||
X002073Y003147D01*
|
||||
X002059Y003112D01*
|
||||
X002059Y002958D01*
|
||||
X002118Y002815D01*
|
||||
X002227Y002706D01*
|
||||
X002370Y002647D01*
|
||||
X002524Y002647D01*
|
||||
X002667Y002706D01*
|
||||
X002719Y002759D01*
|
||||
X006051Y002929D01*
|
||||
X006077Y002903D01*
|
||||
X006220Y002844D01*
|
||||
X006374Y002844D01*
|
||||
X006517Y002903D01*
|
||||
X006626Y003012D01*
|
||||
X006685Y003155D01*
|
||||
X006685Y003309D01*
|
||||
X006626Y003452D01*
|
||||
X006517Y003561D01*
|
||||
X006374Y003620D01*
|
||||
X006220Y003620D01*
|
||||
X006077Y003561D01*
|
||||
X006025Y003508D01*
|
||||
X006159Y003595D02*
|
||||
X003817Y003595D01*
|
||||
X003338Y004071D02*
|
||||
X001793Y004071D01*
|
||||
X001714Y003912D02*
|
||||
X002721Y003912D01*
|
||||
X003201Y003437D02*
|
||||
X004619Y003437D01*
|
||||
X004434Y003754D02*
|
||||
X009558Y003754D01*
|
||||
X009558Y003595D02*
|
||||
X006435Y003595D01*
|
||||
X006632Y003437D02*
|
||||
X009558Y003437D01*
|
||||
X009558Y003278D02*
|
||||
X006685Y003278D01*
|
||||
X006670Y003120D02*
|
||||
X009558Y003120D01*
|
||||
X009558Y002961D02*
|
||||
X006575Y002961D01*
|
||||
X007403Y002137D02*
|
||||
X007245Y001979D01*
|
||||
X007160Y001773D01*
|
||||
X007160Y001551D01*
|
||||
X007245Y001345D01*
|
||||
X007403Y001187D01*
|
||||
X007609Y001102D01*
|
||||
X007831Y001102D01*
|
||||
X008037Y001187D01*
|
||||
X008195Y001345D01*
|
||||
X008280Y001551D01*
|
||||
X008280Y001773D01*
|
||||
X008195Y001979D01*
|
||||
X008037Y002137D01*
|
||||
X007831Y002222D01*
|
||||
X007609Y002222D01*
|
||||
X007403Y002137D01*
|
||||
X007479Y002169D02*
|
||||
X001820Y002169D01*
|
||||
X001820Y002327D02*
|
||||
X008740Y002327D01*
|
||||
X008740Y002169D02*
|
||||
X007961Y002169D01*
|
||||
X008164Y002010D02*
|
||||
X008792Y002010D01*
|
||||
X008907Y001852D02*
|
||||
X008248Y001852D01*
|
||||
X008280Y001693D02*
|
||||
X009042Y001693D01*
|
||||
X008862Y001535D02*
|
||||
X008273Y001535D01*
|
||||
X008208Y001376D02*
|
||||
X008787Y001376D01*
|
||||
X008770Y001218D02*
|
||||
X008067Y001218D01*
|
||||
X007373Y001218D02*
|
||||
X001820Y001218D01*
|
||||
X001789Y001059D02*
|
||||
X008803Y001059D01*
|
||||
X008796Y002486D02*
|
||||
X001764Y002486D01*
|
||||
X001642Y002644D02*
|
||||
X008918Y002644D01*
|
||||
X009558Y002803D02*
|
||||
X003578Y002803D01*
|
||||
X004780Y004142D02*
|
||||
X005230Y005692D01*
|
||||
X005498Y005973D02*
|
||||
X008807Y005973D01*
|
||||
X008741Y006131D02*
|
||||
X001819Y006131D01*
|
||||
X001753Y005973D02*
|
||||
X004962Y005973D01*
|
||||
X004860Y005814D02*
|
||||
X001616Y005814D01*
|
||||
X001630Y005656D02*
|
||||
X004842Y005656D01*
|
||||
X004871Y005497D02*
|
||||
X001759Y005497D01*
|
||||
X001820Y005339D02*
|
||||
X004825Y005339D01*
|
||||
X004779Y005180D02*
|
||||
X001820Y005180D01*
|
||||
X001773Y005022D02*
|
||||
X004733Y005022D01*
|
||||
X004687Y004863D02*
|
||||
X001665Y004863D01*
|
||||
X001574Y004705D02*
|
||||
X004641Y004705D01*
|
||||
X004595Y004546D02*
|
||||
X001739Y004546D01*
|
||||
X001804Y004388D02*
|
||||
X004477Y004388D01*
|
||||
X004780Y004142D02*
|
||||
X001280Y003242D01*
|
||||
X001604Y002803D02*
|
||||
X002131Y002803D01*
|
||||
X002059Y002961D02*
|
||||
X001748Y002961D01*
|
||||
X001968Y003120D02*
|
||||
X002062Y003120D01*
|
||||
X002105Y003754D02*
|
||||
X001512Y003754D01*
|
||||
X001820Y004229D02*
|
||||
X003954Y004229D01*
|
||||
X005099Y003912D02*
|
||||
X009558Y003912D01*
|
||||
X009558Y004071D02*
|
||||
X005168Y004071D01*
|
||||
X005164Y004229D02*
|
||||
X009558Y004229D01*
|
||||
X009558Y004388D02*
|
||||
X005153Y004388D01*
|
||||
X005199Y004546D02*
|
||||
X009558Y004546D01*
|
||||
X009558Y004705D02*
|
||||
X005245Y004705D01*
|
||||
X005291Y004863D02*
|
||||
X008938Y004863D01*
|
||||
X008948Y004853D02*
|
||||
X009013Y004806D01*
|
||||
X009084Y004769D01*
|
||||
X009161Y004745D01*
|
||||
X009240Y004732D01*
|
||||
X009262Y004732D01*
|
||||
X009262Y005224D01*
|
||||
X008770Y005224D01*
|
||||
X008770Y005202D01*
|
||||
X008783Y005123D01*
|
||||
X008807Y005046D01*
|
||||
X008844Y004975D01*
|
||||
X008891Y004910D01*
|
||||
X008948Y004853D01*
|
||||
X008820Y005022D02*
|
||||
X005337Y005022D01*
|
||||
X005383Y005180D02*
|
||||
X008773Y005180D01*
|
||||
X008779Y005339D02*
|
||||
X005429Y005339D01*
|
||||
X005569Y005497D02*
|
||||
X008838Y005497D01*
|
||||
X008981Y005656D02*
|
||||
X005618Y005656D01*
|
||||
X005600Y005814D02*
|
||||
X008944Y005814D01*
|
||||
X008740Y006290D02*
|
||||
X001820Y006290D01*
|
||||
X001779Y006448D02*
|
||||
X008781Y006448D01*
|
||||
X008881Y006607D02*
|
||||
X001679Y006607D01*
|
||||
X001462Y006765D02*
|
||||
X008115Y006765D01*
|
||||
X008040Y006924D02*
|
||||
X008005Y006924D01*
|
||||
X007755Y006924D02*
|
||||
X002531Y006924D01*
|
||||
X002553Y006931D02*
|
||||
X002628Y006969D01*
|
||||
X002695Y007018D01*
|
||||
X002754Y007077D01*
|
||||
X002803Y007144D01*
|
||||
X002841Y007219D01*
|
||||
X002867Y007298D01*
|
||||
X002880Y007380D01*
|
||||
X002880Y007402D01*
|
||||
X002370Y007402D01*
|
||||
X002370Y007442D01*
|
||||
X002330Y007442D01*
|
||||
X002330Y007402D01*
|
||||
X001820Y007402D01*
|
||||
X001820Y007380D01*
|
||||
X001833Y007298D01*
|
||||
X001859Y007219D01*
|
||||
X001897Y007144D01*
|
||||
X001946Y007077D01*
|
||||
X002005Y007018D01*
|
||||
X002072Y006969D01*
|
||||
X002147Y006931D01*
|
||||
X002226Y006905D01*
|
||||
X002308Y006892D01*
|
||||
X002330Y006892D01*
|
||||
X002330Y007402D01*
|
||||
X002370Y007402D01*
|
||||
X002370Y006892D01*
|
||||
X002392Y006892D01*
|
||||
X002474Y006905D01*
|
||||
X002553Y006931D01*
|
||||
X002370Y006924D02*
|
||||
X002330Y006924D01*
|
||||
X002169Y006924D02*
|
||||
X001679Y006924D01*
|
||||
X001764Y007082D02*
|
||||
X001942Y007082D01*
|
||||
X001852Y007241D02*
|
||||
X001298Y007241D01*
|
||||
X001298Y007260D02*
|
||||
X001790Y007260D01*
|
||||
X001790Y007282D01*
|
||||
X001777Y007361D01*
|
||||
X001753Y007438D01*
|
||||
X001716Y007509D01*
|
||||
X001669Y007574D01*
|
||||
X001612Y007631D01*
|
||||
X001547Y007678D01*
|
||||
X001476Y007715D01*
|
||||
X001399Y007739D01*
|
||||
X001320Y007752D01*
|
||||
X001298Y007752D01*
|
||||
X001298Y007260D01*
|
||||
X001298Y007399D02*
|
||||
X001262Y007399D01*
|
||||
X001262Y007558D02*
|
||||
X001298Y007558D01*
|
||||
X001298Y007716D02*
|
||||
X001262Y007716D01*
|
||||
X001306Y007875D02*
|
||||
X000990Y007875D01*
|
||||
X000990Y008033D02*
|
||||
X001070Y008033D01*
|
||||
X001088Y007716D02*
|
||||
X000990Y007716D01*
|
||||
X001472Y007716D02*
|
||||
X001908Y007716D01*
|
||||
X001837Y007558D02*
|
||||
X001681Y007558D01*
|
||||
X001765Y007399D02*
|
||||
X001820Y007399D01*
|
||||
X002330Y007399D02*
|
||||
X002370Y007399D01*
|
||||
X002370Y007442D02*
|
||||
X002880Y007442D01*
|
||||
X002880Y007464D01*
|
||||
X002867Y007546D01*
|
||||
X002841Y007625D01*
|
||||
X002803Y007700D01*
|
||||
X002754Y007767D01*
|
||||
X002695Y007826D01*
|
||||
X002628Y007875D01*
|
||||
X002553Y007913D01*
|
||||
X002474Y007939D01*
|
||||
X002392Y007952D01*
|
||||
X002370Y007952D01*
|
||||
X002370Y007442D01*
|
||||
X002370Y007558D02*
|
||||
X002330Y007558D01*
|
||||
X002330Y007716D02*
|
||||
X002370Y007716D01*
|
||||
X002370Y007875D02*
|
||||
X002330Y007875D01*
|
||||
X002490Y008033D02*
|
||||
X003969Y008033D01*
|
||||
X003945Y008090D02*
|
||||
X003995Y007970D01*
|
||||
X004088Y007877D01*
|
||||
X004208Y007827D01*
|
||||
X004339Y007827D01*
|
||||
X004459Y007877D01*
|
||||
X004552Y007970D01*
|
||||
X004602Y008090D01*
|
||||
X004602Y008221D01*
|
||||
X004552Y008341D01*
|
||||
X004459Y008434D01*
|
||||
X004339Y008484D01*
|
||||
X004208Y008484D01*
|
||||
X004088Y008434D01*
|
||||
X003995Y008341D01*
|
||||
X003945Y008221D01*
|
||||
X003945Y008090D01*
|
||||
X003945Y008192D02*
|
||||
X002596Y008192D01*
|
||||
X002702Y008350D02*
|
||||
X004004Y008350D01*
|
||||
X004095Y007875D02*
|
||||
X002629Y007875D01*
|
||||
X002792Y007716D02*
|
||||
X008112Y007716D01*
|
||||
X008306Y007875D02*
|
||||
X006027Y007875D01*
|
||||
X006034Y007877D02*
|
||||
X006127Y007970D01*
|
||||
X006177Y008090D01*
|
||||
X006177Y008221D01*
|
||||
X006127Y008341D01*
|
||||
X006034Y008434D01*
|
||||
X005914Y008484D01*
|
||||
X005783Y008484D01*
|
||||
X005663Y008434D01*
|
||||
X005570Y008341D01*
|
||||
X005520Y008221D01*
|
||||
X005520Y008090D01*
|
||||
X005570Y007970D01*
|
||||
X005663Y007877D01*
|
||||
X005783Y007827D01*
|
||||
X005914Y007827D01*
|
||||
X006034Y007877D01*
|
||||
X006153Y008033D02*
|
||||
X008070Y008033D01*
|
||||
X007964Y008192D02*
|
||||
X006177Y008192D01*
|
||||
X006118Y008350D02*
|
||||
X007858Y008350D01*
|
||||
X007823Y008509D02*
|
||||
X002737Y008509D01*
|
||||
X002769Y008667D02*
|
||||
X007791Y008667D01*
|
||||
X007793Y008826D02*
|
||||
X002767Y008826D01*
|
||||
X002736Y008984D02*
|
||||
X007824Y008984D01*
|
||||
X007864Y009143D02*
|
||||
X002696Y009143D01*
|
||||
X002590Y009301D02*
|
||||
X007970Y009301D01*
|
||||
X008082Y009460D02*
|
||||
X002478Y009460D01*
|
||||
X002863Y007558D02*
|
||||
X007597Y007558D01*
|
||||
X007504Y007399D02*
|
||||
X002880Y007399D01*
|
||||
X002848Y007241D02*
|
||||
X007492Y007241D01*
|
||||
X007547Y007082D02*
|
||||
X002758Y007082D01*
|
||||
X002370Y007082D02*
|
||||
X002330Y007082D01*
|
||||
X002330Y007241D02*
|
||||
X002370Y007241D01*
|
||||
X004453Y007875D02*
|
||||
X005669Y007875D01*
|
||||
X005544Y008033D02*
|
||||
X004578Y008033D01*
|
||||
X004602Y008192D02*
|
||||
X005520Y008192D01*
|
||||
X005579Y008350D02*
|
||||
X004543Y008350D01*
|
||||
X007880Y007292D02*
|
||||
X007930Y007242D01*
|
||||
X008480Y007242D01*
|
||||
X009082Y007082D02*
|
||||
X009115Y007082D01*
|
||||
X009115Y007207D02*
|
||||
X009115Y007207D01*
|
||||
X009115Y007241D02*
|
||||
X009558Y007241D01*
|
||||
X009490Y007277D02*
|
||||
X009490Y007581D01*
|
||||
X009478Y007626D01*
|
||||
X009454Y007668D01*
|
||||
X009421Y007701D01*
|
||||
X009379Y007725D01*
|
||||
X009334Y007737D01*
|
||||
X009115Y007737D01*
|
||||
X009115Y007277D01*
|
||||
X009115Y007277D01*
|
||||
X009490Y007277D01*
|
||||
X009490Y007399D02*
|
||||
X009558Y007399D01*
|
||||
X009558Y007558D02*
|
||||
X009490Y007558D01*
|
||||
X009558Y007716D02*
|
||||
X009395Y007716D01*
|
||||
X009254Y007875D02*
|
||||
X009558Y007875D01*
|
||||
X009558Y008033D02*
|
||||
X009490Y008033D01*
|
||||
X009115Y007716D02*
|
||||
X009045Y007716D01*
|
||||
X009045Y007558D02*
|
||||
X009115Y007558D01*
|
||||
X009115Y007399D02*
|
||||
X009085Y007399D01*
|
||||
X009490Y007082D02*
|
||||
X009558Y007082D01*
|
||||
X009558Y006924D02*
|
||||
X009490Y006924D01*
|
||||
X009428Y006765D02*
|
||||
X009558Y006765D01*
|
||||
X009115Y006765D02*
|
||||
X009045Y006765D01*
|
||||
X009045Y006924D02*
|
||||
X009115Y006924D01*
|
||||
X009262Y005180D02*
|
||||
X009298Y005180D01*
|
||||
X009298Y005022D02*
|
||||
X009262Y005022D01*
|
||||
X009262Y004863D02*
|
||||
X009298Y004863D01*
|
||||
X007276Y002010D02*
|
||||
X001768Y002010D01*
|
||||
X001653Y001852D02*
|
||||
X007192Y001852D01*
|
||||
X007160Y001693D02*
|
||||
X001593Y001693D01*
|
||||
X001743Y001535D02*
|
||||
X007167Y001535D01*
|
||||
X007232Y001376D02*
|
||||
X001809Y001376D01*
|
||||
M02*
|
90548
gerber/gerbmerge/1x1/proj1.GBO
Normal file
90548
gerber/gerbmerge/1x1/proj1.GBO
Normal file
File diff suppressed because it is too large
Load Diff
45
gerber/gerbmerge/1x1/proj1.GBS
Normal file
45
gerber/gerbmerge/1x1/proj1.GBS
Normal file
|
@ -0,0 +1,45 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.1306*%
|
||||
%ADD11C,0.0276*%
|
||||
%ADD12C,0.0700*%
|
||||
%ADD13R,0.0500X0.0670*%
|
||||
%ADD14R,0.0290X0.0120*%
|
||||
%ADD15R,0.0060X0.0720*%
|
||||
%ADD16C,0.0740*%
|
||||
D10*
|
||||
X001780Y008742D03*
|
||||
X008780Y008742D03*
|
||||
D11*
|
||||
X005848Y008156D03*
|
||||
X004274Y008156D03*
|
||||
D12*
|
||||
X001280Y001242D03*
|
||||
X001280Y002242D03*
|
||||
X001280Y003242D03*
|
||||
X001280Y004242D03*
|
||||
X001280Y005242D03*
|
||||
X001280Y006242D03*
|
||||
X001280Y007242D03*
|
||||
X009280Y006242D03*
|
||||
X009280Y005242D03*
|
||||
X009280Y002242D03*
|
||||
X009280Y001242D03*
|
||||
D13*
|
||||
X009080Y007242D03*
|
||||
X008480Y007242D03*
|
||||
D14*
|
||||
X008780Y007242D03*
|
||||
D15*
|
||||
X008780Y007242D03*
|
||||
D16*
|
||||
X007720Y001662D03*
|
||||
X002350Y007422D03*
|
||||
M02*
|
89
gerber/gerbmerge/1x1/proj1.GKO
Normal file
89
gerber/gerbmerge/1x1/proj1.GKO
Normal file
|
@ -0,0 +1,89 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.0000*%
|
||||
D10*
|
||||
X000780Y000742D02*
|
||||
X000780Y009742D01*
|
||||
X009768Y009742D01*
|
||||
X009768Y000742D01*
|
||||
X000780Y000742D01*
|
||||
X004156Y008156D02*
|
||||
X004158Y008177D01*
|
||||
X004164Y008197D01*
|
||||
X004173Y008217D01*
|
||||
X004185Y008234D01*
|
||||
X004200Y008248D01*
|
||||
X004218Y008260D01*
|
||||
X004238Y008268D01*
|
||||
X004258Y008273D01*
|
||||
X004279Y008274D01*
|
||||
X004300Y008271D01*
|
||||
X004320Y008265D01*
|
||||
X004339Y008254D01*
|
||||
X004356Y008241D01*
|
||||
X004369Y008225D01*
|
||||
X004380Y008207D01*
|
||||
X004388Y008187D01*
|
||||
X004392Y008167D01*
|
||||
X004392Y008145D01*
|
||||
X004388Y008125D01*
|
||||
X004380Y008105D01*
|
||||
X004369Y008087D01*
|
||||
X004356Y008071D01*
|
||||
X004339Y008058D01*
|
||||
X004320Y008047D01*
|
||||
X004300Y008041D01*
|
||||
X004279Y008038D01*
|
||||
X004258Y008039D01*
|
||||
X004238Y008044D01*
|
||||
X004218Y008052D01*
|
||||
X004200Y008064D01*
|
||||
X004185Y008078D01*
|
||||
X004173Y008095D01*
|
||||
X004164Y008115D01*
|
||||
X004158Y008135D01*
|
||||
X004156Y008156D01*
|
||||
X005730Y008156D02*
|
||||
X005732Y008177D01*
|
||||
X005738Y008197D01*
|
||||
X005747Y008217D01*
|
||||
X005759Y008234D01*
|
||||
X005774Y008248D01*
|
||||
X005792Y008260D01*
|
||||
X005812Y008268D01*
|
||||
X005832Y008273D01*
|
||||
X005853Y008274D01*
|
||||
X005874Y008271D01*
|
||||
X005894Y008265D01*
|
||||
X005913Y008254D01*
|
||||
X005930Y008241D01*
|
||||
X005943Y008225D01*
|
||||
X005954Y008207D01*
|
||||
X005962Y008187D01*
|
||||
X005966Y008167D01*
|
||||
X005966Y008145D01*
|
||||
X005962Y008125D01*
|
||||
X005954Y008105D01*
|
||||
X005943Y008087D01*
|
||||
X005930Y008071D01*
|
||||
X005913Y008058D01*
|
||||
X005894Y008047D01*
|
||||
X005874Y008041D01*
|
||||
X005853Y008038D01*
|
||||
X005832Y008039D01*
|
||||
X005812Y008044D01*
|
||||
X005792Y008052D01*
|
||||
X005774Y008064D01*
|
||||
X005759Y008078D01*
|
||||
X005747Y008095D01*
|
||||
X005738Y008115D01*
|
||||
X005732Y008135D01*
|
||||
X005730Y008156D01*
|
||||
M02*
|
89
gerber/gerbmerge/1x1/proj1.GML
Normal file
89
gerber/gerbmerge/1x1/proj1.GML
Normal file
|
@ -0,0 +1,89 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.0000*%
|
||||
D10*
|
||||
X000780Y000742D02*
|
||||
X000780Y009742D01*
|
||||
X009768Y009742D01*
|
||||
X009768Y000742D01*
|
||||
X000780Y000742D01*
|
||||
X004156Y008156D02*
|
||||
X004158Y008177D01*
|
||||
X004164Y008197D01*
|
||||
X004173Y008217D01*
|
||||
X004185Y008234D01*
|
||||
X004200Y008248D01*
|
||||
X004218Y008260D01*
|
||||
X004238Y008268D01*
|
||||
X004258Y008273D01*
|
||||
X004279Y008274D01*
|
||||
X004300Y008271D01*
|
||||
X004320Y008265D01*
|
||||
X004339Y008254D01*
|
||||
X004356Y008241D01*
|
||||
X004369Y008225D01*
|
||||
X004380Y008207D01*
|
||||
X004388Y008187D01*
|
||||
X004392Y008167D01*
|
||||
X004392Y008145D01*
|
||||
X004388Y008125D01*
|
||||
X004380Y008105D01*
|
||||
X004369Y008087D01*
|
||||
X004356Y008071D01*
|
||||
X004339Y008058D01*
|
||||
X004320Y008047D01*
|
||||
X004300Y008041D01*
|
||||
X004279Y008038D01*
|
||||
X004258Y008039D01*
|
||||
X004238Y008044D01*
|
||||
X004218Y008052D01*
|
||||
X004200Y008064D01*
|
||||
X004185Y008078D01*
|
||||
X004173Y008095D01*
|
||||
X004164Y008115D01*
|
||||
X004158Y008135D01*
|
||||
X004156Y008156D01*
|
||||
X005730Y008156D02*
|
||||
X005732Y008177D01*
|
||||
X005738Y008197D01*
|
||||
X005747Y008217D01*
|
||||
X005759Y008234D01*
|
||||
X005774Y008248D01*
|
||||
X005792Y008260D01*
|
||||
X005812Y008268D01*
|
||||
X005832Y008273D01*
|
||||
X005853Y008274D01*
|
||||
X005874Y008271D01*
|
||||
X005894Y008265D01*
|
||||
X005913Y008254D01*
|
||||
X005930Y008241D01*
|
||||
X005943Y008225D01*
|
||||
X005954Y008207D01*
|
||||
X005962Y008187D01*
|
||||
X005966Y008167D01*
|
||||
X005966Y008145D01*
|
||||
X005962Y008125D01*
|
||||
X005954Y008105D01*
|
||||
X005943Y008087D01*
|
||||
X005930Y008071D01*
|
||||
X005913Y008058D01*
|
||||
X005894Y008047D01*
|
||||
X005874Y008041D01*
|
||||
X005853Y008038D01*
|
||||
X005832Y008039D01*
|
||||
X005812Y008044D01*
|
||||
X005792Y008052D01*
|
||||
X005774Y008064D01*
|
||||
X005759Y008078D01*
|
||||
X005747Y008095D01*
|
||||
X005738Y008115D01*
|
||||
X005732Y008135D01*
|
||||
X005730Y008156D01*
|
||||
M02*
|
1477
gerber/gerbmerge/1x1/proj1.GTL
Normal file
1477
gerber/gerbmerge/1x1/proj1.GTL
Normal file
File diff suppressed because it is too large
Load Diff
985
gerber/gerbmerge/1x1/proj1.GTO
Normal file
985
gerber/gerbmerge/1x1/proj1.GTO
Normal file
|
@ -0,0 +1,985 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.0030*%
|
||||
%ADD11C,0.0050*%
|
||||
%ADD12C,0.0040*%
|
||||
%ADD13R,0.0280X0.0050*%
|
||||
%ADD14C,0.0100*%
|
||||
%ADD15R,0.0050X0.0280*%
|
||||
%ADD16C,0.0060*%
|
||||
%ADD17C,0.0160*%
|
||||
%ADD18C,0.0080*%
|
||||
%ADD19R,0.0394X0.0079*%
|
||||
%ADD20R,0.0079X0.0079*%
|
||||
%ADD21C,0.0000*%
|
||||
%ADD22R,0.0079X0.0394*%
|
||||
D10*
|
||||
X001824Y001146D02*
|
||||
X001917Y001306D01*
|
||||
X002001Y001329D01*
|
||||
X002108Y001267D01*
|
||||
X002131Y001183D01*
|
||||
X002038Y001023D01*
|
||||
X002145Y000961D02*
|
||||
X001824Y001146D01*
|
||||
X002009Y001465D02*
|
||||
X002101Y001625D01*
|
||||
X002185Y001648D01*
|
||||
X002239Y001617D01*
|
||||
X002261Y001533D01*
|
||||
X002169Y001372D01*
|
||||
X002329Y001280D02*
|
||||
X002422Y001440D01*
|
||||
X002399Y001525D01*
|
||||
X002346Y001555D01*
|
||||
X002261Y001533D01*
|
||||
X002460Y001630D02*
|
||||
X002544Y001652D01*
|
||||
X002606Y001759D01*
|
||||
X002583Y001844D01*
|
||||
X002476Y001905D01*
|
||||
X002392Y001883D01*
|
||||
X002361Y001829D01*
|
||||
X002353Y001691D01*
|
||||
X002193Y001784D01*
|
||||
X002316Y001998D01*
|
||||
X002157Y002073D02*
|
||||
X002064Y001913D01*
|
||||
X002171Y001851D02*
|
||||
X001850Y002036D01*
|
||||
X001943Y002197D01*
|
||||
X002027Y002219D01*
|
||||
X002134Y002158D01*
|
||||
X002157Y002073D01*
|
||||
X002355Y002170D02*
|
||||
X002448Y002331D01*
|
||||
X002425Y002415D01*
|
||||
X002372Y002446D01*
|
||||
X002287Y002423D01*
|
||||
X002195Y002263D01*
|
||||
X002287Y002423D02*
|
||||
X002265Y002507D01*
|
||||
X002211Y002538D01*
|
||||
X002127Y002516D01*
|
||||
X002034Y002355D01*
|
||||
X002355Y002170D01*
|
||||
X002379Y002582D02*
|
||||
X002502Y002796D01*
|
||||
X002311Y002835D02*
|
||||
X002379Y002582D01*
|
||||
X002632Y002650D02*
|
||||
X002311Y002835D01*
|
||||
X002128Y002788D02*
|
||||
X001808Y002973D01*
|
||||
X001900Y003133D01*
|
||||
X001985Y003156D01*
|
||||
X002092Y003094D01*
|
||||
X002114Y003010D01*
|
||||
X002022Y002850D01*
|
||||
X002313Y003107D02*
|
||||
X002405Y003267D01*
|
||||
X002383Y003351D01*
|
||||
X002329Y003382D01*
|
||||
X002245Y003360D01*
|
||||
X002152Y003199D01*
|
||||
X001992Y003292D02*
|
||||
X002084Y003452D01*
|
||||
X002169Y003475D01*
|
||||
X002222Y003444D01*
|
||||
X002245Y003360D01*
|
||||
X002443Y003457D02*
|
||||
X002528Y003479D01*
|
||||
X002589Y003586D01*
|
||||
X002567Y003670D01*
|
||||
X002513Y003701D01*
|
||||
X002429Y003679D01*
|
||||
X002398Y003625D01*
|
||||
X002429Y003679D02*
|
||||
X002406Y003763D01*
|
||||
X002353Y003794D01*
|
||||
X002269Y003771D01*
|
||||
X002207Y003664D01*
|
||||
X002230Y003580D01*
|
||||
X002094Y003783D02*
|
||||
X001773Y003968D01*
|
||||
X001866Y004129D01*
|
||||
X001950Y004151D01*
|
||||
X002057Y004090D01*
|
||||
X002080Y004005D01*
|
||||
X001987Y003845D01*
|
||||
X002278Y004102D02*
|
||||
X002371Y004263D01*
|
||||
X002348Y004347D01*
|
||||
X002295Y004378D01*
|
||||
X002211Y004355D01*
|
||||
X002118Y004195D01*
|
||||
X002211Y004355D02*
|
||||
X002188Y004440D01*
|
||||
X002134Y004470D01*
|
||||
X002050Y004448D01*
|
||||
X001958Y004287D01*
|
||||
X002278Y004102D01*
|
||||
X002462Y004421D02*
|
||||
X002372Y004758D01*
|
||||
X002319Y004789D01*
|
||||
X002234Y004767D01*
|
||||
X002173Y004660D01*
|
||||
X002195Y004576D01*
|
||||
X002089Y004755D02*
|
||||
X001768Y004940D01*
|
||||
X001860Y005101D01*
|
||||
X001945Y005123D01*
|
||||
X002052Y005061D01*
|
||||
X002074Y004977D01*
|
||||
X001982Y004817D01*
|
||||
X002112Y005167D02*
|
||||
X002205Y005327D01*
|
||||
X002289Y005350D01*
|
||||
X002343Y005319D01*
|
||||
X002365Y005234D01*
|
||||
X002273Y005074D01*
|
||||
X001952Y005259D01*
|
||||
X002045Y005420D01*
|
||||
X002129Y005442D01*
|
||||
X002182Y005411D01*
|
||||
X002205Y005327D01*
|
||||
X002243Y005516D02*
|
||||
X002198Y005685D01*
|
||||
X002519Y005500D01*
|
||||
X002457Y005393D02*
|
||||
X002580Y005607D01*
|
||||
X002146Y005821D02*
|
||||
X001825Y006006D01*
|
||||
X001918Y006167D01*
|
||||
X002002Y006189D01*
|
||||
X002109Y006127D01*
|
||||
X002131Y006043D01*
|
||||
X002039Y005883D01*
|
||||
X002169Y006233D02*
|
||||
X002262Y006393D01*
|
||||
X002346Y006416D01*
|
||||
X002400Y006385D01*
|
||||
X002422Y006300D01*
|
||||
X002330Y006140D01*
|
||||
X002009Y006325D01*
|
||||
X002102Y006486D01*
|
||||
X002186Y006508D01*
|
||||
X002239Y006477D01*
|
||||
X002262Y006393D01*
|
||||
X002247Y006613D02*
|
||||
X002461Y006490D01*
|
||||
X002370Y006827D01*
|
||||
X002584Y006704D01*
|
||||
X002607Y006619D01*
|
||||
X002545Y006512D01*
|
||||
X002461Y006490D01*
|
||||
X002247Y006613D02*
|
||||
X002224Y006698D01*
|
||||
X002286Y006805D01*
|
||||
X002370Y006827D01*
|
||||
X002815Y007222D02*
|
||||
X002815Y007512D01*
|
||||
X002960Y007512D01*
|
||||
X003008Y007464D01*
|
||||
X003008Y007367D01*
|
||||
X002960Y007319D01*
|
||||
X002815Y007319D01*
|
||||
X002912Y007319D02*
|
||||
X003008Y007222D01*
|
||||
X003110Y007367D02*
|
||||
X003303Y007367D01*
|
||||
X003255Y007222D02*
|
||||
X003255Y007512D01*
|
||||
X003110Y007367D01*
|
||||
X003505Y007257D02*
|
||||
X003650Y007257D01*
|
||||
X003698Y007305D01*
|
||||
X003698Y007499D01*
|
||||
X003650Y007547D01*
|
||||
X003505Y007547D01*
|
||||
X003505Y007257D01*
|
||||
X003800Y007257D02*
|
||||
X003993Y007257D01*
|
||||
X003896Y007257D02*
|
||||
X003896Y007547D01*
|
||||
X003800Y007450D01*
|
||||
X004395Y007537D02*
|
||||
X004395Y007247D01*
|
||||
X004540Y007247D01*
|
||||
X004588Y007295D01*
|
||||
X004588Y007489D01*
|
||||
X004540Y007537D01*
|
||||
X004395Y007537D01*
|
||||
X004690Y007489D02*
|
||||
X004738Y007537D01*
|
||||
X004835Y007537D01*
|
||||
X004883Y007489D01*
|
||||
X004883Y007440D01*
|
||||
X004690Y007247D01*
|
||||
X004883Y007247D01*
|
||||
X005170Y007207D02*
|
||||
X005170Y007497D01*
|
||||
X005315Y007497D01*
|
||||
X005363Y007449D01*
|
||||
X005363Y007352D01*
|
||||
X005315Y007304D01*
|
||||
X005170Y007304D01*
|
||||
X005267Y007304D02*
|
||||
X005363Y007207D01*
|
||||
X005465Y007255D02*
|
||||
X005513Y007207D01*
|
||||
X005610Y007207D01*
|
||||
X005658Y007255D01*
|
||||
X005658Y007304D01*
|
||||
X005610Y007352D01*
|
||||
X005561Y007352D01*
|
||||
X005610Y007352D02*
|
||||
X005658Y007400D01*
|
||||
X005658Y007449D01*
|
||||
X005610Y007497D01*
|
||||
X005513Y007497D01*
|
||||
X005465Y007449D01*
|
||||
X006020Y007447D02*
|
||||
X006020Y007157D01*
|
||||
X006020Y007254D02*
|
||||
X006165Y007254D01*
|
||||
X006213Y007302D01*
|
||||
X006213Y007399D01*
|
||||
X006165Y007447D01*
|
||||
X006020Y007447D01*
|
||||
X006117Y007254D02*
|
||||
X006213Y007157D01*
|
||||
X006315Y007157D02*
|
||||
X006508Y007157D01*
|
||||
X006411Y007157D02*
|
||||
X006411Y007447D01*
|
||||
X006315Y007350D01*
|
||||
X007583Y006942D02*
|
||||
X007583Y006652D01*
|
||||
X007583Y006749D02*
|
||||
X007728Y006749D01*
|
||||
X007776Y006797D01*
|
||||
X007776Y006894D01*
|
||||
X007728Y006942D01*
|
||||
X007583Y006942D01*
|
||||
X007680Y006749D02*
|
||||
X007776Y006652D01*
|
||||
X007878Y006652D02*
|
||||
X008071Y006845D01*
|
||||
X008071Y006894D01*
|
||||
X008023Y006942D01*
|
||||
X007926Y006942D01*
|
||||
X007878Y006894D01*
|
||||
X007878Y006652D02*
|
||||
X008071Y006652D01*
|
||||
X008392Y006617D02*
|
||||
X008732Y006617D01*
|
||||
X008732Y006390D02*
|
||||
X008505Y006390D01*
|
||||
X008392Y006277D01*
|
||||
X008505Y006163D01*
|
||||
X008732Y006163D01*
|
||||
X008675Y006049D02*
|
||||
X008732Y005993D01*
|
||||
X008732Y005823D01*
|
||||
X008392Y005823D01*
|
||||
X008392Y005993D01*
|
||||
X008449Y006049D01*
|
||||
X008505Y006049D01*
|
||||
X008562Y005993D01*
|
||||
X008562Y005823D01*
|
||||
X008562Y005993D02*
|
||||
X008619Y006049D01*
|
||||
X008675Y006049D01*
|
||||
X008562Y006163D02*
|
||||
X008562Y006390D01*
|
||||
X008392Y006504D02*
|
||||
X008392Y006731D01*
|
||||
X009135Y007568D02*
|
||||
X009280Y007568D01*
|
||||
X009329Y007617D01*
|
||||
X009329Y007810D01*
|
||||
X009280Y007858D01*
|
||||
X009135Y007858D01*
|
||||
X009135Y007568D01*
|
||||
X009430Y007713D02*
|
||||
X009623Y007713D01*
|
||||
X009575Y007568D02*
|
||||
X009575Y007858D01*
|
||||
X009430Y007713D01*
|
||||
X006649Y005246D02*
|
||||
X006649Y004956D01*
|
||||
X006552Y004956D02*
|
||||
X006746Y004956D01*
|
||||
X006552Y005149D02*
|
||||
X006649Y005246D01*
|
||||
X006451Y005198D02*
|
||||
X006402Y005246D01*
|
||||
X006306Y005246D01*
|
||||
X006257Y005198D01*
|
||||
X006257Y005004D01*
|
||||
X006306Y004956D01*
|
||||
X006402Y004956D01*
|
||||
X006451Y005004D01*
|
||||
X006443Y004288D02*
|
||||
X006443Y004046D01*
|
||||
X006395Y003998D01*
|
||||
X006298Y003998D01*
|
||||
X006250Y004046D01*
|
||||
X006250Y004288D01*
|
||||
X006545Y004191D02*
|
||||
X006641Y004288D01*
|
||||
X006641Y003998D01*
|
||||
X006545Y003998D02*
|
||||
X006738Y003998D01*
|
||||
X008287Y002405D02*
|
||||
X008199Y002317D01*
|
||||
X008199Y002230D01*
|
||||
X008374Y002056D01*
|
||||
X008461Y002056D01*
|
||||
X008548Y002143D01*
|
||||
X008548Y002230D01*
|
||||
X008634Y002316D02*
|
||||
X008722Y002316D01*
|
||||
X008809Y002403D01*
|
||||
X008809Y002491D01*
|
||||
X008634Y002665D02*
|
||||
X008547Y002665D01*
|
||||
X008460Y002578D01*
|
||||
X008460Y002491D01*
|
||||
X008634Y002316D01*
|
||||
X008374Y002405D02*
|
||||
X008287Y002405D01*
|
||||
X008070Y002188D02*
|
||||
X008244Y002013D01*
|
||||
X008244Y001839D01*
|
||||
X008070Y001839D01*
|
||||
X007895Y002013D01*
|
||||
X004434Y002755D02*
|
||||
X004241Y002755D01*
|
||||
X004434Y002948D01*
|
||||
X004434Y002997D01*
|
||||
X004386Y003045D01*
|
||||
X004289Y003045D01*
|
||||
X004241Y002997D01*
|
||||
X004139Y002997D02*
|
||||
X004091Y003045D01*
|
||||
X003994Y003045D01*
|
||||
X003946Y002997D01*
|
||||
X003946Y002803D01*
|
||||
X003994Y002755D01*
|
||||
X004091Y002755D01*
|
||||
X004139Y002803D01*
|
||||
X002462Y004421D02*
|
||||
X002586Y004635D01*
|
||||
X003177Y005488D02*
|
||||
X003274Y005488D01*
|
||||
X003322Y005536D01*
|
||||
X003322Y005778D01*
|
||||
X003424Y005730D02*
|
||||
X003472Y005778D01*
|
||||
X003569Y005778D01*
|
||||
X003617Y005730D01*
|
||||
X003617Y005681D01*
|
||||
X003424Y005488D01*
|
||||
X003617Y005488D01*
|
||||
X003177Y005488D02*
|
||||
X003129Y005536D01*
|
||||
X003129Y005778D01*
|
||||
X001992Y003292D02*
|
||||
X002313Y003107D01*
|
||||
X002009Y001465D02*
|
||||
X002329Y001280D01*
|
||||
D11*
|
||||
X004009Y001019D02*
|
||||
X004009Y000861D01*
|
||||
X004010Y000844D01*
|
||||
X004015Y000827D01*
|
||||
X004022Y000812D01*
|
||||
X004032Y000798D01*
|
||||
X004044Y000786D01*
|
||||
X004058Y000776D01*
|
||||
X004073Y000769D01*
|
||||
X004090Y000764D01*
|
||||
X004107Y000763D01*
|
||||
X006273Y000763D01*
|
||||
X006290Y000764D01*
|
||||
X006307Y000769D01*
|
||||
X006322Y000776D01*
|
||||
X006336Y000786D01*
|
||||
X006348Y000798D01*
|
||||
X006358Y000812D01*
|
||||
X006365Y000827D01*
|
||||
X006370Y000844D01*
|
||||
X006371Y000861D01*
|
||||
X006371Y001019D01*
|
||||
X005781Y001157D02*
|
||||
X005781Y001747D01*
|
||||
X004599Y001747D01*
|
||||
X004599Y001157D01*
|
||||
X005781Y001157D01*
|
||||
X006371Y001885D02*
|
||||
X006371Y002043D01*
|
||||
X006370Y002060D01*
|
||||
X006365Y002077D01*
|
||||
X006358Y002092D01*
|
||||
X006348Y002106D01*
|
||||
X006336Y002118D01*
|
||||
X006322Y002128D01*
|
||||
X006307Y002135D01*
|
||||
X006290Y002140D01*
|
||||
X006273Y002141D01*
|
||||
X004147Y002141D01*
|
||||
X004126Y002143D01*
|
||||
X004105Y002141D01*
|
||||
X004085Y002135D01*
|
||||
X004066Y002126D01*
|
||||
X004049Y002114D01*
|
||||
X004034Y002099D01*
|
||||
X004023Y002082D01*
|
||||
X004014Y002063D01*
|
||||
X004009Y002043D01*
|
||||
X004009Y001885D01*
|
||||
X005980Y002242D02*
|
||||
X005980Y002392D01*
|
||||
X005980Y003092D02*
|
||||
X005980Y003242D01*
|
||||
X007780Y003242D02*
|
||||
X007780Y003092D01*
|
||||
X007780Y002392D02*
|
||||
X007780Y002242D01*
|
||||
X008482Y001543D02*
|
||||
X008482Y001243D01*
|
||||
X008732Y001243D01*
|
||||
X008632Y001123D02*
|
||||
X008332Y001123D01*
|
||||
X008432Y001002D02*
|
||||
X008532Y001002D01*
|
||||
X008482Y001243D02*
|
||||
X008232Y001243D01*
|
||||
X008524Y005058D02*
|
||||
X008624Y005058D01*
|
||||
X008725Y005179D02*
|
||||
X008425Y005179D01*
|
||||
X008325Y005299D02*
|
||||
X008575Y005299D01*
|
||||
X008575Y005499D01*
|
||||
X008575Y005299D02*
|
||||
X008825Y005299D01*
|
||||
X007631Y005903D02*
|
||||
X007481Y005903D01*
|
||||
X006781Y005903D02*
|
||||
X006631Y005903D01*
|
||||
X006631Y007703D02*
|
||||
X006781Y007703D01*
|
||||
X007481Y007703D02*
|
||||
X007631Y007703D01*
|
||||
X002268Y007064D02*
|
||||
X002018Y007064D01*
|
||||
X002018Y007364D01*
|
||||
X002018Y007064D02*
|
||||
X001768Y007064D01*
|
||||
X001868Y006944D02*
|
||||
X002168Y006944D01*
|
||||
X002067Y006823D02*
|
||||
X001967Y006823D01*
|
||||
D12*
|
||||
X003939Y002961D02*
|
||||
X003978Y002905D01*
|
||||
X004057Y002793D02*
|
||||
X004213Y002568D01*
|
||||
X004157Y002529D02*
|
||||
X004269Y002608D01*
|
||||
X004042Y002449D02*
|
||||
X003924Y002617D01*
|
||||
X003829Y002633D01*
|
||||
X003812Y002538D01*
|
||||
X003930Y002370D01*
|
||||
X003818Y002292D02*
|
||||
X003661Y002516D01*
|
||||
X003717Y002555D01*
|
||||
X003812Y002538D01*
|
||||
X003624Y002323D02*
|
||||
X003456Y002205D01*
|
||||
X003439Y002110D01*
|
||||
X003535Y002093D01*
|
||||
X003703Y002211D01*
|
||||
X003585Y002379D01*
|
||||
X003490Y002396D01*
|
||||
X003378Y002317D01*
|
||||
X003207Y002198D02*
|
||||
X003095Y002120D01*
|
||||
X003112Y002215D02*
|
||||
X003269Y001991D01*
|
||||
X003364Y001974D01*
|
||||
X003025Y001736D02*
|
||||
X002790Y002072D01*
|
||||
X002678Y001994D02*
|
||||
X002902Y002151D01*
|
||||
X002641Y001801D02*
|
||||
X002798Y001577D01*
|
||||
X002680Y001745D02*
|
||||
X002456Y001588D01*
|
||||
X002417Y001644D02*
|
||||
X002450Y001835D01*
|
||||
X002641Y001801D01*
|
||||
X002417Y001644D02*
|
||||
X002573Y001420D01*
|
||||
X004001Y002753D02*
|
||||
X004057Y002793D01*
|
||||
D13*
|
||||
X003790Y006387D03*
|
||||
X004580Y006377D03*
|
||||
D14*
|
||||
X001680Y000842D02*
|
||||
X000880Y000842D01*
|
||||
X000880Y006742D01*
|
||||
X001680Y006742D01*
|
||||
X001680Y007742D01*
|
||||
X000880Y007742D01*
|
||||
X000880Y006742D01*
|
||||
X001680Y006742D02*
|
||||
X001680Y000842D01*
|
||||
X003790Y006412D02*
|
||||
X003700Y006582D01*
|
||||
X003880Y006582D01*
|
||||
X003790Y006412D01*
|
||||
X003767Y006455D02*
|
||||
X003813Y006455D01*
|
||||
X003865Y006554D02*
|
||||
X003715Y006554D01*
|
||||
X004490Y006582D02*
|
||||
X004580Y006412D01*
|
||||
X004670Y006582D01*
|
||||
X004490Y006582D01*
|
||||
X004505Y006554D02*
|
||||
X004655Y006554D01*
|
||||
X004603Y006455D02*
|
||||
X004557Y006455D01*
|
||||
X003289Y009691D02*
|
||||
X006833Y009691D01*
|
||||
X008890Y007262D02*
|
||||
X009060Y007352D01*
|
||||
X009060Y007172D01*
|
||||
X008890Y007262D01*
|
||||
X008926Y007243D02*
|
||||
X009060Y007243D01*
|
||||
X009060Y007342D02*
|
||||
X009040Y007342D01*
|
||||
X008980Y006742D02*
|
||||
X008880Y006642D01*
|
||||
X008880Y005742D01*
|
||||
X008880Y004742D01*
|
||||
X009680Y004742D01*
|
||||
X009680Y005742D01*
|
||||
X008880Y005742D01*
|
||||
X009680Y005742D02*
|
||||
X009680Y006742D01*
|
||||
X009280Y006742D01*
|
||||
X008980Y002742D02*
|
||||
X009680Y002742D01*
|
||||
X009680Y001742D01*
|
||||
X008880Y001742D01*
|
||||
X008880Y002642D01*
|
||||
X008980Y002742D01*
|
||||
X008880Y001742D02*
|
||||
X008880Y000942D01*
|
||||
X008980Y000842D01*
|
||||
X009680Y000842D01*
|
||||
X009680Y001742D01*
|
||||
D15*
|
||||
X008845Y007262D03*
|
||||
D16*
|
||||
X008179Y006205D02*
|
||||
X008179Y005969D01*
|
||||
X007707Y005969D02*
|
||||
X007707Y006205D01*
|
||||
X007621Y005906D02*
|
||||
X007621Y007706D01*
|
||||
X007403Y008292D02*
|
||||
X007316Y008292D01*
|
||||
X007272Y008336D01*
|
||||
X007151Y008336D02*
|
||||
X007151Y008509D01*
|
||||
X007108Y008553D01*
|
||||
X006978Y008553D01*
|
||||
X006978Y008292D01*
|
||||
X007108Y008292D01*
|
||||
X007151Y008336D01*
|
||||
X007272Y008509D02*
|
||||
X007316Y008553D01*
|
||||
X007403Y008553D01*
|
||||
X007446Y008509D01*
|
||||
X007446Y008466D01*
|
||||
X007403Y008422D01*
|
||||
X007446Y008379D01*
|
||||
X007446Y008336D01*
|
||||
X007403Y008292D01*
|
||||
X007403Y008422D02*
|
||||
X007359Y008422D01*
|
||||
X006621Y007706D02*
|
||||
X006621Y005906D01*
|
||||
X006416Y006424D02*
|
||||
X006416Y006660D01*
|
||||
X005944Y006660D02*
|
||||
X005944Y006424D01*
|
||||
X005616Y006424D02*
|
||||
X005616Y006660D01*
|
||||
X005144Y006660D02*
|
||||
X005144Y006424D01*
|
||||
X005274Y005303D02*
|
||||
X003264Y005303D01*
|
||||
X003216Y006424D02*
|
||||
X003216Y006660D01*
|
||||
X002744Y006660D02*
|
||||
X002744Y006424D01*
|
||||
X003264Y003443D02*
|
||||
X005274Y003443D01*
|
||||
X005222Y002876D02*
|
||||
X005222Y002746D01*
|
||||
X005308Y002789D01*
|
||||
X005352Y002789D01*
|
||||
X005395Y002746D01*
|
||||
X005395Y002659D01*
|
||||
X005352Y002616D01*
|
||||
X005265Y002616D01*
|
||||
X005222Y002659D01*
|
||||
X005100Y002659D02*
|
||||
X005057Y002616D01*
|
||||
X004927Y002616D01*
|
||||
X004927Y002876D01*
|
||||
X005057Y002876D01*
|
||||
X005100Y002833D01*
|
||||
X005100Y002659D01*
|
||||
X005222Y002876D02*
|
||||
X005395Y002876D01*
|
||||
X005983Y003252D02*
|
||||
X007783Y003252D01*
|
||||
X008330Y003442D02*
|
||||
X008330Y003042D01*
|
||||
X008332Y003025D01*
|
||||
X008336Y003008D01*
|
||||
X008343Y002992D01*
|
||||
X008353Y002978D01*
|
||||
X008366Y002965D01*
|
||||
X008380Y002955D01*
|
||||
X008396Y002948D01*
|
||||
X008413Y002944D01*
|
||||
X008430Y002942D01*
|
||||
X009530Y002942D01*
|
||||
X009547Y002944D01*
|
||||
X009564Y002948D01*
|
||||
X009580Y002955D01*
|
||||
X009594Y002965D01*
|
||||
X009607Y002978D01*
|
||||
X009617Y002992D01*
|
||||
X009624Y003008D01*
|
||||
X009628Y003025D01*
|
||||
X009630Y003042D01*
|
||||
X009630Y003442D01*
|
||||
X009030Y003442D01*
|
||||
X009030Y003242D01*
|
||||
X009130Y003642D02*
|
||||
X008830Y003842D01*
|
||||
X009630Y004042D01*
|
||||
X009630Y004192D02*
|
||||
X009130Y004492D01*
|
||||
X009130Y004192D02*
|
||||
X009630Y004592D01*
|
||||
X008830Y003842D02*
|
||||
X008530Y004042D01*
|
||||
X008330Y003642D02*
|
||||
X009130Y003642D01*
|
||||
X009630Y003642D01*
|
||||
X007783Y002252D02*
|
||||
X005983Y002252D01*
|
||||
X003437Y002620D02*
|
||||
X003201Y002620D01*
|
||||
X003201Y003092D02*
|
||||
X003437Y003092D01*
|
||||
X007274Y004879D02*
|
||||
X007510Y004879D01*
|
||||
X007510Y005352D02*
|
||||
X007274Y005352D01*
|
||||
D17*
|
||||
X004888Y003412D02*
|
||||
X004890Y003433D01*
|
||||
X004896Y003452D01*
|
||||
X004905Y003471D01*
|
||||
X004917Y003487D01*
|
||||
X004933Y003501D01*
|
||||
X004950Y003512D01*
|
||||
X004969Y003520D01*
|
||||
X004990Y003524D01*
|
||||
X005010Y003524D01*
|
||||
X005031Y003520D01*
|
||||
X005050Y003512D01*
|
||||
X005067Y003501D01*
|
||||
X005083Y003487D01*
|
||||
X005095Y003471D01*
|
||||
X005104Y003452D01*
|
||||
X005110Y003433D01*
|
||||
X005112Y003412D01*
|
||||
X005110Y003391D01*
|
||||
X005104Y003372D01*
|
||||
X005095Y003353D01*
|
||||
X005083Y003337D01*
|
||||
X005067Y003323D01*
|
||||
X005050Y003312D01*
|
||||
X005031Y003304D01*
|
||||
X005010Y003300D01*
|
||||
X004990Y003300D01*
|
||||
X004969Y003304D01*
|
||||
X004950Y003312D01*
|
||||
X004933Y003323D01*
|
||||
X004917Y003337D01*
|
||||
X004905Y003353D01*
|
||||
X004896Y003372D01*
|
||||
X004890Y003391D01*
|
||||
X004888Y003412D01*
|
||||
D18*
|
||||
X004897Y003663D02*
|
||||
X004899Y003678D01*
|
||||
X004905Y003691D01*
|
||||
X004914Y003703D01*
|
||||
X004925Y003712D01*
|
||||
X004939Y003718D01*
|
||||
X004954Y003720D01*
|
||||
X004969Y003718D01*
|
||||
X004982Y003712D01*
|
||||
X004994Y003703D01*
|
||||
X005003Y003692D01*
|
||||
X005009Y003678D01*
|
||||
X005011Y003663D01*
|
||||
X005009Y003648D01*
|
||||
X005003Y003635D01*
|
||||
X004994Y003623D01*
|
||||
X004983Y003614D01*
|
||||
X004969Y003608D01*
|
||||
X004954Y003606D01*
|
||||
X004939Y003608D01*
|
||||
X004926Y003614D01*
|
||||
X004914Y003623D01*
|
||||
X004905Y003634D01*
|
||||
X004899Y003648D01*
|
||||
X004897Y003663D01*
|
||||
X006782Y002742D02*
|
||||
X007018Y002585D01*
|
||||
X007018Y002899D01*
|
||||
X006782Y002742D01*
|
||||
X006841Y002742D02*
|
||||
X006959Y002663D01*
|
||||
X006959Y002821D01*
|
||||
X006841Y002742D01*
|
||||
X006897Y002705D02*
|
||||
X006959Y002705D01*
|
||||
X006959Y002783D02*
|
||||
X006902Y002783D01*
|
||||
X007061Y003541D02*
|
||||
X007399Y003541D01*
|
||||
X006911Y003995D02*
|
||||
X006911Y004207D01*
|
||||
X007061Y004661D02*
|
||||
X007399Y004661D01*
|
||||
X007131Y006705D02*
|
||||
X007288Y006941D01*
|
||||
X006974Y006941D01*
|
||||
X007131Y006705D01*
|
||||
X007131Y006764D02*
|
||||
X007210Y006882D01*
|
||||
X007052Y006882D01*
|
||||
X007131Y006764D01*
|
||||
X007116Y006787D02*
|
||||
X007146Y006787D01*
|
||||
X007199Y006865D02*
|
||||
X007063Y006865D01*
|
||||
X008091Y008742D02*
|
||||
X008093Y008794D01*
|
||||
X008099Y008846D01*
|
||||
X008109Y008897D01*
|
||||
X008122Y008947D01*
|
||||
X008140Y008997D01*
|
||||
X008161Y009044D01*
|
||||
X008185Y009090D01*
|
||||
X008214Y009134D01*
|
||||
X008245Y009176D01*
|
||||
X008279Y009215D01*
|
||||
X008316Y009252D01*
|
||||
X008356Y009285D01*
|
||||
X008399Y009316D01*
|
||||
X008443Y009343D01*
|
||||
X008489Y009367D01*
|
||||
X008538Y009387D01*
|
||||
X008587Y009403D01*
|
||||
X008638Y009416D01*
|
||||
X008689Y009425D01*
|
||||
X008741Y009430D01*
|
||||
X008793Y009431D01*
|
||||
X008845Y009428D01*
|
||||
X008897Y009421D01*
|
||||
X008948Y009410D01*
|
||||
X008998Y009396D01*
|
||||
X009047Y009377D01*
|
||||
X009094Y009355D01*
|
||||
X009139Y009330D01*
|
||||
X009183Y009301D01*
|
||||
X009224Y009269D01*
|
||||
X009263Y009234D01*
|
||||
X009298Y009196D01*
|
||||
X009331Y009155D01*
|
||||
X009361Y009113D01*
|
||||
X009387Y009068D01*
|
||||
X009410Y009021D01*
|
||||
X009429Y008972D01*
|
||||
X009445Y008922D01*
|
||||
X009457Y008872D01*
|
||||
X009465Y008820D01*
|
||||
X009469Y008768D01*
|
||||
X009469Y008716D01*
|
||||
X009465Y008664D01*
|
||||
X009457Y008612D01*
|
||||
X009445Y008562D01*
|
||||
X009429Y008512D01*
|
||||
X009410Y008463D01*
|
||||
X009387Y008416D01*
|
||||
X009361Y008371D01*
|
||||
X009331Y008329D01*
|
||||
X009298Y008288D01*
|
||||
X009263Y008250D01*
|
||||
X009224Y008215D01*
|
||||
X009183Y008183D01*
|
||||
X009139Y008154D01*
|
||||
X009094Y008129D01*
|
||||
X009047Y008107D01*
|
||||
X008998Y008088D01*
|
||||
X008948Y008074D01*
|
||||
X008897Y008063D01*
|
||||
X008845Y008056D01*
|
||||
X008793Y008053D01*
|
||||
X008741Y008054D01*
|
||||
X008689Y008059D01*
|
||||
X008638Y008068D01*
|
||||
X008587Y008081D01*
|
||||
X008538Y008097D01*
|
||||
X008489Y008117D01*
|
||||
X008443Y008141D01*
|
||||
X008399Y008168D01*
|
||||
X008356Y008199D01*
|
||||
X008316Y008232D01*
|
||||
X008279Y008269D01*
|
||||
X008245Y008308D01*
|
||||
X008214Y008350D01*
|
||||
X008185Y008394D01*
|
||||
X008161Y008440D01*
|
||||
X008140Y008487D01*
|
||||
X008122Y008537D01*
|
||||
X008109Y008587D01*
|
||||
X008099Y008638D01*
|
||||
X008093Y008690D01*
|
||||
X008091Y008742D01*
|
||||
X001091Y008742D02*
|
||||
X001093Y008794D01*
|
||||
X001099Y008846D01*
|
||||
X001109Y008897D01*
|
||||
X001122Y008947D01*
|
||||
X001140Y008997D01*
|
||||
X001161Y009044D01*
|
||||
X001185Y009090D01*
|
||||
X001214Y009134D01*
|
||||
X001245Y009176D01*
|
||||
X001279Y009215D01*
|
||||
X001316Y009252D01*
|
||||
X001356Y009285D01*
|
||||
X001399Y009316D01*
|
||||
X001443Y009343D01*
|
||||
X001489Y009367D01*
|
||||
X001538Y009387D01*
|
||||
X001587Y009403D01*
|
||||
X001638Y009416D01*
|
||||
X001689Y009425D01*
|
||||
X001741Y009430D01*
|
||||
X001793Y009431D01*
|
||||
X001845Y009428D01*
|
||||
X001897Y009421D01*
|
||||
X001948Y009410D01*
|
||||
X001998Y009396D01*
|
||||
X002047Y009377D01*
|
||||
X002094Y009355D01*
|
||||
X002139Y009330D01*
|
||||
X002183Y009301D01*
|
||||
X002224Y009269D01*
|
||||
X002263Y009234D01*
|
||||
X002298Y009196D01*
|
||||
X002331Y009155D01*
|
||||
X002361Y009113D01*
|
||||
X002387Y009068D01*
|
||||
X002410Y009021D01*
|
||||
X002429Y008972D01*
|
||||
X002445Y008922D01*
|
||||
X002457Y008872D01*
|
||||
X002465Y008820D01*
|
||||
X002469Y008768D01*
|
||||
X002469Y008716D01*
|
||||
X002465Y008664D01*
|
||||
X002457Y008612D01*
|
||||
X002445Y008562D01*
|
||||
X002429Y008512D01*
|
||||
X002410Y008463D01*
|
||||
X002387Y008416D01*
|
||||
X002361Y008371D01*
|
||||
X002331Y008329D01*
|
||||
X002298Y008288D01*
|
||||
X002263Y008250D01*
|
||||
X002224Y008215D01*
|
||||
X002183Y008183D01*
|
||||
X002139Y008154D01*
|
||||
X002094Y008129D01*
|
||||
X002047Y008107D01*
|
||||
X001998Y008088D01*
|
||||
X001948Y008074D01*
|
||||
X001897Y008063D01*
|
||||
X001845Y008056D01*
|
||||
X001793Y008053D01*
|
||||
X001741Y008054D01*
|
||||
X001689Y008059D01*
|
||||
X001638Y008068D01*
|
||||
X001587Y008081D01*
|
||||
X001538Y008097D01*
|
||||
X001489Y008117D01*
|
||||
X001443Y008141D01*
|
||||
X001399Y008168D01*
|
||||
X001356Y008199D01*
|
||||
X001316Y008232D01*
|
||||
X001279Y008269D01*
|
||||
X001245Y008308D01*
|
||||
X001214Y008350D01*
|
||||
X001185Y008394D01*
|
||||
X001161Y008440D01*
|
||||
X001140Y008487D01*
|
||||
X001122Y008537D01*
|
||||
X001109Y008587D01*
|
||||
X001099Y008638D01*
|
||||
X001093Y008690D01*
|
||||
X001091Y008742D01*
|
||||
D19*
|
||||
X007131Y006665D03*
|
||||
D20*
|
||||
X008920Y007223D03*
|
||||
X004580Y006443D03*
|
||||
X003790Y006453D03*
|
||||
D21*
|
||||
X003526Y007860D02*
|
||||
X003526Y009711D01*
|
||||
X003526Y007860D02*
|
||||
X006596Y007860D01*
|
||||
X006596Y009711D01*
|
||||
D22*
|
||||
X006742Y002742D03*
|
||||
M02*
|
76
gerber/gerbmerge/1x1/proj1.GTP
Normal file
76
gerber/gerbmerge/1x1/proj1.GTP
Normal file
|
@ -0,0 +1,76 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10R,0.0870X0.0240*%
|
||||
%ADD11R,0.0472X0.0315*%
|
||||
%ADD12R,0.0420X0.0860*%
|
||||
%ADD13R,0.0315X0.0472*%
|
||||
%ADD14R,0.0472X0.0472*%
|
||||
%ADD15R,0.0472X0.0217*%
|
||||
%ADD16R,0.0906X0.0630*%
|
||||
%ADD17R,0.0157X0.0531*%
|
||||
%ADD18R,0.0748X0.0748*%
|
||||
%ADD19R,0.0860X0.0420*%
|
||||
D10*
|
||||
X002944Y003623D03*
|
||||
X002944Y004123D03*
|
||||
X002944Y004623D03*
|
||||
X002944Y005123D03*
|
||||
X005594Y005123D03*
|
||||
X005594Y004623D03*
|
||||
X005594Y004123D03*
|
||||
X005594Y003623D03*
|
||||
D11*
|
||||
X007943Y005733D03*
|
||||
X007943Y006441D03*
|
||||
X006180Y006188D03*
|
||||
X006180Y006896D03*
|
||||
X005380Y006896D03*
|
||||
X005380Y006188D03*
|
||||
X002980Y006188D03*
|
||||
X002980Y006896D03*
|
||||
D12*
|
||||
X007131Y007653D03*
|
||||
X007131Y005953D03*
|
||||
D13*
|
||||
X002965Y002856D03*
|
||||
X003673Y002856D03*
|
||||
X007038Y005116D03*
|
||||
X007747Y005116D03*
|
||||
D14*
|
||||
X008546Y007223D03*
|
||||
X009373Y007223D03*
|
||||
X004580Y006895D03*
|
||||
X003790Y006905D03*
|
||||
X003790Y006079D03*
|
||||
X004580Y006069D03*
|
||||
D15*
|
||||
X006718Y004475D03*
|
||||
X006718Y003727D03*
|
||||
X007742Y003727D03*
|
||||
X007742Y004101D03*
|
||||
X007742Y004475D03*
|
||||
D16*
|
||||
X006430Y001452D03*
|
||||
X003950Y001452D03*
|
||||
D17*
|
||||
X004549Y008008D03*
|
||||
X004805Y008008D03*
|
||||
X005061Y008008D03*
|
||||
X005317Y008008D03*
|
||||
X005573Y008008D03*
|
||||
D18*
|
||||
X005533Y009061D03*
|
||||
X004589Y009061D03*
|
||||
X003506Y009061D03*
|
||||
X006616Y009061D03*
|
||||
D19*
|
||||
X006030Y002742D03*
|
||||
X007730Y002742D03*
|
||||
M02*
|
101
gerber/gerbmerge/1x1/proj1.GTS
Normal file
101
gerber/gerbmerge/1x1/proj1.GTS
Normal file
|
@ -0,0 +1,101 @@
|
|||
G75*
|
||||
%MOIN*%
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10R,0.0910X0.0280*%
|
||||
%ADD11R,0.0512X0.0355*%
|
||||
%ADD12R,0.0460X0.0900*%
|
||||
%ADD13R,0.0355X0.0512*%
|
||||
%ADD14R,0.0512X0.0512*%
|
||||
%ADD15R,0.0512X0.0257*%
|
||||
%ADD16C,0.1306*%
|
||||
%ADD17R,0.0946X0.0670*%
|
||||
%ADD18R,0.0197X0.0571*%
|
||||
%ADD19R,0.0788X0.0788*%
|
||||
%ADD20C,0.0276*%
|
||||
%ADD21C,0.0700*%
|
||||
%ADD22R,0.0900X0.0460*%
|
||||
%ADD23C,0.0740*%
|
||||
D10*
|
||||
X002944Y003623D03*
|
||||
X002944Y004123D03*
|
||||
X002944Y004623D03*
|
||||
X002944Y005123D03*
|
||||
X005594Y005123D03*
|
||||
X005594Y004623D03*
|
||||
X005594Y004123D03*
|
||||
X005594Y003623D03*
|
||||
D11*
|
||||
X007943Y005733D03*
|
||||
X007943Y006441D03*
|
||||
X006180Y006188D03*
|
||||
X006180Y006896D03*
|
||||
X005380Y006896D03*
|
||||
X005380Y006188D03*
|
||||
X002980Y006188D03*
|
||||
X002980Y006896D03*
|
||||
D12*
|
||||
X007131Y007653D03*
|
||||
X007131Y005953D03*
|
||||
D13*
|
||||
X007038Y005116D03*
|
||||
X007747Y005116D03*
|
||||
X003673Y002856D03*
|
||||
X002965Y002856D03*
|
||||
D14*
|
||||
X003790Y006079D03*
|
||||
X004580Y006069D03*
|
||||
X004580Y006895D03*
|
||||
X003790Y006905D03*
|
||||
X008546Y007223D03*
|
||||
X009373Y007223D03*
|
||||
D15*
|
||||
X007742Y004475D03*
|
||||
X007742Y004101D03*
|
||||
X007742Y003727D03*
|
||||
X006718Y003727D03*
|
||||
X006718Y004475D03*
|
||||
D16*
|
||||
X008780Y008742D03*
|
||||
X001780Y008742D03*
|
||||
D17*
|
||||
X003950Y001452D03*
|
||||
X006430Y001452D03*
|
||||
D18*
|
||||
X005573Y008008D03*
|
||||
X005317Y008008D03*
|
||||
X005061Y008008D03*
|
||||
X004805Y008008D03*
|
||||
X004549Y008008D03*
|
||||
D19*
|
||||
X004589Y009061D03*
|
||||
X005533Y009061D03*
|
||||
X006616Y009061D03*
|
||||
X003506Y009061D03*
|
||||
D20*
|
||||
X004274Y008156D03*
|
||||
X005848Y008156D03*
|
||||
D21*
|
||||
X001280Y001242D03*
|
||||
X001280Y002242D03*
|
||||
X001280Y003242D03*
|
||||
X001280Y004242D03*
|
||||
X001280Y005242D03*
|
||||
X001280Y006242D03*
|
||||
X001280Y007242D03*
|
||||
X009280Y006242D03*
|
||||
X009280Y005242D03*
|
||||
X009280Y002242D03*
|
||||
X009280Y001242D03*
|
||||
D22*
|
||||
X007730Y002742D03*
|
||||
X006030Y002742D03*
|
||||
D23*
|
||||
X007720Y001662D03*
|
||||
X002350Y007422D03*
|
||||
M02*
|
39
gerber/gerbmerge/1x1/proj1.TXT
Normal file
39
gerber/gerbmerge/1x1/proj1.TXT
Normal file
|
@ -0,0 +1,39 @@
|
|||
%
|
||||
M48
|
||||
M72
|
||||
T01C0.0236
|
||||
T02C0.0394
|
||||
T03C0.0400
|
||||
T04C0.0866
|
||||
%
|
||||
T01
|
||||
X2447Y3035
|
||||
X3318Y2464
|
||||
X4780Y4142
|
||||
X4218Y5514
|
||||
X5230Y5692
|
||||
X7225Y4371
|
||||
X7228Y3820
|
||||
X6297Y3232
|
||||
X7880Y7292
|
||||
X5848Y8156
|
||||
X4274Y8156
|
||||
T02
|
||||
X1280Y1242
|
||||
X1280Y2242
|
||||
X1280Y3242
|
||||
X1280Y4242
|
||||
X1280Y5242
|
||||
X1280Y6242
|
||||
X1280Y7242
|
||||
X9280Y6242
|
||||
X9280Y5242
|
||||
X9280Y2242
|
||||
X9280Y1242
|
||||
T03
|
||||
X7720Y1662
|
||||
X2350Y7422
|
||||
T04
|
||||
X1780Y8742
|
||||
X8780Y8742
|
||||
M30
|
10625
gerber/gerbmerge/4x4/ATtami.GBL
Normal file
10625
gerber/gerbmerge/4x4/ATtami.GBL
Normal file
File diff suppressed because it is too large
Load Diff
1448603
gerber/gerbmerge/4x4/ATtami.GBO
Normal file
1448603
gerber/gerbmerge/4x4/ATtami.GBO
Normal file
File diff suppressed because it is too large
Load Diff
513
gerber/gerbmerge/4x4/ATtami.GBS
Normal file
513
gerber/gerbmerge/4x4/ATtami.GBS
Normal file
|
@ -0,0 +1,513 @@
|
|||
G75*
|
||||
G70*
|
||||
%OFA0B0*%
|
||||
%FSLAX25Y25*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.13060*%
|
||||
%ADD12C,0.07000*%
|
||||
%ADD16C,0.02760*%
|
||||
%ADD25R,0.02900X0.01200*%
|
||||
%ADD26C,0.07400*%
|
||||
%ADD28R,0.00600X0.07200*%
|
||||
%ADD29R,0.05000X0.06700*%
|
||||
X0011000Y0011000D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0021000Y0091000D03*
|
||||
X0091000Y0091000D03*
|
||||
D16*
|
||||
X0061680Y0085140D03*
|
||||
X0045940Y0085140D03*
|
||||
D12*
|
||||
X0016000Y0016000D03*
|
||||
X0016000Y0026000D03*
|
||||
X0016000Y0036000D03*
|
||||
X0016000Y0046000D03*
|
||||
X0016000Y0056000D03*
|
||||
X0016000Y0066000D03*
|
||||
X0016000Y0076000D03*
|
||||
X0096000Y0066000D03*
|
||||
X0096000Y0056000D03*
|
||||
X0096000Y0026000D03*
|
||||
X0096000Y0016000D03*
|
||||
D29*
|
||||
X0094000Y0076000D03*
|
||||
X0088000Y0076000D03*
|
||||
D25*
|
||||
X0091000Y0076000D03*
|
||||
D28*
|
||||
X0091000Y0076000D03*
|
||||
D26*
|
||||
X0080400Y0020200D03*
|
||||
X0026700Y0077800D03*
|
||||
X0011000Y0107250D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0021000Y0187250D03*
|
||||
X0091000Y0187250D03*
|
||||
D16*
|
||||
X0061680Y0181390D03*
|
||||
X0045940Y0181390D03*
|
||||
D12*
|
||||
X0016000Y0112250D03*
|
||||
X0016000Y0122250D03*
|
||||
X0016000Y0132250D03*
|
||||
X0016000Y0142250D03*
|
||||
X0016000Y0152250D03*
|
||||
X0016000Y0162250D03*
|
||||
X0016000Y0172250D03*
|
||||
X0096000Y0162250D03*
|
||||
X0096000Y0152250D03*
|
||||
X0096000Y0122250D03*
|
||||
X0096000Y0112250D03*
|
||||
D29*
|
||||
X0094000Y0172250D03*
|
||||
X0088000Y0172250D03*
|
||||
D25*
|
||||
X0091000Y0172250D03*
|
||||
D28*
|
||||
X0091000Y0172250D03*
|
||||
D26*
|
||||
X0080400Y0116450D03*
|
||||
X0026700Y0174050D03*
|
||||
X0011000Y0203500D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0021000Y0283500D03*
|
||||
X0091000Y0283500D03*
|
||||
D16*
|
||||
X0061680Y0277640D03*
|
||||
X0045940Y0277640D03*
|
||||
D12*
|
||||
X0016000Y0208500D03*
|
||||
X0016000Y0218500D03*
|
||||
X0016000Y0228500D03*
|
||||
X0016000Y0238500D03*
|
||||
X0016000Y0248500D03*
|
||||
X0016000Y0258500D03*
|
||||
X0016000Y0268500D03*
|
||||
X0096000Y0258500D03*
|
||||
X0096000Y0248500D03*
|
||||
X0096000Y0218500D03*
|
||||
X0096000Y0208500D03*
|
||||
D29*
|
||||
X0094000Y0268500D03*
|
||||
X0088000Y0268500D03*
|
||||
D25*
|
||||
X0091000Y0268500D03*
|
||||
D28*
|
||||
X0091000Y0268500D03*
|
||||
D26*
|
||||
X0080400Y0212700D03*
|
||||
X0026700Y0270300D03*
|
||||
X0011000Y0299750D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0021000Y0379750D03*
|
||||
X0091000Y0379750D03*
|
||||
D16*
|
||||
X0061680Y0373890D03*
|
||||
X0045940Y0373890D03*
|
||||
D12*
|
||||
X0016000Y0304750D03*
|
||||
X0016000Y0314750D03*
|
||||
X0016000Y0324750D03*
|
||||
X0016000Y0334750D03*
|
||||
X0016000Y0344750D03*
|
||||
X0016000Y0354750D03*
|
||||
X0016000Y0364750D03*
|
||||
X0096000Y0354750D03*
|
||||
X0096000Y0344750D03*
|
||||
X0096000Y0314750D03*
|
||||
X0096000Y0304750D03*
|
||||
D29*
|
||||
X0094000Y0364750D03*
|
||||
X0088000Y0364750D03*
|
||||
D25*
|
||||
X0091000Y0364750D03*
|
||||
D28*
|
||||
X0091000Y0364750D03*
|
||||
D26*
|
||||
X0080400Y0308950D03*
|
||||
X0026700Y0366550D03*
|
||||
X0107130Y0011000D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0117130Y0091000D03*
|
||||
X0187130Y0091000D03*
|
||||
D16*
|
||||
X0157810Y0085140D03*
|
||||
X0142070Y0085140D03*
|
||||
D12*
|
||||
X0112130Y0016000D03*
|
||||
X0112130Y0026000D03*
|
||||
X0112130Y0036000D03*
|
||||
X0112130Y0046000D03*
|
||||
X0112130Y0056000D03*
|
||||
X0112130Y0066000D03*
|
||||
X0112130Y0076000D03*
|
||||
X0192130Y0066000D03*
|
||||
X0192130Y0056000D03*
|
||||
X0192130Y0026000D03*
|
||||
X0192130Y0016000D03*
|
||||
D29*
|
||||
X0190130Y0076000D03*
|
||||
X0184130Y0076000D03*
|
||||
D25*
|
||||
X0187130Y0076000D03*
|
||||
D28*
|
||||
X0187130Y0076000D03*
|
||||
D26*
|
||||
X0176530Y0020200D03*
|
||||
X0122830Y0077800D03*
|
||||
X0107130Y0107250D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0117130Y0187250D03*
|
||||
X0187130Y0187250D03*
|
||||
D16*
|
||||
X0157810Y0181390D03*
|
||||
X0142070Y0181390D03*
|
||||
D12*
|
||||
X0112130Y0112250D03*
|
||||
X0112130Y0122250D03*
|
||||
X0112130Y0132250D03*
|
||||
X0112130Y0142250D03*
|
||||
X0112130Y0152250D03*
|
||||
X0112130Y0162250D03*
|
||||
X0112130Y0172250D03*
|
||||
X0192130Y0162250D03*
|
||||
X0192130Y0152250D03*
|
||||
X0192130Y0122250D03*
|
||||
X0192130Y0112250D03*
|
||||
D29*
|
||||
X0190130Y0172250D03*
|
||||
X0184130Y0172250D03*
|
||||
D25*
|
||||
X0187130Y0172250D03*
|
||||
D28*
|
||||
X0187130Y0172250D03*
|
||||
D26*
|
||||
X0176530Y0116450D03*
|
||||
X0122830Y0174050D03*
|
||||
X0107130Y0203500D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0117130Y0283500D03*
|
||||
X0187130Y0283500D03*
|
||||
D16*
|
||||
X0157810Y0277640D03*
|
||||
X0142070Y0277640D03*
|
||||
D12*
|
||||
X0112130Y0208500D03*
|
||||
X0112130Y0218500D03*
|
||||
X0112130Y0228500D03*
|
||||
X0112130Y0238500D03*
|
||||
X0112130Y0248500D03*
|
||||
X0112130Y0258500D03*
|
||||
X0112130Y0268500D03*
|
||||
X0192130Y0258500D03*
|
||||
X0192130Y0248500D03*
|
||||
X0192130Y0218500D03*
|
||||
X0192130Y0208500D03*
|
||||
D29*
|
||||
X0190130Y0268500D03*
|
||||
X0184130Y0268500D03*
|
||||
D25*
|
||||
X0187130Y0268500D03*
|
||||
D28*
|
||||
X0187130Y0268500D03*
|
||||
D26*
|
||||
X0176530Y0212700D03*
|
||||
X0122830Y0270300D03*
|
||||
X0107130Y0299750D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0117130Y0379750D03*
|
||||
X0187130Y0379750D03*
|
||||
D16*
|
||||
X0157810Y0373890D03*
|
||||
X0142070Y0373890D03*
|
||||
D12*
|
||||
X0112130Y0304750D03*
|
||||
X0112130Y0314750D03*
|
||||
X0112130Y0324750D03*
|
||||
X0112130Y0334750D03*
|
||||
X0112130Y0344750D03*
|
||||
X0112130Y0354750D03*
|
||||
X0112130Y0364750D03*
|
||||
X0192130Y0354750D03*
|
||||
X0192130Y0344750D03*
|
||||
X0192130Y0314750D03*
|
||||
X0192130Y0304750D03*
|
||||
D29*
|
||||
X0190130Y0364750D03*
|
||||
X0184130Y0364750D03*
|
||||
D25*
|
||||
X0187130Y0364750D03*
|
||||
D28*
|
||||
X0187130Y0364750D03*
|
||||
D26*
|
||||
X0176530Y0308950D03*
|
||||
X0122830Y0366550D03*
|
||||
X0203260Y0011000D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0213260Y0091000D03*
|
||||
X0283260Y0091000D03*
|
||||
D16*
|
||||
X0253940Y0085140D03*
|
||||
X0238200Y0085140D03*
|
||||
D12*
|
||||
X0208260Y0016000D03*
|
||||
X0208260Y0026000D03*
|
||||
X0208260Y0036000D03*
|
||||
X0208260Y0046000D03*
|
||||
X0208260Y0056000D03*
|
||||
X0208260Y0066000D03*
|
||||
X0208260Y0076000D03*
|
||||
X0288260Y0066000D03*
|
||||
X0288260Y0056000D03*
|
||||
X0288260Y0026000D03*
|
||||
X0288260Y0016000D03*
|
||||
D29*
|
||||
X0286260Y0076000D03*
|
||||
X0280260Y0076000D03*
|
||||
D25*
|
||||
X0283260Y0076000D03*
|
||||
D28*
|
||||
X0283260Y0076000D03*
|
||||
D26*
|
||||
X0272660Y0020200D03*
|
||||
X0218960Y0077800D03*
|
||||
X0203260Y0107250D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0213260Y0187250D03*
|
||||
X0283260Y0187250D03*
|
||||
D16*
|
||||
X0253940Y0181390D03*
|
||||
X0238200Y0181390D03*
|
||||
D12*
|
||||
X0208260Y0112250D03*
|
||||
X0208260Y0122250D03*
|
||||
X0208260Y0132250D03*
|
||||
X0208260Y0142250D03*
|
||||
X0208260Y0152250D03*
|
||||
X0208260Y0162250D03*
|
||||
X0208260Y0172250D03*
|
||||
X0288260Y0162250D03*
|
||||
X0288260Y0152250D03*
|
||||
X0288260Y0122250D03*
|
||||
X0288260Y0112250D03*
|
||||
D29*
|
||||
X0286260Y0172250D03*
|
||||
X0280260Y0172250D03*
|
||||
D25*
|
||||
X0283260Y0172250D03*
|
||||
D28*
|
||||
X0283260Y0172250D03*
|
||||
D26*
|
||||
X0272660Y0116450D03*
|
||||
X0218960Y0174050D03*
|
||||
X0203260Y0203500D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0213260Y0283500D03*
|
||||
X0283260Y0283500D03*
|
||||
D16*
|
||||
X0253940Y0277640D03*
|
||||
X0238200Y0277640D03*
|
||||
D12*
|
||||
X0208260Y0208500D03*
|
||||
X0208260Y0218500D03*
|
||||
X0208260Y0228500D03*
|
||||
X0208260Y0238500D03*
|
||||
X0208260Y0248500D03*
|
||||
X0208260Y0258500D03*
|
||||
X0208260Y0268500D03*
|
||||
X0288260Y0258500D03*
|
||||
X0288260Y0248500D03*
|
||||
X0288260Y0218500D03*
|
||||
X0288260Y0208500D03*
|
||||
D29*
|
||||
X0286260Y0268500D03*
|
||||
X0280260Y0268500D03*
|
||||
D25*
|
||||
X0283260Y0268500D03*
|
||||
D28*
|
||||
X0283260Y0268500D03*
|
||||
D26*
|
||||
X0272660Y0212700D03*
|
||||
X0218960Y0270300D03*
|
||||
X0203260Y0299750D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0213260Y0379750D03*
|
||||
X0283260Y0379750D03*
|
||||
D16*
|
||||
X0253940Y0373890D03*
|
||||
X0238200Y0373890D03*
|
||||
D12*
|
||||
X0208260Y0304750D03*
|
||||
X0208260Y0314750D03*
|
||||
X0208260Y0324750D03*
|
||||
X0208260Y0334750D03*
|
||||
X0208260Y0344750D03*
|
||||
X0208260Y0354750D03*
|
||||
X0208260Y0364750D03*
|
||||
X0288260Y0354750D03*
|
||||
X0288260Y0344750D03*
|
||||
X0288260Y0314750D03*
|
||||
X0288260Y0304750D03*
|
||||
D29*
|
||||
X0286260Y0364750D03*
|
||||
X0280260Y0364750D03*
|
||||
D25*
|
||||
X0283260Y0364750D03*
|
||||
D28*
|
||||
X0283260Y0364750D03*
|
||||
D26*
|
||||
X0272660Y0308950D03*
|
||||
X0218960Y0366550D03*
|
||||
X0299390Y0011000D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0309390Y0091000D03*
|
||||
X0379390Y0091000D03*
|
||||
D16*
|
||||
X0350070Y0085140D03*
|
||||
X0334330Y0085140D03*
|
||||
D12*
|
||||
X0304390Y0016000D03*
|
||||
X0304390Y0026000D03*
|
||||
X0304390Y0036000D03*
|
||||
X0304390Y0046000D03*
|
||||
X0304390Y0056000D03*
|
||||
X0304390Y0066000D03*
|
||||
X0304390Y0076000D03*
|
||||
X0384390Y0066000D03*
|
||||
X0384390Y0056000D03*
|
||||
X0384390Y0026000D03*
|
||||
X0384390Y0016000D03*
|
||||
D29*
|
||||
X0382390Y0076000D03*
|
||||
X0376390Y0076000D03*
|
||||
D25*
|
||||
X0379390Y0076000D03*
|
||||
D28*
|
||||
X0379390Y0076000D03*
|
||||
D26*
|
||||
X0368790Y0020200D03*
|
||||
X0315090Y0077800D03*
|
||||
X0299390Y0107250D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0309390Y0187250D03*
|
||||
X0379390Y0187250D03*
|
||||
D16*
|
||||
X0350070Y0181390D03*
|
||||
X0334330Y0181390D03*
|
||||
D12*
|
||||
X0304390Y0112250D03*
|
||||
X0304390Y0122250D03*
|
||||
X0304390Y0132250D03*
|
||||
X0304390Y0142250D03*
|
||||
X0304390Y0152250D03*
|
||||
X0304390Y0162250D03*
|
||||
X0304390Y0172250D03*
|
||||
X0384390Y0162250D03*
|
||||
X0384390Y0152250D03*
|
||||
X0384390Y0122250D03*
|
||||
X0384390Y0112250D03*
|
||||
D29*
|
||||
X0382390Y0172250D03*
|
||||
X0376390Y0172250D03*
|
||||
D25*
|
||||
X0379390Y0172250D03*
|
||||
D28*
|
||||
X0379390Y0172250D03*
|
||||
D26*
|
||||
X0368790Y0116450D03*
|
||||
X0315090Y0174050D03*
|
||||
X0299390Y0203500D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0309390Y0283500D03*
|
||||
X0379390Y0283500D03*
|
||||
D16*
|
||||
X0350070Y0277640D03*
|
||||
X0334330Y0277640D03*
|
||||
D12*
|
||||
X0304390Y0208500D03*
|
||||
X0304390Y0218500D03*
|
||||
X0304390Y0228500D03*
|
||||
X0304390Y0238500D03*
|
||||
X0304390Y0248500D03*
|
||||
X0304390Y0258500D03*
|
||||
X0304390Y0268500D03*
|
||||
X0384390Y0258500D03*
|
||||
X0384390Y0248500D03*
|
||||
X0384390Y0218500D03*
|
||||
X0384390Y0208500D03*
|
||||
D29*
|
||||
X0382390Y0268500D03*
|
||||
X0376390Y0268500D03*
|
||||
D25*
|
||||
X0379390Y0268500D03*
|
||||
D28*
|
||||
X0379390Y0268500D03*
|
||||
D26*
|
||||
X0368790Y0212700D03*
|
||||
X0315090Y0270300D03*
|
||||
X0299390Y0299750D02*
|
||||
G75*
|
||||
%LPD*%
|
||||
D10*
|
||||
X0309390Y0379750D03*
|
||||
X0379390Y0379750D03*
|
||||
D16*
|
||||
X0350070Y0373890D03*
|
||||
X0334330Y0373890D03*
|
||||
D12*
|
||||
X0304390Y0304750D03*
|
||||
X0304390Y0314750D03*
|
||||
X0304390Y0324750D03*
|
||||
X0304390Y0334750D03*
|
||||
X0304390Y0344750D03*
|
||||
X0304390Y0354750D03*
|
||||
X0304390Y0364750D03*
|
||||
X0384390Y0354750D03*
|
||||
X0384390Y0344750D03*
|
||||
X0384390Y0314750D03*
|
||||
X0384390Y0304750D03*
|
||||
D29*
|
||||
X0382390Y0364750D03*
|
||||
X0376390Y0364750D03*
|
||||
D25*
|
||||
X0379390Y0364750D03*
|
||||
D28*
|
||||
X0379390Y0364750D03*
|
||||
D26*
|
||||
X0368790Y0308950D03*
|
||||
X0315090Y0366550D03*
|
||||
M02*
|
1307
gerber/gerbmerge/4x4/ATtami.GML
Normal file
1307
gerber/gerbmerge/4x4/ATtami.GML
Normal file
File diff suppressed because it is too large
Load Diff
23245
gerber/gerbmerge/4x4/ATtami.GTL
Normal file
23245
gerber/gerbmerge/4x4/ATtami.GTL
Normal file
File diff suppressed because it is too large
Load Diff
15456
gerber/gerbmerge/4x4/ATtami.GTO
Normal file
15456
gerber/gerbmerge/4x4/ATtami.GTO
Normal file
File diff suppressed because it is too large
Load Diff
1304
gerber/gerbmerge/4x4/ATtami.GTS
Normal file
1304
gerber/gerbmerge/4x4/ATtami.GTS
Normal file
File diff suppressed because it is too large
Load Diff
421
gerber/gerbmerge/4x4/ATtami.TXT
Normal file
421
gerber/gerbmerge/4x4/ATtami.TXT
Normal file
|
@ -0,0 +1,421 @@
|
|||
%
|
||||
T01C0.023600
|
||||
X2767Y3393
|
||||
X3638Y2822
|
||||
X5100Y4500
|
||||
X4538Y5872
|
||||
X5550Y6050
|
||||
X7545Y4729
|
||||
X7548Y4178
|
||||
X6617Y3590
|
||||
X8200Y7650
|
||||
X6168Y8514
|
||||
X4594Y8514
|
||||
X2767Y13018
|
||||
X3638Y12447
|
||||
X5100Y14125
|
||||
X4538Y15497
|
||||
X5550Y15675
|
||||
X7545Y14354
|
||||
X7548Y13803
|
||||
X6617Y13215
|
||||
X8200Y17275
|
||||
X6168Y18139
|
||||
X4594Y18139
|
||||
X2767Y22643
|
||||
X3638Y22072
|
||||
X5100Y23750
|
||||
X4538Y25122
|
||||
X5550Y25300
|
||||
X7545Y23979
|
||||
X7548Y23428
|
||||
X6617Y22840
|
||||
X8200Y26900
|
||||
X6168Y27764
|
||||
X4594Y27764
|
||||
X2767Y32268
|
||||
X3638Y31697
|
||||
X5100Y33375
|
||||
X4538Y34747
|
||||
X5550Y34925
|
||||
X7545Y33604
|
||||
X7548Y33053
|
||||
X6617Y32465
|
||||
X8200Y36525
|
||||
X6168Y37389
|
||||
X4594Y37389
|
||||
X12380Y3393
|
||||
X13251Y2822
|
||||
X14713Y4500
|
||||
X14151Y5872
|
||||
X15163Y6050
|
||||
X17158Y4729
|
||||
X17161Y4178
|
||||
X16230Y3590
|
||||
X17813Y7650
|
||||
X15781Y8514
|
||||
X14207Y8514
|
||||
X12380Y13018
|
||||
X13251Y12447
|
||||
X14713Y14125
|
||||
X14151Y15497
|
||||
X15163Y15675
|
||||
X17158Y14354
|
||||
X17161Y13803
|
||||
X16230Y13215
|
||||
X17813Y17275
|
||||
X15781Y18139
|
||||
X14207Y18139
|
||||
X12380Y22643
|
||||
X13251Y22072
|
||||
X14713Y23750
|
||||
X14151Y25122
|
||||
X15163Y25300
|
||||
X17158Y23979
|
||||
X17161Y23428
|
||||
X16230Y22840
|
||||
X17813Y26900
|
||||
X15781Y27764
|
||||
X14207Y27764
|
||||
X12380Y32268
|
||||
X13251Y31697
|
||||
X14713Y33375
|
||||
X14151Y34747
|
||||
X15163Y34925
|
||||
X17158Y33604
|
||||
X17161Y33053
|
||||
X16230Y32465
|
||||
X17813Y36525
|
||||
X15781Y37389
|
||||
X14207Y37389
|
||||
X21993Y3393
|
||||
X22864Y2822
|
||||
X24326Y4500
|
||||
X23764Y5872
|
||||
X24776Y6050
|
||||
X26771Y4729
|
||||
X26774Y4178
|
||||
X25843Y3590
|
||||
X27426Y7650
|
||||
X25394Y8514
|
||||
X23820Y8514
|
||||
X21993Y13018
|
||||
X22864Y12447
|
||||
X24326Y14125
|
||||
X23764Y15497
|
||||
X24776Y15675
|
||||
X26771Y14354
|
||||
X26774Y13803
|
||||
X25843Y13215
|
||||
X27426Y17275
|
||||
X25394Y18139
|
||||
X23820Y18139
|
||||
X21993Y22643
|
||||
X22864Y22072
|
||||
X24326Y23750
|
||||
X23764Y25122
|
||||
X24776Y25300
|
||||
X26771Y23979
|
||||
X26774Y23428
|
||||
X25843Y22840
|
||||
X27426Y26900
|
||||
X25394Y27764
|
||||
X23820Y27764
|
||||
X21993Y32268
|
||||
X22864Y31697
|
||||
X24326Y33375
|
||||
X23764Y34747
|
||||
X24776Y34925
|
||||
X26771Y33604
|
||||
X26774Y33053
|
||||
X25843Y32465
|
||||
X27426Y36525
|
||||
X25394Y37389
|
||||
X23820Y37389
|
||||
X31606Y3393
|
||||
X32477Y2822
|
||||
X33939Y4500
|
||||
X33377Y5872
|
||||
X34389Y6050
|
||||
X36384Y4729
|
||||
X36387Y4178
|
||||
X35456Y3590
|
||||
X37039Y7650
|
||||
X35007Y8514
|
||||
X33433Y8514
|
||||
X31606Y13018
|
||||
X32477Y12447
|
||||
X33939Y14125
|
||||
X33377Y15497
|
||||
X34389Y15675
|
||||
X36384Y14354
|
||||
X36387Y13803
|
||||
X35456Y13215
|
||||
X37039Y17275
|
||||
X35007Y18139
|
||||
X33433Y18139
|
||||
X31606Y22643
|
||||
X32477Y22072
|
||||
X33939Y23750
|
||||
X33377Y25122
|
||||
X34389Y25300
|
||||
X36384Y23979
|
||||
X36387Y23428
|
||||
X35456Y22840
|
||||
X37039Y26900
|
||||
X35007Y27764
|
||||
X33433Y27764
|
||||
X31606Y32268
|
||||
X32477Y31697
|
||||
X33939Y33375
|
||||
X33377Y34747
|
||||
X34389Y34925
|
||||
X36384Y33604
|
||||
X36387Y33053
|
||||
X35456Y32465
|
||||
X37039Y36525
|
||||
X35007Y37389
|
||||
X33433Y37389
|
||||
T02C0.039700
|
||||
X8040Y2020
|
||||
X2670Y7780
|
||||
X1600Y1600
|
||||
X1600Y2600
|
||||
X1600Y3600
|
||||
X1600Y4600
|
||||
X1600Y5600
|
||||
X1600Y6600
|
||||
X1600Y7600
|
||||
X9600Y6600
|
||||
X9600Y5600
|
||||
X9600Y2600
|
||||
X9600Y1600
|
||||
X8040Y11645
|
||||
X2670Y17405
|
||||
X1600Y11225
|
||||
X1600Y12225
|
||||
X1600Y13225
|
||||
X1600Y14225
|
||||
X1600Y15225
|
||||
X1600Y16225
|
||||
X1600Y17225
|
||||
X9600Y16225
|
||||
X9600Y15225
|
||||
X9600Y12225
|
||||
X9600Y11225
|
||||
X8040Y21270
|
||||
X2670Y27030
|
||||
X1600Y20850
|
||||
X1600Y21850
|
||||
X1600Y22850
|
||||
X1600Y23850
|
||||
X1600Y24850
|
||||
X1600Y25850
|
||||
X1600Y26850
|
||||
X9600Y25850
|
||||
X9600Y24850
|
||||
X9600Y21850
|
||||
X9600Y20850
|
||||
X8040Y30895
|
||||
X2670Y36655
|
||||
X1600Y30475
|
||||
X1600Y31475
|
||||
X1600Y32475
|
||||
X1600Y33475
|
||||
X1600Y34475
|
||||
X1600Y35475
|
||||
X1600Y36475
|
||||
X9600Y35475
|
||||
X9600Y34475
|
||||
X9600Y31475
|
||||
X9600Y30475
|
||||
X17653Y2020
|
||||
X12283Y7780
|
||||
X11213Y1600
|
||||
X11213Y2600
|
||||
X11213Y3600
|
||||
X11213Y4600
|
||||
X11213Y5600
|
||||
X11213Y6600
|
||||
X11213Y7600
|
||||
X19213Y6600
|
||||
X19213Y5600
|
||||
X19213Y2600
|
||||
X19213Y1600
|
||||
X17653Y11645
|
||||
X12283Y17405
|
||||
X11213Y11225
|
||||
X11213Y12225
|
||||
X11213Y13225
|
||||
X11213Y14225
|
||||
X11213Y15225
|
||||
X11213Y16225
|
||||
X11213Y17225
|
||||
X19213Y16225
|
||||
X19213Y15225
|
||||
X19213Y12225
|
||||
X19213Y11225
|
||||
X17653Y21270
|
||||
X12283Y27030
|
||||
X11213Y20850
|
||||
X11213Y21850
|
||||
X11213Y22850
|
||||
X11213Y23850
|
||||
X11213Y24850
|
||||
X11213Y25850
|
||||
X11213Y26850
|
||||
X19213Y25850
|
||||
X19213Y24850
|
||||
X19213Y21850
|
||||
X19213Y20850
|
||||
X17653Y30895
|
||||
X12283Y36655
|
||||
X11213Y30475
|
||||
X11213Y31475
|
||||
X11213Y32475
|
||||
X11213Y33475
|
||||
X11213Y34475
|
||||
X11213Y35475
|
||||
X11213Y36475
|
||||
X19213Y35475
|
||||
X19213Y34475
|
||||
X19213Y31475
|
||||
X19213Y30475
|
||||
X27266Y2020
|
||||
X21896Y7780
|
||||
X20826Y1600
|
||||
X20826Y2600
|
||||
X20826Y3600
|
||||
X20826Y4600
|
||||
X20826Y5600
|
||||
X20826Y6600
|
||||
X20826Y7600
|
||||
X28826Y6600
|
||||
X28826Y5600
|
||||
X28826Y2600
|
||||
X28826Y1600
|
||||
X27266Y11645
|
||||
X21896Y17405
|
||||
X20826Y11225
|
||||
X20826Y12225
|
||||
X20826Y13225
|
||||
X20826Y14225
|
||||
X20826Y15225
|
||||
X20826Y16225
|
||||
X20826Y17225
|
||||
X28826Y16225
|
||||
X28826Y15225
|
||||
X28826Y12225
|
||||
X28826Y11225
|
||||
X27266Y21270
|
||||
X21896Y27030
|
||||
X20826Y20850
|
||||
X20826Y21850
|
||||
X20826Y22850
|
||||
X20826Y23850
|
||||
X20826Y24850
|
||||
X20826Y25850
|
||||
X20826Y26850
|
||||
X28826Y25850
|
||||
X28826Y24850
|
||||
X28826Y21850
|
||||
X28826Y20850
|
||||
X27266Y30895
|
||||
X21896Y36655
|
||||
X20826Y30475
|
||||
X20826Y31475
|
||||
X20826Y32475
|
||||
X20826Y33475
|
||||
X20826Y34475
|
||||
X20826Y35475
|
||||
X20826Y36475
|
||||
X28826Y35475
|
||||
X28826Y34475
|
||||
X28826Y31475
|
||||
X28826Y30475
|
||||
X36879Y2020
|
||||
X31509Y7780
|
||||
X30439Y1600
|
||||
X30439Y2600
|
||||
X30439Y3600
|
||||
X30439Y4600
|
||||
X30439Y5600
|
||||
X30439Y6600
|
||||
X30439Y7600
|
||||
X38439Y6600
|
||||
X38439Y5600
|
||||
X38439Y2600
|
||||
X38439Y1600
|
||||
X36879Y11645
|
||||
X31509Y17405
|
||||
X30439Y11225
|
||||
X30439Y12225
|
||||
X30439Y13225
|
||||
X30439Y14225
|
||||
X30439Y15225
|
||||
X30439Y16225
|
||||
X30439Y17225
|
||||
X38439Y16225
|
||||
X38439Y15225
|
||||
X38439Y12225
|
||||
X38439Y11225
|
||||
X36879Y21270
|
||||
X31509Y27030
|
||||
X30439Y20850
|
||||
X30439Y21850
|
||||
X30439Y22850
|
||||
X30439Y23850
|
||||
X30439Y24850
|
||||
X30439Y25850
|
||||
X30439Y26850
|
||||
X38439Y25850
|
||||
X38439Y24850
|
||||
X38439Y21850
|
||||
X38439Y20850
|
||||
X36879Y30895
|
||||
X31509Y36655
|
||||
X30439Y30475
|
||||
X30439Y31475
|
||||
X30439Y32475
|
||||
X30439Y33475
|
||||
X30439Y34475
|
||||
X30439Y35475
|
||||
X30439Y36475
|
||||
X38439Y35475
|
||||
X38439Y34475
|
||||
X38439Y31475
|
||||
X38439Y30475
|
||||
T03C0.086600
|
||||
X2100Y9100
|
||||
X9100Y9100
|
||||
X2100Y18725
|
||||
X9100Y18725
|
||||
X2100Y28350
|
||||
X9100Y28350
|
||||
X2100Y37975
|
||||
X9100Y37975
|
||||
X11713Y9100
|
||||
X18713Y9100
|
||||
X11713Y18725
|
||||
X18713Y18725
|
||||
X11713Y28350
|
||||
X18713Y28350
|
||||
X11713Y37975
|
||||
X18713Y37975
|
||||
X21326Y9100
|
||||
X28326Y9100
|
||||
X21326Y18725
|
||||
X28326Y18725
|
||||
X21326Y28350
|
||||
X28326Y28350
|
||||
X21326Y37975
|
||||
X28326Y37975
|
||||
X30939Y9100
|
||||
X37939Y9100
|
||||
X30939Y18725
|
||||
X37939Y18725
|
||||
X30939Y28350
|
||||
X37939Y28350
|
||||
X30939Y37975
|
||||
X37939Y37975
|
||||
M30
|
24
gerber/gerbmerge/4x4/ATtami.sco
Normal file
24
gerber/gerbmerge/4x4/ATtami.sco
Normal file
|
@ -0,0 +1,24 @@
|
|||
G75*
|
||||
G70*
|
||||
%OFA0B0*%
|
||||
%FSLAX25Y25*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
%ADD10C,0.00100*%
|
||||
D10*
|
||||
X0010000Y0104125D02*
|
||||
X0390270Y0104125D01*
|
||||
X0010000Y0200375D02*
|
||||
X0390270Y0200375D01*
|
||||
X0010000Y0296625D02*
|
||||
X0390270Y0296625D01*
|
||||
X0104005Y0010000D02*
|
||||
X0104005Y0390750D01*
|
||||
X0200135Y0010000D02*
|
||||
X0200135Y0390750D01*
|
||||
X0296265Y0010000D02*
|
||||
X0296265Y0390750D01*
|
||||
M02*
|
1
gerber/gerbmerge/bin/__init__.py
Normal file
1
gerber/gerbmerge/bin/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Placeholder for GerbMerge package
|
BIN
gerber/gerbmerge/bin/__init__.pyc
Normal file
BIN
gerber/gerbmerge/bin/__init__.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/__init__.pyo
Normal file
BIN
gerber/gerbmerge/bin/__init__.pyo
Normal file
Binary file not shown.
348
gerber/gerbmerge/bin/amacro.py
Normal file
348
gerber/gerbmerge/bin/amacro.py
Normal file
|
@ -0,0 +1,348 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Define and manage aperture macros (%AM command). Currently,
|
||||
only macros without replaceable parameters (e.g., $1, $2, etc.)
|
||||
are supported.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import string
|
||||
import copy
|
||||
|
||||
import config
|
||||
|
||||
_macro_pat = re.compile(r'^%AM([^*]+)\*$')
|
||||
|
||||
# This list stores the expected types of parameters for each primitive type
|
||||
# (e.g., outline, line, circle, polygon, etc.). None is used for undefined
|
||||
# primitives. Each entry corresponds to the defined primitive code, and
|
||||
# comprises a tuple of conversion functions (i.e., built-in int() and float()
|
||||
# functions) that apply to all parameters AFTER the primitive code. For example,
|
||||
# code 1 (circle) may be instantiated as:
|
||||
# 1,1,0.025,0.0,0.0
|
||||
# (the parameters are code, exposure type, diameter, X center, Y center).
|
||||
# After the integer code, we expect an int for exposure type, then floats
|
||||
# for the remaining three parameters. Thus, the entry for code 1 is
|
||||
# (int, float, float, float).
|
||||
PrimitiveParmTypes = (
|
||||
None, # Code 0 -- undefined
|
||||
(int, float, float, float), # Code 1 -- circle
|
||||
(int, float, float, float, float, float, float), # Code 2 -- line (vector)
|
||||
None, # Code 3 -- end-of-file for .DES files
|
||||
(int, int, float, float, float, float, float), # Code 4 -- outline...takes any number of additional floats
|
||||
(int, int, float, float, float, float), # Code 5 -- regular polygon
|
||||
(float, float, float, float, float, int, float, float, float), # Code 6 -- moire
|
||||
(float, float, float, float, float, float), # Code 7 -- thermal
|
||||
None, # Code 8 -- undefined
|
||||
None, # Code 9 -- undefined
|
||||
None, # Code 10 -- undefined
|
||||
None, # Code 11 -- undefined
|
||||
None, # Code 12 -- undefined
|
||||
None, # Code 13 -- undefined
|
||||
None, # Code 14 -- undefined
|
||||
None, # Code 15 -- undefined
|
||||
None, # Code 16 -- undefined
|
||||
None, # Code 17 -- undefined
|
||||
None, # Code 18 -- undefined
|
||||
None, # Code 19 -- undefined
|
||||
(int, float, float, float, float, float), # Code 20 -- line (vector)...alias for code 2
|
||||
(int, float, float, float, float, float), # Code 21 -- line (center)
|
||||
(int, float, float, float, float, float) # Code 22 -- line (lower-left)
|
||||
)
|
||||
|
||||
def rotatexy(x,y):
|
||||
# Rotate point (x,y) counterclockwise 90 degrees about the origin
|
||||
return (-y,x)
|
||||
|
||||
def rotatexypair(L, ix):
|
||||
# Rotate list items L[ix],L[ix+1] by 90 degrees
|
||||
L[ix],L[ix+1] = rotatexy(L[ix],L[ix+1])
|
||||
|
||||
def swapxypair(L, ix):
|
||||
# Swap two list elements
|
||||
L[ix],L[ix+1] = L[ix+1],L[ix]
|
||||
|
||||
def rotatetheta(th):
|
||||
# Increase angle th in degrees by +90 degrees (counterclockwise).
|
||||
# Handle modulo 360 issues
|
||||
th += 90
|
||||
if th >= 360:
|
||||
th -= 360
|
||||
return th
|
||||
|
||||
def rotatethelem(L, ix):
|
||||
# Increase angle th by +90 degrees for a list element
|
||||
L[ix] = rotatetheta(L[ix])
|
||||
|
||||
class ApertureMacroPrimitive:
|
||||
def __init__(self, code=-1, fields=None):
|
||||
self.code = code
|
||||
self.parms = []
|
||||
if fields is not None:
|
||||
self.setFromFields(code, fields)
|
||||
|
||||
def setFromFields(self, code, fields):
|
||||
# code is an integer describing the primitive type, and fields is
|
||||
# a list of STRINGS for each parameter
|
||||
self.code = code
|
||||
|
||||
# valids will be one of the PrimitiveParmTypes tuples above. Some are
|
||||
# None to indicate illegal codes. We also set valids to None to indicate
|
||||
# the macro primitive code is outside the range of known types.
|
||||
try:
|
||||
valids = PrimitiveParmTypes[code]
|
||||
except:
|
||||
valids = None
|
||||
|
||||
if valids is None:
|
||||
raise RuntimeError, 'Undefined aperture macro primitive code %d' % code
|
||||
|
||||
# We expect exactly the number of fields required, except for macro
|
||||
# type 4 which is an outline and has a variable number of points.
|
||||
# For outlines, the second parameter indicates the number of points,
|
||||
# each of which has an (X,Y) co-ordinate. Thus, we expect an Outline
|
||||
# specification to have 1+1+2*N+1=3+2N fields:
|
||||
# - first field is exposure
|
||||
# - second field is number of points
|
||||
# - 2*N fields for X,Y points
|
||||
# - last field is rotation
|
||||
if self.code==4:
|
||||
if len(fields) < 2:
|
||||
raise RuntimeError, 'Outline macro primitive has way too few fields'
|
||||
|
||||
try:
|
||||
N = int(fields[1])
|
||||
except:
|
||||
raise RuntimeError, 'Outline macro primitive has non-integer number of points'
|
||||
|
||||
if len(fields) != (3+2*N):
|
||||
raise RuntimeError, 'Outline macro primitive has %d fields...expecting %d fields' % (len(fields), 3+2*N)
|
||||
else:
|
||||
if len(fields) != len(valids):
|
||||
raise RuntimeError, 'Macro primitive has %d fields...expecting %d fields' % (len(fields), len(valids))
|
||||
|
||||
# Convert each parameter on the input line to an entry in the self.parms
|
||||
# list, using either int() or float() conversion.
|
||||
for parmix in range(len(fields)):
|
||||
try:
|
||||
converter = valids[parmix]
|
||||
except:
|
||||
converter = float # To handle variable number of points in Outline type
|
||||
|
||||
try:
|
||||
self.parms.append(converter(fields[parmix]))
|
||||
except:
|
||||
raise RuntimeError, 'Aperture macro primitive parameter %d has incorrect type' % (parmix+1)
|
||||
|
||||
def setFromLine(self, line):
|
||||
# Account for DOS line endings and get rid of line ending and '*' at the end
|
||||
line = line.replace('\x0D', '')
|
||||
line = line.rstrip()
|
||||
line = line.rstrip('*')
|
||||
|
||||
fields = line.split(',')
|
||||
|
||||
try:
|
||||
try:
|
||||
code = int(fields[0])
|
||||
except:
|
||||
raise RuntimeError, 'Illegal aperture macro primitive code "%s"' % fields[0]
|
||||
self.setFromFields(code, fields[1:])
|
||||
except:
|
||||
print '='*20
|
||||
print "==> ", line
|
||||
print '='*20
|
||||
raise
|
||||
|
||||
def rotate(self):
|
||||
if self.code == 1: # Circle: nothing to do
|
||||
pass
|
||||
elif self.code in (2,20): # Line (vector): fields (2,3) and (4,5) must be rotated, no need to
|
||||
# rotate field 6
|
||||
rotatexypair(self.parms, 2)
|
||||
rotatexypair(self.parms, 4)
|
||||
elif self.code == 21: # Line (center): fields (3,4) must be rotated, and field 5 incremented by +90
|
||||
rotatexypair(self.parms, 3)
|
||||
rotatethelem(self.parms, 5)
|
||||
elif self.code == 22: # Line (lower-left): fields (3,4) must be rotated, and field 5 incremented by +90
|
||||
rotatexypair(self.parms, 3)
|
||||
rotatethelem(self.parms, 5)
|
||||
elif self.code == 4: # Outline: fields (2,3), (4,5), etc. must be rotated, the last field need not be incremented
|
||||
ix = 2
|
||||
for pts in range(self.parms[1]): # parms[1] is the number of points
|
||||
rotatexypair(self.parms, ix)
|
||||
ix += 2
|
||||
#rotatethelem(self.parms, ix)
|
||||
elif self.code == 5: # Polygon: fields (2,3) must be rotated, and field 5 incremented by +90
|
||||
rotatexypair(self.parms, 2)
|
||||
rotatethelem(self.parms, 5)
|
||||
elif self.code == 6: # Moire: fields (0,1) must be rotated, and field 8 incremented by +90
|
||||
rotatexypair(self.parms, 0)
|
||||
rotatethelem(self.parms, 8)
|
||||
elif self.code == 7: # Thermal: fields (0,1) must be rotated, and field 5 incremented by +90
|
||||
rotatexypair(self.parms, 0)
|
||||
rotatethelem(self.parms, 5)
|
||||
|
||||
def __str__(self):
|
||||
# Construct a string with ints as ints and floats as floats
|
||||
s = '%d' % self.code
|
||||
for parmix in range(len(self.parms)):
|
||||
valids = PrimitiveParmTypes[self.code]
|
||||
|
||||
format = ',%f'
|
||||
try:
|
||||
if valids[parmix] is int:
|
||||
format = ',%d'
|
||||
except:
|
||||
pass # '%f' is OK for Outline extra points
|
||||
|
||||
s += format % self.parms[parmix]
|
||||
|
||||
return s
|
||||
|
||||
def writeDef(self, fid):
|
||||
fid.write('%s*\n' % str(self))
|
||||
|
||||
class ApertureMacro:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.prim = []
|
||||
|
||||
def add(self, prim):
|
||||
self.prim.append(prim)
|
||||
|
||||
def rotate(self):
|
||||
for prim in self.prim:
|
||||
prim.rotate()
|
||||
|
||||
def rotated(self):
|
||||
# Return copy of ourselves, rotated. Replace 'R' as the first letter of the
|
||||
# macro name. We don't append because we like to be able to count the
|
||||
# number of aperture macros by stripping off the leading character.
|
||||
M = copy.deepcopy(self)
|
||||
M.rotate()
|
||||
M.name = 'R'+M.name[1:]
|
||||
return M
|
||||
|
||||
def dump(self, fid=sys.stdout):
|
||||
fid.write(str(self))
|
||||
|
||||
def __str__(self):
|
||||
s = '%s:\n' % self.name
|
||||
s += self.hash()
|
||||
return s
|
||||
|
||||
def hash(self):
|
||||
s = ''
|
||||
for prim in self.prim:
|
||||
s += ' '+str(prim)+'\n'
|
||||
return s
|
||||
|
||||
def writeDef(self, fid):
|
||||
fid.write('%%AM%s*\n' % self.name)
|
||||
for prim in self.prim:
|
||||
prim.writeDef(fid)
|
||||
fid.write('%\n')
|
||||
|
||||
def parseApertureMacro(s, fid):
|
||||
match = _macro_pat.match(s)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
|
||||
M = ApertureMacro(name)
|
||||
|
||||
for line in fid:
|
||||
if line[0]=='%':
|
||||
return M
|
||||
|
||||
P = ApertureMacroPrimitive()
|
||||
P.setFromLine(line)
|
||||
|
||||
M.add(P)
|
||||
else:
|
||||
raise RuntimeError, "Premature end-of-file while parsing aperture macro"
|
||||
else:
|
||||
return None
|
||||
|
||||
# This function adds the new aperture macro AM to the global aperture macro
|
||||
# table. The return value is the modified macro (name modified to be its global
|
||||
# name). macro.
|
||||
def addToApertureMacroTable(AM):
|
||||
GAMT = config.GAMT
|
||||
|
||||
# Must sort keys by integer value, not string since 99 comes before 100
|
||||
# as an integer but not a string.
|
||||
keys = map(int, map(lambda K: K[1:], GAMT.keys()))
|
||||
keys.sort()
|
||||
|
||||
if len(keys):
|
||||
lastCode = keys[-1]
|
||||
else:
|
||||
lastCode = 0
|
||||
|
||||
mcode = 'M%d' % (lastCode+1)
|
||||
AM.name = mcode
|
||||
GAMT[mcode] = AM
|
||||
|
||||
return AM
|
||||
|
||||
if __name__=="__main__":
|
||||
# Create a funky aperture macro with all the fixins, and make sure
|
||||
# it rotates properly.
|
||||
M = ApertureMacro('TEST')
|
||||
|
||||
# X and Y axes
|
||||
M.add(ApertureMacroPrimitive(2, ('1', '0.0025', '0.0', '-0.1', '0.0', '0.1', '0.0')))
|
||||
M.add(ApertureMacroPrimitive(2, ('1', '0.0025', '0.0', '-0.1', '0.0', '0.1', '90.0')))
|
||||
|
||||
# A circle in the top-right quadrant, touching the axes
|
||||
M.add(ApertureMacroPrimitive(1, ('1', '0.02', '0.01', '0.01')))
|
||||
# A line of slope -1 centered on the above circle, of thickness 5mil, length 0.05
|
||||
M.add(ApertureMacroPrimitive(2, ('1', '0.005', '0.0', '0.02', '0.02', '0.0', '0.0')))
|
||||
# A narrow vertical rectangle centered on the circle of width 2.5mil
|
||||
M.add(ApertureMacroPrimitive(21, ('1', '0.0025', '0.03', '0.01', '0.01', '0.0')))
|
||||
# A 45-degree line in the third quadrant, not quite touching the origin
|
||||
M.add(ApertureMacroPrimitive(22, ('1', '0.02', '0.01', '-0.03', '-0.03', '45')))
|
||||
# A right triangle in the second quadrant
|
||||
M.add(ApertureMacroPrimitive(4, ('1', '4', '-0.03', '0.01', '-0.03', '0.03', '-0.01', '0.01', '-0.03', '0.01', '0.0')))
|
||||
# A pentagon in the fourth quadrant, rotated by 15 degrees
|
||||
M.add(ApertureMacroPrimitive(5, ('1', '5', '0.03', '-0.03', '0.02', '15')))
|
||||
# A moire in the first quadrant, beyond the circle, with 2 annuli
|
||||
M.add(ApertureMacroPrimitive(6, ('0.07', '0.07', '0.04', '0.005', '0.01', '2', '0.005', '0.04', '0.0')))
|
||||
# A thermal in the second quadrant, beyond the right triangle
|
||||
M.add(ApertureMacroPrimitive(7, ('-0.07', '0.07', '0.03', '0.02', '0.005', '15')))
|
||||
|
||||
MR = M.rotated()
|
||||
|
||||
# Generate the Gerber so we can view it
|
||||
fid = file('amacro.ger', 'wt')
|
||||
print >> fid, \
|
||||
"""G75*
|
||||
G70*
|
||||
%OFA0B0*%
|
||||
%FSLAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%"""
|
||||
M.writeDef(fid)
|
||||
MR.writeDef(fid)
|
||||
print >> fid, \
|
||||
"""%ADD10TEST*%
|
||||
%ADD11TESTR*%
|
||||
D10*
|
||||
X010000Y010000D03*
|
||||
D11*
|
||||
X015000Y010000D03*
|
||||
M02*"""
|
||||
fid.close()
|
||||
|
||||
print M
|
||||
print MR
|
BIN
gerber/gerbmerge/bin/amacro.pyc
Normal file
BIN
gerber/gerbmerge/bin/amacro.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/amacro.pyo
Normal file
BIN
gerber/gerbmerge/bin/amacro.pyo
Normal file
Binary file not shown.
335
gerber/gerbmerge/bin/aptable.py
Normal file
335
gerber/gerbmerge/bin/aptable.py
Normal file
|
@ -0,0 +1,335 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Manage apertures, read aperture table, etc.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import string
|
||||
|
||||
import config
|
||||
import amacro
|
||||
import util
|
||||
|
||||
# Recognized apertures and re pattern that matches its definition Thermals and
|
||||
# annuli are generated using macros (see the eagle.def file) but only on inner
|
||||
# layers. Octagons are also generated as macros (%AMOC8) but we handle these
|
||||
# specially as the Eagle macro uses a replaceable macro parameter ($1) and
|
||||
# GerbMerge doesn't handle these yet...only fixed macros (no parameters) are
|
||||
# currently supported.
|
||||
Apertures = (
|
||||
('Rectangle', re.compile(r'^%AD(D\d+)R,([^X]+)X([^*]+)\*%$'), '%%AD%sR,%.5fX%.5f*%%\n'),
|
||||
('Circle', re.compile(r'^%AD(D\d+)C,([^*]+)\*%$'), '%%AD%sC,%.5f*%%\n'),
|
||||
('Oval', re.compile(r'^%AD(D\d+)O,([^X]+)X([^*]+)\*%$'), '%%AD%sO,%.5fX%.5f*%%\n'),
|
||||
('Octagon', re.compile(r'^%AD(D\d+)OC8,([^*]+)\*%$'), '%%AD%sOC8,%.5f*%%\n'), # Specific to Eagle
|
||||
('Macro', re.compile(r'^%AD(D\d+)([^*]+)\*%$'), '%%AD%s%s*%%\n')
|
||||
)
|
||||
|
||||
# This loop defines names in this module like 'Rectangle',
|
||||
# which are element 0 of the Apertures list above. So code
|
||||
# will be like:
|
||||
# import aptable
|
||||
# A = aptable.Aperture(aptable.Rectangle, ......)
|
||||
|
||||
for ap in Apertures:
|
||||
globals()[ap[0]] = ap
|
||||
|
||||
class Aperture:
|
||||
def __init__(self, aptype, code, dimx, dimy=None):
|
||||
assert aptype in Apertures
|
||||
self.apname, self.pat, self.format = aptype
|
||||
self.code = code
|
||||
self.dimx = dimx # Macro name for Macro apertures
|
||||
self.dimy = dimy # None for Macro apertures
|
||||
|
||||
if self.apname in ('Circle', 'Octagon', 'Macro'):
|
||||
assert (dimy is None)
|
||||
|
||||
def isRectangle(self):
|
||||
return self.apname == 'Rectangle'
|
||||
|
||||
def rectangleAsRect(self, X, Y):
|
||||
"""Return a 4-tuple (minx,miny,maxx,maxy) describing the area covered by
|
||||
this Rectangle aperture when flashed at center co-ordinates (X,Y)"""
|
||||
dx = util.in2gerb(self.dimx)
|
||||
dy = util.in2gerb(self.dimy)
|
||||
|
||||
if dx & 1: # Odd-sized: X extents are (dx+1)/2 on the left and (dx-1)/2 on the right
|
||||
xm = (dx+1)/2
|
||||
xp = xm-1
|
||||
else: # Even-sized: X extents are X-dx/2 and X+dx/2
|
||||
xm = xp = dx/2
|
||||
|
||||
if dy & 1: # Odd-sized: Y extents are (dy+1)/2 below and (dy-1)/2 above
|
||||
ym = (dy+1)/2
|
||||
yp = ym-1
|
||||
else: # Even-sized: Y extents are Y-dy/2 and Y+dy/2
|
||||
ym = yp = dy/2
|
||||
|
||||
return (X-xm, Y-ym, X+xp, Y+yp)
|
||||
|
||||
def getAdjusted(self, minimum):
|
||||
"""
|
||||
Adjust aperture properties to conform to minimum feature dimensions
|
||||
Return new aperture if required, else return False
|
||||
"""
|
||||
dimx = dimy = None
|
||||
|
||||
# Check for X and Y dimensions less than minimum
|
||||
if (self.dimx != None) and (self.dimx < minimum):
|
||||
dimx = minimum
|
||||
if (self.dimy != None) and (self.dimx < minimum):
|
||||
dimy = minimum
|
||||
|
||||
# Return new aperture if needed
|
||||
if (dimx != None) or (dimy != None):
|
||||
if dimx==None: dimx=self.dimx
|
||||
if dimy==None: dimy=self.dimy
|
||||
return Aperture( (self.apname, self.pat, self.format), self.code, dimx, dimy )
|
||||
else:
|
||||
return False ## no new aperture needs to be created
|
||||
|
||||
def rotate(self, RevGAMT):
|
||||
if self.apname in ('Macro',):
|
||||
# Construct a rotated macro, see if it's in the GAMT, and set self.dimx
|
||||
# to its name if so. If not, add the rotated macro to the GAMT and set
|
||||
# self.dimx to the new name. Recall that GAMT maps name to macro
|
||||
# (e.g., GAMT['M9'] = ApertureMacro(...)) while RevGAMT maps hash to
|
||||
# macro name (e.g., RevGAMT[hash] = 'M9')
|
||||
AMR = config.GAMT[self.dimx].rotated()
|
||||
hash = AMR.hash()
|
||||
try:
|
||||
self.dimx = RevGAMT[hash]
|
||||
except KeyError:
|
||||
AMR = amacro.addToApertureMacroTable(AMR) # adds to GAMT and modifies name to global name
|
||||
self.dimx = RevGAMT[hash] = AMR.name
|
||||
|
||||
elif self.dimy is not None: # Rectangles and Ovals have a dimy setting and need to be rotated
|
||||
t = self.dimx
|
||||
self.dimx = self.dimy
|
||||
self.dimy = t
|
||||
|
||||
def rotated(self, RevGAMT):
|
||||
# deepcopy doesn't work on re patterns for some reason so we copy ourselves manually
|
||||
APR = Aperture((self.apname, self.pat, self.format), self.code, self.dimx, self.dimy)
|
||||
APR.rotate(RevGAMT)
|
||||
return APR
|
||||
|
||||
def dump(self, fid=sys.stdout):
|
||||
fid.write(str(self))
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s' % (self.code, self.hash())
|
||||
#if 0:
|
||||
# if self.dimy:
|
||||
# return ('%s: %s (%.4f x %.4f)' % (self.code, self.apname, self.dimx, self.dimy))
|
||||
# else:
|
||||
# if self.apname in ('Macro'):
|
||||
# return ('%s: %s (%s)' % (self.code, self.apname, self.dimx))
|
||||
# else:
|
||||
# return ('%s: %s (%.4f)' % (self.code, self.apname, self.dimx))
|
||||
|
||||
def hash(self):
|
||||
if self.dimy:
|
||||
return ('%s (%.5f x %.5f)' % (self.apname, self.dimx, self.dimy))
|
||||
else:
|
||||
if self.apname in ('Macro',):
|
||||
return ('%s (%s)' % (self.apname, self.dimx))
|
||||
else:
|
||||
return ('%s (%.5f)' % (self.apname, self.dimx))
|
||||
|
||||
def writeDef(self, fid):
|
||||
if self.dimy:
|
||||
fid.write(self.format % (self.code, self.dimx, self.dimy))
|
||||
else:
|
||||
fid.write(self.format % (self.code, self.dimx))
|
||||
|
||||
# Parse the aperture definition in line 's'. macroNames is an aperture macro dictionary
|
||||
# that translates macro names local to this file to global names in the GAMT. We make
|
||||
# the translation right away so that the return value from this function is an aperture
|
||||
# definition with a global macro name, e.g., 'ADD10M5'
|
||||
def parseAperture(s, knownMacroNames):
|
||||
for ap in Apertures:
|
||||
match = ap[1].match(s)
|
||||
if match:
|
||||
dimy = None
|
||||
if ap[0] in ('Circle', 'Octagon', 'Macro'):
|
||||
code, dimx = match.groups()
|
||||
else:
|
||||
code, dimx, dimy = match.groups()
|
||||
|
||||
if ap[0] in ('Macro',):
|
||||
if knownMacroNames.has_key(dimx):
|
||||
dimx = knownMacroNames[dimx] # dimx is now GLOBAL, permanent macro name (e.g., 'M2')
|
||||
else:
|
||||
raise RuntimeError, 'Aperture Macro name "%s" not defined' % dimx
|
||||
else:
|
||||
try:
|
||||
dimx = float(dimx)
|
||||
if dimy:
|
||||
dimy = float(dimy)
|
||||
except:
|
||||
raise RuntimeError, "Illegal floating point aperture size"
|
||||
|
||||
return Aperture(ap, code, dimx, dimy)
|
||||
|
||||
return None
|
||||
|
||||
# This function returns a dictionary where each key is an
|
||||
# aperture code string (e.g., "D11") and the value is the
|
||||
# Aperture object that represents it. For example:
|
||||
#
|
||||
# %ADD12R,0.0630X0.0630*%
|
||||
#
|
||||
# from a Gerber file would result in the dictionary entry:
|
||||
#
|
||||
# "D12": Aperture(ap, 'D10', 0.063, 0.063)
|
||||
#
|
||||
# The input fileList is a list of pathnames which will be read to construct the
|
||||
# aperture table for a job. All the files in the given list will be so
|
||||
# examined, and a global aperture table will be constructed as a dictionary.
|
||||
# Same goes for the global aperture macro table.
|
||||
|
||||
tool_pat = re.compile(r'^(?:G54)?D\d+\*$')
|
||||
|
||||
def constructApertureTable(fileList):
|
||||
# First we construct a dictionary where each key is the
|
||||
# string representation of the aperture. Then we go back and assign
|
||||
# numbers. For aperture macros, we construct their final version
|
||||
# (i.e., 'M1', 'M2', etc.) right away, as they are parsed. Thus,
|
||||
# we translate from 'THX10N' or whatever to 'M2' right away.
|
||||
GAT = config.GAT # Global Aperture Table
|
||||
GAT.clear()
|
||||
GAMT = config.GAMT # Global Aperture Macro Table
|
||||
GAMT.clear()
|
||||
RevGAMT = {} # Dictionary keyed by aperture macro hash and returning macro name
|
||||
|
||||
AT = {} # Aperture Table for this file
|
||||
for fname in fileList:
|
||||
#print 'Reading apertures from %s ...' % fname
|
||||
|
||||
knownMacroNames = {}
|
||||
|
||||
fid = file(fname,'rt')
|
||||
for line in fid:
|
||||
# Get rid of CR
|
||||
line = line.replace('\x0D', '')
|
||||
|
||||
if tool_pat.match(line):
|
||||
break # When tools start, no more apertures are being defined
|
||||
|
||||
# If this is an aperture macro definition, add its string
|
||||
# representation to the dictionary. It might already exist.
|
||||
# Ignore %AMOC8* from Eagle for now as it uses a macro parameter.
|
||||
if line[:7]=='%AMOC8*':
|
||||
continue
|
||||
|
||||
# parseApertureMacro() sucks up all macro lines up to terminating '%'
|
||||
AM = amacro.parseApertureMacro(line, fid)
|
||||
if AM:
|
||||
# Has this macro definition already been defined (perhaps by another name
|
||||
# in another layer)?
|
||||
try:
|
||||
# If this macro has already been encountered anywhere in any job,
|
||||
# RevGAMT will map the macro hash to the global macro name. Then,
|
||||
# make the local association knownMacroNames[localMacroName] = globalMacroName.
|
||||
knownMacroNames[AM.name] = RevGAMT[AM.hash()]
|
||||
except KeyError:
|
||||
# No, so define the global macro and do the translation. Note that
|
||||
# addToApertureMacroTable() MODIFIES AM.name to the new M-name.
|
||||
localMacroName = AM.name
|
||||
AM = amacro.addToApertureMacroTable(AM)
|
||||
knownMacroNames[localMacroName] = AM.name
|
||||
RevGAMT[AM.hash()] = AM.name
|
||||
else:
|
||||
A = parseAperture(line, knownMacroNames)
|
||||
|
||||
# If this is an aperture definition, add the string representation
|
||||
# to the dictionary. It might already exist.
|
||||
if A:
|
||||
AT[A.hash()] = A
|
||||
|
||||
fid.close()
|
||||
|
||||
# Now, go through and assign sequential codes to all apertures
|
||||
code = 10
|
||||
for val in AT.values():
|
||||
key = 'D%d' % code
|
||||
GAT[key] = val
|
||||
val.code = key
|
||||
code += 1
|
||||
|
||||
if 0:
|
||||
keylist = config.GAT.keys()
|
||||
keylist.sort()
|
||||
print 'Apertures'
|
||||
print '========='
|
||||
for key in keylist:
|
||||
print '%s' % config.GAT[key]
|
||||
sys.exit(0)
|
||||
|
||||
def findHighestApertureCode(keys):
|
||||
"Find the highest integer value in a list of aperture codes: ['D10', 'D23', 'D35', ...]"
|
||||
|
||||
# Must sort keys by integer value, not string since 99 comes before 100
|
||||
# as an integer but not a string.
|
||||
keys = [int(K[1:]) for K in keys]
|
||||
keys.sort()
|
||||
|
||||
return keys[-1]
|
||||
|
||||
def addToApertureTable(AP):
|
||||
GAT = config.GAT
|
||||
|
||||
lastCode = findHighestApertureCode(GAT.keys())
|
||||
code = 'D%d' % (lastCode+1)
|
||||
GAT[code] = AP
|
||||
AP.code = code
|
||||
|
||||
return code
|
||||
|
||||
def findInApertureTable(AP):
|
||||
"""Return 'D10', for example in response to query for an object
|
||||
of type Aperture()"""
|
||||
hash = AP.hash()
|
||||
for key, val in config.GAT.items():
|
||||
if hash==val.hash():
|
||||
return key
|
||||
|
||||
return None
|
||||
|
||||
def findOrAddAperture(AP):
|
||||
"""If the aperture exists in the GAT, modify the AP.code field to reflect the global code
|
||||
and return the code. Otherwise, create a new aperture in the GAT and return the new code
|
||||
for it."""
|
||||
code = findInApertureTable(AP)
|
||||
if code:
|
||||
AP.code = code
|
||||
return code
|
||||
else:
|
||||
return addToApertureTable(AP)
|
||||
|
||||
if __name__=="__main__":
|
||||
constructApertureTable(sys.argv[1:])
|
||||
|
||||
keylist = config.GAMT.keys()
|
||||
keylist.sort()
|
||||
print 'Aperture Macros'
|
||||
print '==============='
|
||||
for key in keylist:
|
||||
print '%s' % config.GAMT[key]
|
||||
|
||||
keylist = config.GAT.keys()
|
||||
keylist.sort()
|
||||
print 'Apertures'
|
||||
print '========='
|
||||
for key in keylist:
|
||||
print '%s' % config.GAT[key]
|
BIN
gerber/gerbmerge/bin/aptable.pyc
Normal file
BIN
gerber/gerbmerge/bin/aptable.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/aptable.pyo
Normal file
BIN
gerber/gerbmerge/bin/aptable.pyo
Normal file
Binary file not shown.
399
gerber/gerbmerge/bin/config.py
Normal file
399
gerber/gerbmerge/bin/config.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Parse the GerbMerge configuration file.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import ConfigParser
|
||||
import re
|
||||
import string
|
||||
|
||||
import jobs
|
||||
import aptable
|
||||
|
||||
# Configuration dictionary. Specify floats as strings. Ints can be specified
|
||||
# as ints or strings.
|
||||
Config = {
|
||||
'xspacing': '0.125', # Spacing in horizontal direction
|
||||
'yspacing': '0.125', # Spacing in vertical direction
|
||||
'panelwidth': '12.6', # X-Dimension maximum panel size (Olimex)
|
||||
'panelheight': '7.8', # Y-Dimension maximum panel size (Olimex)
|
||||
'cropmarklayers': None, # e.g., *toplayer,*bottomlayer
|
||||
'cropmarkwidth': '0.01', # Width (inches) of crop lines
|
||||
'cutlinelayers': None, # as for cropmarklayers
|
||||
'cutlinewidth': '0.01', # Width (inches) of cut lines
|
||||
'minimumfeaturesize': 0, # Minimum dimension for selected layers
|
||||
'toollist': None, # Name of file containing default tool list
|
||||
'drillclustertolerance': '.002', # Tolerance for clustering drill sizes
|
||||
'allowmissinglayers': 0, # Set to 1 to allow multiple jobs to have non-matching layers
|
||||
'fabricationdrawingfile': None, # Name of file to which to write fabrication drawing, or None
|
||||
'fabricationdrawingtext': None, # Name of file containing text to write to fab drawing
|
||||
'excellondecimals': 4, # Number of digits after the decimal point in input Excellon files
|
||||
'excellonleadingzeros': 0, # Generate leading zeros in merged Excellon output file
|
||||
'outlinelayerfile': None, # Name of file to which to write simple box outline, or None
|
||||
'scoringfile': None, # Name of file to which to write scoring data, or None
|
||||
'leftmargin': 0, # Inches of extra room to leave on left side of panel for tooling
|
||||
'topmargin': 0, # Inches of extra room to leave on top side of panel for tooling
|
||||
'rightmargin': 0, # Inches of extra room to leave on right side of panel for tooling
|
||||
'bottommargin': 0, # Inches of extra room to leave on bottom side of panel for tooling
|
||||
'fiducialpoints': None, # List of X,Y co-ordinates at which to draw fiducials
|
||||
'fiducialcopperdiameter': 0.08, # Diameter of copper part of fiducial
|
||||
'fiducialmaskdiameter': 0.32, # Diameter of fiducial soldermask opening
|
||||
}
|
||||
|
||||
# This dictionary is indexed by lowercase layer name and has as values a file
|
||||
# name to use for the output.
|
||||
MergeOutputFiles = {
|
||||
'boardoutline': 'merged.boardoutline.ger',
|
||||
'drills': 'merged.drills.xln',
|
||||
'placement': 'merged.placement.txt',
|
||||
'toollist': 'merged.toollist.drl'
|
||||
}
|
||||
|
||||
# The global aperture table, indexed by aperture code (e.g., 'D10')
|
||||
GAT = {}
|
||||
|
||||
# The global aperture macro table, indexed by macro name (e.g., 'M3', 'M4R' for rotated macros)
|
||||
GAMT = {}
|
||||
|
||||
# The list of all jobs loaded, indexed by job name (e.g., 'PowerBoard')
|
||||
Jobs = {}
|
||||
|
||||
# The set of all Gerber layer names encountered in all jobs. Doesn't
|
||||
# include drills.
|
||||
LayerList = {'boardoutline': 1}
|
||||
|
||||
# The tool list as read in from the DefaultToolList file in the configuration
|
||||
# file. This is a dictionary indexed by tool name (e.g., 'T03') and
|
||||
# a floating point number as the value, the drill diameter in inches.
|
||||
DefaultToolList = {}
|
||||
|
||||
# The GlobalToolMap dictionary maps tool name to diameter in inches. It
|
||||
# is initially empty and is constructed after all files are read in. It
|
||||
# only contains actual tools used in jobs.
|
||||
GlobalToolMap = {}
|
||||
|
||||
# The GlobalToolRMap dictionary is a reverse dictionary of ToolMap, i.e., it maps
|
||||
# diameter to tool name.
|
||||
GlobalToolRMap = {}
|
||||
|
||||
##############################################################################
|
||||
|
||||
# This configuration option determines whether trimGerber() is called
|
||||
TrimGerber = 1
|
||||
|
||||
# This configuration option determines whether trimExcellon() is called
|
||||
TrimExcellon = 1
|
||||
|
||||
# This configuration option determines the minimum size of feature dimensions for
|
||||
# each layer. It is a dictionary indexed by layer name (e.g. '*topsilkscreen') and
|
||||
# has a floating point number as the value (in inches).
|
||||
MinimumFeatureDimension = {}
|
||||
|
||||
# This configuration option is a positive integer that determines the maximum
|
||||
# amout of time to allow for random placements (seconds). A SearchTimeout of 0
|
||||
# indicates that no timeout should occur and random placements will occur
|
||||
# forever until a KeyboardInterrupt is raised.
|
||||
SearchTimeout = 0
|
||||
|
||||
# Construct the reverse-GAT/GAMT translation table, keyed by aperture/aperture macro
|
||||
# hash string. The value is the aperture code (e.g., 'D10') or macro name (e.g., 'M5').
|
||||
def buildRevDict(D):
|
||||
RevD = {}
|
||||
for key,val in D.items():
|
||||
RevD[val.hash()] = key
|
||||
return RevD
|
||||
|
||||
def parseStringList(L):
|
||||
"""Parse something like '*toplayer, *bottomlayer' into a list of names
|
||||
without quotes, spaces, etc."""
|
||||
|
||||
if 0:
|
||||
if L[0]=="'":
|
||||
if L[-1] != "'":
|
||||
raise RuntimeError, "Illegal configuration string '%s'" % L
|
||||
L = L[1:-1]
|
||||
|
||||
elif L[0]=='"':
|
||||
if L[-1] != '"':
|
||||
raise RuntimeError, "Illegal configuration string '%s'" % L
|
||||
L = L[1:-1]
|
||||
|
||||
# This pattern matches quotes at the beginning and end...quotes must match
|
||||
quotepat = re.compile(r'^([' "'" '"' r']?)([^\1]*)\1$')
|
||||
delimitpat = re.compile(r'[ \t]*[,;][ \t]*')
|
||||
|
||||
match = quotepat.match(L)
|
||||
if match:
|
||||
L = match.group(2)
|
||||
|
||||
return delimitpat.split(L)
|
||||
|
||||
# Parse an Excellon tool list file of the form
|
||||
#
|
||||
# T01 0.035in
|
||||
# T02 0.042in
|
||||
def parseToolList(fname):
|
||||
TL = {}
|
||||
|
||||
try:
|
||||
fid = file(fname, 'rt')
|
||||
except Exception, detail:
|
||||
raise RuntimeError, "Unable to open tool list file '%s':\n %s" % (fname, str(detail))
|
||||
|
||||
pat_in = re.compile(r'\s*(T\d+)\s+([0-9.]+)\s*in\s*')
|
||||
pat_mm = re.compile(r'\s*(T\d+)\s+([0-9.]+)\s*mm\s*')
|
||||
pat_mil = re.compile(r'\s*(T\d+)\s+([0-9.]+)\s*(?:mil)?')
|
||||
for line in fid.xreadlines():
|
||||
line = string.strip(line)
|
||||
if (not line) or (line[0] in ('#', ';')): continue
|
||||
|
||||
mm = 0
|
||||
mil = 0
|
||||
match = pat_in.match(line)
|
||||
if not match:
|
||||
mm = 1
|
||||
match = pat_mm.match(line)
|
||||
if not match:
|
||||
mil = 1
|
||||
match = pat_mil.match(line)
|
||||
if not match:
|
||||
continue
|
||||
#raise RuntimeError, "Illegal tool list specification:\n %s" % line
|
||||
|
||||
tool, size = match.groups()
|
||||
|
||||
try:
|
||||
size = float(size)
|
||||
except:
|
||||
raise RuntimeError, "Tool size in file '%s' is not a valid floating-point number:\n %s" % (fname,line)
|
||||
|
||||
if mil:
|
||||
size = size*0.001 # Convert mil to inches
|
||||
elif mm:
|
||||
size = size/25.4 # Convert mm to inches
|
||||
|
||||
# Canonicalize tool so that T1 becomes T01
|
||||
tool = 'T%02d' % int(tool[1:])
|
||||
|
||||
if TL.has_key(tool):
|
||||
raise RuntimeError, "Tool '%s' defined more than once in tool list file '%s'" % (tool,fname)
|
||||
|
||||
TL[tool]=size
|
||||
fid.close()
|
||||
|
||||
return TL
|
||||
|
||||
# This function parses the job configuration file and does
|
||||
# everything needed to:
|
||||
#
|
||||
# * parse global options and store them in the Config dictionary
|
||||
# as natural types (i.e., ints, floats, lists)
|
||||
#
|
||||
# * Read Gerber/Excellon data and populate the Jobs dictionary
|
||||
#
|
||||
# * Read Gerber/Excellon data and populate the global aperture
|
||||
# table, GAT, and the global aperture macro table, GAMT
|
||||
#
|
||||
# * read the tool list file and populate the DefaultToolList dictionary
|
||||
def parseConfigFile(fname, Config=Config, Jobs=Jobs):
|
||||
global DefaultToolList
|
||||
|
||||
CP = ConfigParser.ConfigParser()
|
||||
CP.readfp(file(fname,'rt'))
|
||||
|
||||
# First parse global options
|
||||
if CP.has_section('Options'):
|
||||
for opt in CP.options('Options'):
|
||||
# Is it one we expect
|
||||
if Config.has_key(opt):
|
||||
# Yup...override it
|
||||
Config[opt] = CP.get('Options', opt)
|
||||
|
||||
elif CP.defaults().has_key(opt):
|
||||
pass # Ignore DEFAULTS section keys
|
||||
|
||||
elif opt in ('fabricationdrawing', 'outlinelayer'):
|
||||
print '*'*73
|
||||
print '\nThe FabricationDrawing and OutlineLayer configuration options have been'
|
||||
print 'renamed as of GerbMerge version 1.0. Please consult the documentation for'
|
||||
print 'a description of the new options, then modify your configuration file.\n'
|
||||
print '*'*73
|
||||
sys.exit(1)
|
||||
else:
|
||||
raise RuntimeError, "Unknown option '%s' in [Options] section of configuration file" % opt
|
||||
else:
|
||||
raise RuntimeError, "Missing [Options] section in configuration file"
|
||||
|
||||
# Ensure we got a tool list
|
||||
if not Config.has_key('toollist'):
|
||||
raise RuntimeError, "INTERNAL ERROR: Missing tool list assignment in [Options] section"
|
||||
|
||||
# Make integers integers, floats floats
|
||||
for key,val in Config.items():
|
||||
try:
|
||||
val = int(val)
|
||||
Config[key]=val
|
||||
except:
|
||||
try:
|
||||
val = float(val)
|
||||
Config[key]=val
|
||||
except:
|
||||
pass
|
||||
|
||||
# Process lists of strings
|
||||
if Config['cutlinelayers']:
|
||||
Config['cutlinelayers'] = parseStringList(Config['cutlinelayers'])
|
||||
if Config['cropmarklayers']:
|
||||
Config['cropmarklayers'] = parseStringList(Config['cropmarklayers'])
|
||||
|
||||
# Process list of minimum feature dimensions
|
||||
if Config['minimumfeaturesize']:
|
||||
temp = Config['minimumfeaturesize'].split(",")
|
||||
try:
|
||||
for index in range(0, len(temp), 2):
|
||||
MinimumFeatureDimension[ temp[index] ] = float( temp[index + 1] )
|
||||
except:
|
||||
raise RuntimeError, "Illegal configuration string:" + Config['minimumfeaturesize']
|
||||
|
||||
# Process MergeOutputFiles section to set output file names
|
||||
if CP.has_section('MergeOutputFiles'):
|
||||
for opt in CP.options('MergeOutputFiles'):
|
||||
# Each option is a layer name and the output file for this name
|
||||
if opt[0]=='*' or opt in ('boardoutline', 'drills', 'placement', 'toollist'):
|
||||
MergeOutputFiles[opt] = CP.get('MergeOutputFiles', opt)
|
||||
|
||||
# Now, we go through all jobs and collect Gerber layers
|
||||
# so we can construct the Global Aperture Table.
|
||||
apfiles = []
|
||||
|
||||
for jobname in CP.sections():
|
||||
if jobname=='Options': continue
|
||||
if jobname=='MergeOutputFiles': continue
|
||||
if jobname=='GerbMergeGUI': continue
|
||||
|
||||
# Ensure all jobs have a board outline
|
||||
if not CP.has_option(jobname, 'boardoutline'):
|
||||
raise RuntimeError, "Job '%s' does not have a board outline specified" % jobname
|
||||
|
||||
if not CP.has_option(jobname, 'drills'):
|
||||
raise RuntimeError, "Job '%s' does not have a drills layer specified" % jobname
|
||||
|
||||
for layername in CP.options(jobname):
|
||||
if layername[0]=='*' or layername=='boardoutline':
|
||||
fname = CP.get(jobname, layername)
|
||||
apfiles.append(fname)
|
||||
|
||||
if layername[0]=='*':
|
||||
LayerList[layername]=1
|
||||
|
||||
# Now construct global aperture tables, GAT and GAMT. This step actually
|
||||
# reads in the jobs for aperture data but doesn't store Gerber
|
||||
# data yet.
|
||||
aptable.constructApertureTable(apfiles)
|
||||
del apfiles
|
||||
|
||||
if 0:
|
||||
keylist = GAMT.keys()
|
||||
keylist.sort()
|
||||
for key in keylist:
|
||||
print '%s' % GAMT[key]
|
||||
sys.exit(0)
|
||||
|
||||
# Parse the tool list
|
||||
if Config['toollist']:
|
||||
DefaultToolList = parseToolList(Config['toollist'])
|
||||
|
||||
# Now get jobs. Each job implies layer names, and we
|
||||
# expect consistency in layer names from one job to the
|
||||
# next. Two reserved layer names, however, are
|
||||
# BoardOutline and Drills.
|
||||
|
||||
Jobs.clear()
|
||||
|
||||
do_abort = 0
|
||||
errstr = 'ERROR'
|
||||
if Config['allowmissinglayers']:
|
||||
errstr = 'WARNING'
|
||||
|
||||
for jobname in CP.sections():
|
||||
if jobname=='Options': continue
|
||||
if jobname=='MergeOutputFiles': continue
|
||||
if jobname=='GerbMergeGUI': continue
|
||||
|
||||
print 'Reading data from', jobname, '...'
|
||||
|
||||
J = jobs.Job(jobname)
|
||||
|
||||
# Parse the job settings, like tool list, first, since we are not
|
||||
# guaranteed to have ConfigParser return the layers in the same order that
|
||||
# the user wrote them, and we may get Gerber files before we get a tool
|
||||
# list! Same thing goes for ExcellonDecimals. We need to know what this is
|
||||
# before parsing any Excellon files.
|
||||
for layername in CP.options(jobname):
|
||||
fname = CP.get(jobname, layername)
|
||||
|
||||
if layername == 'toollist':
|
||||
J.ToolList = parseToolList(fname)
|
||||
elif layername=='excellondecimals':
|
||||
try:
|
||||
J.ExcellonDecimals = int(fname)
|
||||
except:
|
||||
raise RuntimeError, "Excellon decimals '%s' in config file is not a valid integer" % fname
|
||||
elif layername=='repeat':
|
||||
try:
|
||||
J.Repeat = int(fname)
|
||||
except:
|
||||
raise RuntimeError, "Repeat count '%s' in config file is not a valid integer" % fname
|
||||
|
||||
for layername in CP.options(jobname):
|
||||
fname = CP.get(jobname, layername)
|
||||
|
||||
if layername=='boardoutline':
|
||||
J.parseGerber(fname, layername, updateExtents=1)
|
||||
elif layername[0]=='*':
|
||||
J.parseGerber(fname, layername, updateExtents=0)
|
||||
elif layername=='drills':
|
||||
J.parseExcellon(fname)
|
||||
|
||||
# Emit warnings if some layers are missing
|
||||
LL = LayerList.copy()
|
||||
for layername in J.apxlat.keys():
|
||||
assert LL.has_key(layername)
|
||||
del LL[layername]
|
||||
|
||||
if LL:
|
||||
if errstr=='ERROR':
|
||||
do_abort=1
|
||||
|
||||
print '%s: Job %s is missing the following layers:' % (errstr, jobname)
|
||||
for layername in LL.keys():
|
||||
print ' %s' % layername
|
||||
|
||||
# Store the job in the global Jobs dictionary, keyed by job name
|
||||
Jobs[jobname] = J
|
||||
|
||||
if do_abort:
|
||||
raise RuntimeError, 'Exiting since jobs are missing layers. Set AllowMissingLayers=1\nto override.'
|
||||
|
||||
if __name__=="__main__":
|
||||
CP = parseConfigFile(sys.argv[1])
|
||||
print Config
|
||||
sys.exit(0)
|
||||
|
||||
if 0:
|
||||
for key, val in CP.defaults().items():
|
||||
print '%s: %s' % (key,val)
|
||||
|
||||
for section in CP.sections():
|
||||
print '[%s]' % section
|
||||
for opt in CP.options(section):
|
||||
print ' %s=%s' % (opt, CP.get(section, opt))
|
BIN
gerber/gerbmerge/bin/config.pyc
Normal file
BIN
gerber/gerbmerge/bin/config.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/config.pyo
Normal file
BIN
gerber/gerbmerge/bin/config.pyo
Normal file
Binary file not shown.
208
gerber/gerbmerge/bin/drillcluster.py
Normal file
208
gerber/gerbmerge/bin/drillcluster.py
Normal file
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Drill clustering routines to reduce total number of drills and remap
|
||||
drilling commands to the new reduced drill set.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
_STATUS = True ## indicates status messages should be shown
|
||||
_DEBUG = False ## indicates debug and status messages should be shown
|
||||
|
||||
def cluster(drills, tolerance, debug = _DEBUG):
|
||||
"""
|
||||
Take a dictionary of drill names and sizes and cluster them
|
||||
A tolerance of 0 will effectively disable clustering
|
||||
|
||||
Returns clustered drill dictionary
|
||||
"""
|
||||
|
||||
global _DEBUG
|
||||
_DEBUG = debug
|
||||
|
||||
clusters = []
|
||||
|
||||
debug_print("\n " + str( len(drills) ) + " Original drills:")
|
||||
debug_print( drillsToString(drills) )
|
||||
debug_print("Clustering drill sizes ...", True)
|
||||
|
||||
# Loop through all drill sizes
|
||||
sizes = drills.keys()
|
||||
sizes.sort()
|
||||
for size in sizes:
|
||||
|
||||
match = False
|
||||
|
||||
# See if size fits into any current clusters, else make new cluster
|
||||
for index in range( len(clusters) ):
|
||||
c = clusters[index]
|
||||
if not len(c):
|
||||
break
|
||||
mn = min(c)
|
||||
mx = max(c)
|
||||
|
||||
##debug_print( "Validating " + str_d(size) + " in " + str_d(c) )
|
||||
##debug_print( "Possible cluster range = " + str_d(mx - 2 * tolerance) + " to " + str_d(mn + 2 * tolerance) )
|
||||
|
||||
if (size >= mx - 2 * tolerance) and (size <= mn + 2 * tolerance):
|
||||
|
||||
debug_print( str_d(size) + " belongs with " + str_d(c) )
|
||||
|
||||
clusters[index].append(size)
|
||||
match = True
|
||||
break
|
||||
|
||||
if not match:
|
||||
debug_print(str_d(size) + " belongs in a new cluster")
|
||||
clusters.append( [size] )
|
||||
|
||||
debug_print("\n Creating new drill dictionary ...")
|
||||
|
||||
new_drills = {}
|
||||
tool_num = 0
|
||||
|
||||
# Create new dictionary of clustered drills
|
||||
for c in clusters:
|
||||
tool_num += 1
|
||||
new_drill = "T%02d" % tool_num
|
||||
c.sort()
|
||||
new_size = ( min(c) + max(c) ) / 2.0
|
||||
new_drills[new_size] = new_drill
|
||||
|
||||
debug_print(str_d(c) + " will be represented by " + new_drill + " (" + str_d(new_size) + ")")
|
||||
|
||||
debug_print("\n " + str( len(new_drills) ) + " Clustered Drills:")
|
||||
debug_print( drillsToString(new_drills) )
|
||||
debug_print("Drill count reduced from " + str( len(drills) ) + " to " + str( len(new_drills) ), True)
|
||||
|
||||
return new_drills
|
||||
|
||||
def remap(jobs, globalToolMap, debug = _DEBUG):
|
||||
"""
|
||||
Remap tools and commands in all jobs to match new tool map
|
||||
|
||||
Returns None
|
||||
"""
|
||||
|
||||
# Set global variables from parameters
|
||||
global _DEBUG
|
||||
_DEBUG = debug
|
||||
|
||||
debug_print("Remapping tools and commands ...", True)
|
||||
|
||||
for job in jobs:
|
||||
job = job.job ##access job inside job layout
|
||||
debug_print("\n Job name: " + job.name)
|
||||
debug_print("\n Original job tools:")
|
||||
debug_print( str(job.xdiam) )
|
||||
debug_print("\n Original commands:")
|
||||
debug_print( str(job.xcommands) )
|
||||
new_tools = {}
|
||||
new_commands = {}
|
||||
for tool, diam in job.xdiam.items():
|
||||
|
||||
##debug_print("\n Current tool: " + tool + " (" + str_d(diam) + ")")
|
||||
|
||||
# Search for best matching tool
|
||||
best_diam, best_tool = globalToolMap[0]
|
||||
|
||||
for glob_diam, glob_tool in globalToolMap:
|
||||
if abs(glob_diam - diam) < abs(best_diam - diam):
|
||||
best_tool = glob_tool
|
||||
best_diam = glob_diam
|
||||
##debug_print("Best match: " + best_tool + " (" + str_d(best_diam) + ")")
|
||||
new_tools[best_tool] = best_diam
|
||||
##debug_print(best_tool + " will replace " + tool)
|
||||
|
||||
# Append commands to existing commands if they exist
|
||||
if best_tool in new_commands:
|
||||
##debug_print( "Current commands: " + str( new_commands[best_tool] ) )
|
||||
temp = new_commands[best_tool]
|
||||
temp.extend( job.xcommands[tool] )
|
||||
new_commands[best_tool] = temp
|
||||
##debug_print( "All commands: " + str( new_commands[best_tool] ) )
|
||||
else:
|
||||
new_commands[best_tool] = job.xcommands[tool]
|
||||
|
||||
debug_print("\n New job tools:")
|
||||
debug_print( str(new_tools) )
|
||||
debug_print("\n New commands:")
|
||||
debug_print( str(new_commands) )
|
||||
job.xdiam = new_tools
|
||||
job.xcommands = new_commands
|
||||
|
||||
def debug_print(text, status = False, newLine = True):
|
||||
"""
|
||||
Print debugging statemetns
|
||||
|
||||
Returs None, Printts text
|
||||
"""
|
||||
|
||||
if _DEBUG or (status and _STATUS):
|
||||
if newLine:
|
||||
print " ", text
|
||||
else:
|
||||
print " ", text,
|
||||
|
||||
def str_d(drills):
|
||||
"""
|
||||
Format drill sizes for printing debug and status messages
|
||||
|
||||
Returns drills as formatted string
|
||||
"""
|
||||
|
||||
string = ""
|
||||
|
||||
try:
|
||||
len(drills)
|
||||
except:
|
||||
string = "%.4f" % drills
|
||||
else:
|
||||
string = "["
|
||||
for drill in drills:
|
||||
string += ( "%.4f" % drill + ", ")
|
||||
string = string[:len(string) - 2] + "]"
|
||||
|
||||
return string
|
||||
|
||||
def drillsToString(drills):
|
||||
"""
|
||||
Format drill dictionary for printing debug and status messages
|
||||
|
||||
Returns drills as formatted string
|
||||
"""
|
||||
string = ""
|
||||
|
||||
drills = drills.items()
|
||||
drills.sort()
|
||||
for size, drill in drills:
|
||||
string += drill + " = " + str_d(size) + "\n "
|
||||
|
||||
return string
|
||||
|
||||
"""
|
||||
The following code runs test drill clusterings with random drill sets.
|
||||
"""
|
||||
|
||||
if __name__=="__main__":
|
||||
import random
|
||||
|
||||
print " Clustering random drills..."
|
||||
|
||||
old = {}
|
||||
tool_num = 0
|
||||
while len(old) < 99:
|
||||
rand_size = round(random.uniform(.02, .04), 4)
|
||||
if rand_size in old:
|
||||
continue
|
||||
tool_num += 1
|
||||
old[rand_size] = "T%02d" % tool_num
|
||||
|
||||
new = cluster(old, .0003, True)
|
BIN
gerber/gerbmerge/bin/drillcluster.pyc
Normal file
BIN
gerber/gerbmerge/bin/drillcluster.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/drillcluster.pyo
Normal file
BIN
gerber/gerbmerge/bin/drillcluster.pyo
Normal file
Binary file not shown.
210
gerber/gerbmerge/bin/fabdrawing.py
Normal file
210
gerber/gerbmerge/bin/fabdrawing.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
#!/usr/bin/env python
|
||||
"""This file handles the writing of the fabrication drawing Gerber file
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
import config
|
||||
import makestroke
|
||||
import util
|
||||
|
||||
def writeDrillHits(fid, Place, Tools):
|
||||
toolNumber = -1
|
||||
|
||||
for tool in Tools:
|
||||
toolNumber += 1
|
||||
|
||||
try:
|
||||
size = config.GlobalToolMap[tool]
|
||||
except:
|
||||
raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool list" % tool
|
||||
|
||||
#for row in Layout:
|
||||
# row.writeDrillHits(fid, size, toolNumber)
|
||||
for job in Place.jobs:
|
||||
job.writeDrillHits(fid, size, toolNumber)
|
||||
|
||||
def writeBoundingBox(fid, OriginX, OriginY, MaxXExtent, MaxYExtent):
|
||||
x = util.in2gerb(OriginX)
|
||||
y = util.in2gerb(OriginY)
|
||||
X = util.in2gerb(MaxXExtent)
|
||||
Y = util.in2gerb(MaxYExtent)
|
||||
|
||||
makestroke.drawPolyline(fid, [(x,y), (X,y), (X,Y), (x,Y), (x,y)], 0, 0)
|
||||
|
||||
def writeDrillLegend(fid, Tools, OriginY, MaxXExtent):
|
||||
# This is the spacing from the right edge of the board to where the
|
||||
# drill legend is to be drawn, in inches. Remember we have to allow
|
||||
# for dimension arrows, too.
|
||||
dimspace = 0.5 # inches
|
||||
|
||||
# This is the spacing from the drill hit glyph to the drill size
|
||||
# in inches.
|
||||
glyphspace = 0.1 # inches
|
||||
|
||||
# Convert to Gerber 2.5 units
|
||||
dimspace = util.in2gerb(dimspace)
|
||||
glyphspace = util.in2gerb(glyphspace)
|
||||
|
||||
# Construct a list of tuples (toolSize, toolNumber) where toolNumber
|
||||
# is the position of the tool in Tools and toolSize is in inches.
|
||||
L = []
|
||||
toolNumber = -1
|
||||
for tool in Tools:
|
||||
toolNumber += 1
|
||||
L.append((config.GlobalToolMap[tool], toolNumber))
|
||||
|
||||
# Now sort the list from smallest to largest
|
||||
L.sort()
|
||||
|
||||
# And reverse to go from largest to smallest, so we can write the legend
|
||||
# from the bottom up
|
||||
L.reverse()
|
||||
|
||||
# For each tool, draw a drill hit marker then the size of the tool
|
||||
# in inches.
|
||||
posY = util.in2gerb(OriginY)
|
||||
posX = util.in2gerb(MaxXExtent) + dimspace
|
||||
maxX = 0
|
||||
for size,toolNum in L:
|
||||
# Determine string to write and midpoint of string
|
||||
s = '%.3f"' % size
|
||||
ll, ur = makestroke.boundingBox(s, posX+glyphspace, posY) # Returns lower-left point, upper-right point
|
||||
midpoint = (ur[1]+ll[1])/2
|
||||
|
||||
# Keep track of maximum extent of legend
|
||||
maxX = max(maxX, ur[0])
|
||||
|
||||
makestroke.drawDrillHit(fid, posX, midpoint, toolNum)
|
||||
makestroke.writeString(fid, s, posX+glyphspace, posY, 0)
|
||||
|
||||
posY += int(round((ur[1]-ll[1])*1.5))
|
||||
|
||||
# Return value is lower-left of user text area, without any padding.
|
||||
return maxX, util.in2gerb(OriginY)
|
||||
|
||||
def writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent):
|
||||
x = util.in2gerb(OriginX)
|
||||
y = util.in2gerb(OriginY)
|
||||
X = util.in2gerb(MaxXExtent)
|
||||
Y = util.in2gerb(MaxYExtent)
|
||||
|
||||
# This constant is how far away from the board the centerline of the dimension
|
||||
# arrows should be, in inches.
|
||||
dimspace = 0.2
|
||||
|
||||
# Convert it to Gerber (0.00001" or 2.5) units
|
||||
dimspace = util.in2gerb(dimspace)
|
||||
|
||||
# Draw an arrow above the board, on the left side and right side
|
||||
makestroke.drawDimensionArrow(fid, x, Y+dimspace, makestroke.FacingLeft)
|
||||
makestroke.drawDimensionArrow(fid, X, Y+dimspace, makestroke.FacingRight)
|
||||
|
||||
# Draw arrows to the right of the board, at top and bottom
|
||||
makestroke.drawDimensionArrow(fid, X+dimspace, Y, makestroke.FacingUp)
|
||||
makestroke.drawDimensionArrow(fid, X+dimspace, y, makestroke.FacingDown)
|
||||
|
||||
# Now draw the text. First, horizontal text above the board.
|
||||
s = '%.3f"' % (MaxXExtent - OriginX)
|
||||
ll, ur = makestroke.boundingBox(s, 0, 0)
|
||||
s_width = ur[0]-ll[0] # Width in 2.5 units
|
||||
s_height = ur[1]-ll[1] # Height in 2.5 units
|
||||
|
||||
# Compute the position in 2.5 units where we should draw this. It should be
|
||||
# centered horizontally and also vertically about the dimension arrow centerline.
|
||||
posX = x + (x+X)/2
|
||||
posX -= s_width/2
|
||||
posY = Y + dimspace - s_height/2
|
||||
makestroke.writeString(fid, s, posX, posY, 0)
|
||||
|
||||
# Finally, draw the extending lines from the text to the arrows.
|
||||
posY = Y + dimspace
|
||||
posX1 = posX - util.in2gerb(0.1) # 1000
|
||||
posX2 = posX + s_width + util.in2gerb(0.1) # 1000
|
||||
makestroke.drawLine(fid, x, posY, posX1, posY)
|
||||
makestroke.drawLine(fid, posX2, posY, X, posY)
|
||||
|
||||
# Now do the vertical text
|
||||
s = '%.3f"' % (MaxYExtent - OriginY)
|
||||
ll, ur = makestroke.boundingBox(s, 0, 0)
|
||||
s_width = ur[0]-ll[0]
|
||||
s_height = ur[1]-ll[1]
|
||||
|
||||
# As above, figure out where to draw this. Rotation will be -90 degrees
|
||||
# so new origin will be top-left of bounding box after rotation.
|
||||
posX = X + dimspace - s_height/2
|
||||
posY = y + (y+Y)/2
|
||||
posY += s_width/2
|
||||
makestroke.writeString(fid, s, posX, posY, -90)
|
||||
|
||||
# Draw extending lines
|
||||
posX = X + dimspace
|
||||
posY1 = posY + util.in2gerb(0.1) # 1000
|
||||
posY2 = posY - s_width - util.in2gerb(0.1) # 1000
|
||||
makestroke.drawLine(fid, posX, Y, posX, posY1)
|
||||
makestroke.drawLine(fid, posX, posY2, posX, y)
|
||||
|
||||
def writeUserText(fid, X, Y):
|
||||
fname = config.Config['fabricationdrawingtext']
|
||||
if not fname: return
|
||||
|
||||
try:
|
||||
tfile = file(fname, 'rt')
|
||||
except Exception, detail:
|
||||
raise RuntimeError, "Could not open fabrication drawing text file '%s':\n %s" % (fname,str(detail))
|
||||
|
||||
lines = tfile.readlines()
|
||||
tfile.close()
|
||||
lines.reverse() # We're going to print from bottom up
|
||||
|
||||
# Offset X position to give some clearance from drill legend
|
||||
X += util.in2gerb(0.2) # 2000
|
||||
|
||||
for line in lines:
|
||||
# Get rid of CR
|
||||
line = string.replace(line, '\x0D', '')
|
||||
|
||||
# Chop off \n
|
||||
#if line[-1] in string.whitespace:
|
||||
# line = line[:-1]
|
||||
|
||||
# Strip off trailing whitespace
|
||||
line = string.rstrip(line)
|
||||
|
||||
# Blank lines still need height, so must have at least one character
|
||||
if not line:
|
||||
line = ' '
|
||||
|
||||
ll, ur = makestroke.boundingBox(line, X, Y)
|
||||
makestroke.writeString(fid, line, X, Y, 0)
|
||||
|
||||
Y += int(round((ur[1]-ll[1])*1.5))
|
||||
|
||||
# Main entry point. Gerber file has already been opened, header written
|
||||
# out, 1mil tool selected.
|
||||
def writeFabDrawing(fid, Place, Tools, OriginX, OriginY, MaxXExtent, MaxYExtent):
|
||||
|
||||
# Write out all the drill hits
|
||||
writeDrillHits(fid, Place, Tools)
|
||||
|
||||
# Draw a bounding box for the project
|
||||
writeBoundingBox(fid, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
# Write out the drill hit legend off to the side. This function returns
|
||||
# (X,Y) lower-left origin where user text is to begin, in Gerber units
|
||||
# and without any padding.
|
||||
X,Y = writeDrillLegend(fid, Tools, OriginY, MaxXExtent)
|
||||
|
||||
# Write out the dimensioning arrows
|
||||
writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
# Finally, write out user text
|
||||
writeUserText(fid, X, Y)
|
BIN
gerber/gerbmerge/bin/fabdrawing.pyc
Normal file
BIN
gerber/gerbmerge/bin/fabdrawing.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/fabdrawing.pyo
Normal file
BIN
gerber/gerbmerge/bin/fabdrawing.pyo
Normal file
Binary file not shown.
346
gerber/gerbmerge/bin/geometry.py
Normal file
346
gerber/gerbmerge/bin/geometry.py
Normal file
|
@ -0,0 +1,346 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
General geometry support routines.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
# Ensure all list elements are unique
|
||||
def uniqueify(L):
|
||||
return {}.fromkeys(L).keys()
|
||||
|
||||
# This function rounds an (X,Y) point to integer co-ordinates
|
||||
def roundPoint(pt):
|
||||
return (int(round(pt[0])),int(round(pt[1])))
|
||||
|
||||
# Returns True if the segment defined by endpoints p1 and p2 is vertical
|
||||
def isSegmentVertical(p1, p2):
|
||||
return p1[0]==p2[0]
|
||||
|
||||
# Returns True if the segment defined by endpoints p1 and p2 is horizontal
|
||||
def isSegmentHorizontal(p1, p2):
|
||||
return p1[1]==p2[1]
|
||||
|
||||
# Returns slope of a non-vertical line segment
|
||||
def segmentSlope(p1, p2):
|
||||
return float(p2[1]-p1[1])/(p2[0]-p1[0])
|
||||
|
||||
# Determine if the (X,Y) 'point' is on the line segment defined by endpoints p1
|
||||
# and p2, both (X,Y) tuples. It's assumed that the point is on the line defined
|
||||
# by the segment, but just may be beyond the endpoints. NOTE: No testing is
|
||||
# performed to see if the point is actually on the line defined by the segment!
|
||||
# This is assumed!
|
||||
def isPointOnSegment(point, p1, p2):
|
||||
if isSegmentVertical(p1,p2):
|
||||
# Treat vertical lines by comparing Y-ordinates
|
||||
return (point[1]-p2[1])*(point[1]-p1[1]) <= 0
|
||||
else:
|
||||
# Treat other lines, including horizontal lines, by comparing X-ordinates
|
||||
return (point[0]-p2[0])*(point[0]-p1[0]) <= 0
|
||||
|
||||
# Returns (X,Y) point where the line segment defined by (X,Y) endpoints p1 and
|
||||
# p2 intersects the line segment defined by endpoints q1 and q2. Only a single
|
||||
# intersection point is allowed, so no coincident lines. If there is no point
|
||||
# of intersection, None is returned.
|
||||
def segmentXsegment1pt(p1, p2, q1, q2):
|
||||
A,B = p1
|
||||
C,D = p2
|
||||
P,Q = q1
|
||||
R,S = q2
|
||||
|
||||
# We have to consider special cases of one or other line segments being vertical
|
||||
if isSegmentVertical(p1,p2):
|
||||
if isSegmentVertical(q1,q2): return None
|
||||
|
||||
x = A
|
||||
y = segmentSlope(q1,q2)*(A-P) + Q
|
||||
elif isSegmentVertical(q1,q2):
|
||||
x = P
|
||||
y = segmentSlope(p1,p2)*(P-A) + B
|
||||
else:
|
||||
m1 = segmentSlope(p1,p2)
|
||||
m2 = segmentSlope(q1,q2)
|
||||
|
||||
if m1==m2: return None
|
||||
|
||||
x = (A*m1 - B - P*m2 + Q) / (m1-m2)
|
||||
y = m1*(x-A) + B
|
||||
|
||||
# Candidate point identified. Check to make sure it's on both line segments.
|
||||
if isPointOnSegment((x,y), p1, p2) and isPointOnSegment((x,y), q1, q2):
|
||||
return roundPoint((x,y))
|
||||
else:
|
||||
return None
|
||||
|
||||
# Returns True if the given (X,Y) 'point' is strictly within the rectangle
|
||||
# defined by (LLX,LLY,URX,URY) co-ordinates (LL=lower left, UR=upper right).
|
||||
def isPointStrictlyInRectangle(point, rect):
|
||||
x,y = point
|
||||
llx,lly,urx,ury = rect
|
||||
return (llx < x < urx) and (lly < y < ury)
|
||||
|
||||
# This function takes two points which define the extents of a rectangle. The
|
||||
# return value is a 5-tuple (ll, ul, ur, lr, rect) which comprises 4 points
|
||||
# (lower-left, upper-left, upper-right, lower-right) and a rect object (minx,
|
||||
# miny, maxx, maxy). If called with a single argument, it is expected to be
|
||||
# a 4-tuple (x1,y1,x2,y2).
|
||||
def canonicalizeExtents(pt1, pt2=None):
|
||||
# First canonicalize lower-left and upper-right points
|
||||
if pt2 is None:
|
||||
maxx = max(pt1[0], pt1[2])
|
||||
minx = min(pt1[0], pt1[2])
|
||||
maxy = max(pt1[1], pt1[3])
|
||||
miny = min(pt1[1], pt1[3])
|
||||
else:
|
||||
maxx = max(pt1[0], pt2[0])
|
||||
minx = min(pt1[0], pt2[0])
|
||||
maxy = max(pt1[1], pt2[1])
|
||||
miny = min(pt1[1], pt2[1])
|
||||
|
||||
# Construct the four corners
|
||||
llpt = (minx,miny)
|
||||
urpt = (maxx,maxy)
|
||||
ulpt = (minx,maxy)
|
||||
lrpt = (maxx,miny)
|
||||
|
||||
# Construct a rect object for use by various functions
|
||||
rect = (minx, miny, maxx, maxy)
|
||||
|
||||
return (llpt, ulpt, urpt, lrpt, rect)
|
||||
|
||||
# This function returns a list of intersection points of the line segment
|
||||
# pt1-->pt2 and the box defined by corners llpt and urpt. These corners are
|
||||
# canonicalized internally so they need not necessarily be lower-left and
|
||||
# upper-right points.
|
||||
#
|
||||
# The return value may be a list of 0, 1, or 2 points. If the list has 2
|
||||
# points, then the segment intersects the box in two points since both points
|
||||
# are outside the box. If the list has 1 point, then the segment has one point
|
||||
# inside the box and another point outside. If the list is empty, the segment
|
||||
# has both points outside the box and there is no intersection, or has both
|
||||
# points inside the box.
|
||||
#
|
||||
# Note that segments collinear with box edges produce no points of
|
||||
# intersection.
|
||||
def segmentXbox(pt1, pt2, llpt, urpt):
|
||||
# First canonicalize lower-left and upper-right points
|
||||
llpt, ulpt, urpt, lrpt, rect = canonicalizeExtents(llpt, urpt)
|
||||
|
||||
# Determine whether one point is inside the rectangle and the other is not.
|
||||
# Note the XOR operator '^'
|
||||
oneInOneOut = isPointStrictlyInRectangle(pt1,rect) ^ isPointStrictlyInRectangle(pt2,rect)
|
||||
|
||||
# Find all intersections of the segment with the 4 sides of the box,
|
||||
# one side at a time. L will be the list of definitely-true intersections,
|
||||
# while corners is a list of potential intersections. An intersection
|
||||
# is potential if a) it is a corner, and b) there is another intersection
|
||||
# of the line with the box somewhere else. This is how we handle
|
||||
# corner intersections, which are sometimes legal (when one segment endpoint
|
||||
# is inside the box and the other isn't, or when the segment intersects the
|
||||
# box in two places) and sometimes not (when the segment is "tangent" to
|
||||
# the box at the corner and the corner is the signle point of intersection).
|
||||
L = []
|
||||
corners = []
|
||||
|
||||
# Do not allow intersection if segment is collinear with box sides. For
|
||||
# example, a horizontal line collinear with the box top side should not
|
||||
# return an intersection with the upper-left or upper-right corner.
|
||||
# Similarly, a point of intersection that is a corner should only be
|
||||
# allowed if one segment point is inside the box and the other is not,
|
||||
# otherwise it means the segment is "tangent" to the box at that corner.
|
||||
# There is a case, however, in which a corner is a point of intersection
|
||||
# with both segment points outside the box, and that is if there are two
|
||||
# points of intersection, i.e., the segment goes completely through the box.
|
||||
|
||||
def checkIntersection(corner1, corner2):
|
||||
# Check intersection with side of box
|
||||
pt = segmentXsegment1pt(pt1, pt2, corner1, corner2)
|
||||
if pt in (corner1,corner2):
|
||||
# Only allow this corner intersection point if line is not
|
||||
# horizontal/vertical and one point is inside rectangle while other is
|
||||
# not, or the segment intersects the box in two places. Since oneInOneOut
|
||||
# calls isPointStrictlyInRectangle(), which automatically excludes points
|
||||
# on the box itself, horizontal/vertical lines collinear with box sides
|
||||
# will always lead to oneInOneOut==False (since both will be "out of
|
||||
# box").
|
||||
if oneInOneOut:
|
||||
L.append(pt)
|
||||
else:
|
||||
corners.append(pt) # Potentially a point of intersection...we'll have to wait and
|
||||
# see if there is one more point of intersection somewhere else.
|
||||
else:
|
||||
# Not a corner intersection, so it's valid
|
||||
if pt is not None: L.append(pt)
|
||||
|
||||
# Check intersection with left side of box
|
||||
checkIntersection(llpt, ulpt)
|
||||
|
||||
# Check intersection with top side of box
|
||||
checkIntersection(ulpt, urpt)
|
||||
|
||||
# Check intersection with right side of box
|
||||
checkIntersection(urpt, lrpt)
|
||||
|
||||
# Check intersection with bottom side of box
|
||||
checkIntersection(llpt, lrpt)
|
||||
|
||||
# Ensure all points are unique. We may get a double hit at the corners
|
||||
# of the box.
|
||||
L = uniqueify(L)
|
||||
corners = uniqueify(corners)
|
||||
|
||||
# If the total number of intersections len(L)+len(corners) is 2, the corner
|
||||
# is valid. If there is only a single corner, it's a tangent and invalid.
|
||||
# However, if both corners are on the same side of the box, it's not valid.
|
||||
numPts = len(L)+len(corners)
|
||||
assert numPts <= 2
|
||||
if numPts == 2:
|
||||
if len(corners)==2 and (isSegmentHorizontal(corners[0], corners[1]) or isSegmentVertical(corners[0],corners[1])):
|
||||
return []
|
||||
else:
|
||||
L += corners
|
||||
L.sort() # Just for stability in assertion checking
|
||||
return L
|
||||
else:
|
||||
L.sort()
|
||||
return L # Correct if numPts==1, since it will be empty or contain a single valid intersection
|
||||
# Correct if numPts==0, since it will be empty
|
||||
|
||||
# This function determines if two rectangles defined by 4-tuples
|
||||
# (minx, miny, maxx, maxy) have any rectangle in common. If so, it is
|
||||
# returned as a 4-tuple, else None is returned. This function assumes
|
||||
# the rectangles are canonical so that minx<maxx, miny<maxy. If the
|
||||
# optional allowLines parameter is True, rectangles that overlap on
|
||||
# a line are considered overlapping, otherwise they must overlap with
|
||||
# a rectangle of at least width 1.
|
||||
def areExtentsOverlapping(E1, E2, allowLines=False):
|
||||
minX,minY,maxX,maxY = E1
|
||||
minU,minV,maxU,maxV = E2
|
||||
|
||||
if allowLines:
|
||||
if (minU > maxX) or (maxU < minX) or (minV > maxY) or (maxV < minY):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
if (minU >= maxX) or (maxU <= minX) or (minV >= maxY) or (maxV <= minY):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# Compute the intersection of two rectangles defined by 4-tuples E1 and E2,
|
||||
# which are not necessarily canonicalized.
|
||||
def intersectExtents(E1, E2):
|
||||
ll1, ul1, ur1, lr1, rect1 = canonicalizeExtents(E1)
|
||||
ll2, ul2, ur2, lr2, rect2 = canonicalizeExtents(E2)
|
||||
|
||||
if not areExtentsOverlapping(rect1, rect2):
|
||||
return None
|
||||
|
||||
xll = max(rect1[0], rect2[0]) # Maximum of minx values
|
||||
yll = max(rect1[1], rect2[1]) # Maximum of miny values
|
||||
xur = min(rect1[2], rect2[2]) # Minimum of maxx values
|
||||
yur = min(rect1[3], rect2[3]) # Minimum of maxy values
|
||||
return (xll, yll, xur, yur)
|
||||
|
||||
# This function returns True if rectangle E1 is wholly contained within
|
||||
# rectangle E2. Both E1 and E2 are 4-tuples (minx,miny,maxx,maxy), not
|
||||
# necessarily canonicalized. This function is like a slightly faster
|
||||
# version of "intersectExtents(E1, E2)==E1".
|
||||
def isRect1InRect2(E1, E2):
|
||||
ll1, ul1, ur1, lr1, rect1 = canonicalizeExtents(E1)
|
||||
ll2, ul2, ur2, lr2, rect2 = canonicalizeExtents(E2)
|
||||
|
||||
return (ll1[0] >= ll2[0]) and (ll1[1] >= ll2[1]) \
|
||||
and (ur1[0] <= ur2[0]) and (ur1[1] <= ur2[1])
|
||||
|
||||
# Return width of rectangle, which may be 0 if bottom-left and upper-right X
|
||||
# positions are the same. The rectangle is a 4-tuple (minx,miny,maxx,maxy).
|
||||
def rectWidth(rect):
|
||||
return abs(rect[2]-rect[0])
|
||||
|
||||
# Return height of rectangle, which may be 0 if bottom-left and upper-right Y
|
||||
# positions are the same. The rectangle is a 4-tuple (minx,miny,maxx,maxy).
|
||||
def rectHeight(rect):
|
||||
return abs(rect[3]-rect[1])
|
||||
|
||||
# Return center (X,Y) co-ordinates of rectangle.
|
||||
def rectCenter(rect):
|
||||
dx = rectWidth(rect)
|
||||
dy = rectHeight(rect)
|
||||
|
||||
if dx & 1: # Odd width: center is (left+right)/2 + 1/2
|
||||
X = (rect[0] + rect[2] + 1)/2
|
||||
else: # Even width: center is (left+right)/2
|
||||
X = (rect[0] + rect[2])/2
|
||||
|
||||
if dy & 1:
|
||||
Y = (rect[1] + rect[3] + 1)/2
|
||||
else:
|
||||
Y = (rect[1] + rect[3])/2
|
||||
|
||||
return (X,Y)
|
||||
|
||||
if __name__=="__main__":
|
||||
llpt = (1000,1000)
|
||||
urpt = (5000,5000)
|
||||
|
||||
# A segment that cuts across the box and intersects in corners
|
||||
assert segmentXbox((0,0), (6000,6000), llpt, urpt) == [(1000,1000), (5000,5000)] # Two valid corners
|
||||
assert segmentXbox((0,6000), (6000,0), llpt, urpt) == [(1000,5000), (5000,1000)] # Two valid corners
|
||||
assert segmentXbox((500,500), (2500, 2500), llpt, urpt) == [(1000,1000)] # One valid corner
|
||||
assert segmentXbox((2500,2500), (5500, 5500), llpt, urpt) == [(5000,5000)] # One valid corner
|
||||
|
||||
# Segments collinear with box sides
|
||||
assert segmentXbox((1000,0), (1000,6000), llpt, urpt) == [] # Box side contained in segment
|
||||
assert segmentXbox((1000,0), (1000,3000), llpt, urpt) == [] # Box side partially overlaps segment
|
||||
assert segmentXbox((1000,2000), (1000,4000), llpt, urpt) == [] # Segment contained in box side
|
||||
|
||||
# Segments fully contained within box
|
||||
assert segmentXbox((1500,2000), (2000,2500), llpt, urpt) == []
|
||||
|
||||
# Segments with points on box sides
|
||||
assert segmentXbox((2500,1000), (2700,1200), llpt, urpt) == [(2500,1000)] # One point on box side
|
||||
assert segmentXbox((2500,1000), (2700,5000), llpt, urpt) == [(2500,1000), (2700,5000)] # Two points on box sides
|
||||
|
||||
# Segment intersects box at one point
|
||||
assert segmentXbox((3500,5500), (3000, 2500), llpt, urpt) == [(3417, 5000)] # First point outside
|
||||
assert segmentXbox((3500,1500), (3000, 6500), llpt, urpt) == [(3150, 5000)] # Second point outside
|
||||
|
||||
# Segment intersects box at two points, not corners
|
||||
assert segmentXbox((500,3000), (1500,500), llpt, urpt) == [(1000,1750), (1300,1000)]
|
||||
assert segmentXbox((2500,300), (5500,3500), llpt, urpt) == [(3156,1000), (5000,2967)]
|
||||
assert segmentXbox((5200,1200), (2000,6000), llpt, urpt) == [(2667,5000), (5000, 1500)]
|
||||
assert segmentXbox((3200,5200), (-10, 1200), llpt, urpt) == [(1000, 2459), (3040, 5000)]
|
||||
|
||||
assert segmentXbox((500,2000), (5500, 2000), llpt, urpt) == [(1000,2000), (5000, 2000)]
|
||||
assert segmentXbox((5200,1250), (-200, 4800), llpt, urpt) == [(1000, 4011), (5000, 1381)]
|
||||
|
||||
assert segmentXbox((1300,200), (1300, 5200), llpt, urpt) == [(1300, 1000), (1300, 5000)]
|
||||
assert segmentXbox((1200,200), (1300, 5200), llpt, urpt) == [(1216, 1000), (1296, 5000)]
|
||||
|
||||
assert intersectExtents( (100,100,500,500), (500,500,900,900) ) == None
|
||||
assert intersectExtents( (100,100,500,500), (400,400,900,900) ) == (400,400,500,500)
|
||||
assert intersectExtents( (100,100,500,500), (200,0,600,300) ) == (200,100,500,300)
|
||||
assert intersectExtents( (100,100,500,500), (200,0,300,600) ) == (200,100,300,500)
|
||||
|
||||
assert intersectExtents( (100,100,500,500), (0,600,50,550) ) == None
|
||||
assert intersectExtents( (100,100,500,500), (0,600,600,-10) ) == (100,100,500,500)
|
||||
assert intersectExtents( (100,100,500,500), (0,600,600,200) ) == (100,200,500,500)
|
||||
assert intersectExtents( (100,100,500,500), (0,600,300,300) ) == (100,300,300,500)
|
||||
|
||||
assert isRect1InRect2( (100,100,500,500), (0,600,50,550) ) == False
|
||||
assert isRect1InRect2( (100,100,500,500), (0,600,600,-10) ) == True
|
||||
assert isRect1InRect2( (100,100,500,500), (0,600,600,200) ) == False
|
||||
assert isRect1InRect2( (100,100,500,500), (0,600,300,300) ) == False
|
||||
assert isRect1InRect2( (100,100,500,500), (0,0,500,500) ) == True
|
||||
|
||||
print 'All tests pass'
|
BIN
gerber/gerbmerge/bin/geometry.pyc
Normal file
BIN
gerber/gerbmerge/bin/geometry.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/geometry.pyo
Normal file
BIN
gerber/gerbmerge/bin/geometry.pyo
Normal file
Binary file not shown.
753
gerber/gerbmerge/bin/gerbmerge.py
Normal file
753
gerber/gerbmerge/bin/gerbmerge.py
Normal file
|
@ -0,0 +1,753 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Merge several RS274X (Gerber) files generated by Eagle into a single
|
||||
job.
|
||||
|
||||
This program expects that each separate job has at least three files:
|
||||
- a board outline (RS274X)
|
||||
- data layers (copper, silkscreen, etc. in RS274X format)
|
||||
- an Excellon drill file
|
||||
|
||||
Furthermore, it is expected that each job was generated by Eagle
|
||||
using the GERBER_RS274X plotter, except for the drill file which
|
||||
was generated by the EXCELLON plotter.
|
||||
|
||||
This program places all jobs into a single job.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import re
|
||||
|
||||
import aptable
|
||||
import jobs
|
||||
import config
|
||||
import parselayout
|
||||
import fabdrawing
|
||||
import strokes
|
||||
import tiling
|
||||
import tilesearch1
|
||||
import tilesearch2
|
||||
import placement
|
||||
import schwartz
|
||||
import util
|
||||
import scoring
|
||||
import drillcluster
|
||||
|
||||
VERSION_MAJOR=1
|
||||
VERSION_MINOR=8
|
||||
|
||||
RANDOM_SEARCH = 1
|
||||
EXHAUSTIVE_SEARCH = 2
|
||||
FROM_FILE = 3
|
||||
config.AutoSearchType = RANDOM_SEARCH
|
||||
config.RandomSearchExhaustiveJobs = 2
|
||||
config.PlacementFile = None
|
||||
|
||||
# This is a handle to a GUI front end, if any, else None for command-line usage
|
||||
GUI = None
|
||||
|
||||
def usage():
|
||||
print \
|
||||
"""
|
||||
Usage: gerbmerge [Options] configfile [layoutfile]
|
||||
|
||||
Options:
|
||||
-h, --help -- This help summary
|
||||
-v, --version -- Program version and contact information
|
||||
--random-search -- Automatic placement using random search (default)
|
||||
--full-search -- Automatic placement using exhaustive search
|
||||
--place-file=fn -- Read placement from file
|
||||
--rs-fsjobs=N -- When using random search, exhaustively search N jobs
|
||||
for each random placement (default: N=2)
|
||||
--search-timeout=T -- When using random search, search for T seconds for best
|
||||
random placement (default: T=0, search until stopped)
|
||||
--no-trim-gerber -- Do not attempt to trim Gerber data to extents of board
|
||||
--no-trim-excellon -- Do not attempt to trim Excellon data to extents of board
|
||||
--octagons=fmt -- Generate octagons in two different styles depending on
|
||||
the value of 'fmt':
|
||||
|
||||
fmt is 'rotate' : 0.0 rotation
|
||||
fmt is 'normal' : 22.5 rotation (default)
|
||||
|
||||
If a layout file is not specified, automatic placement is performed. If the
|
||||
placement is read from a file, then no automatic placement is performed and
|
||||
the layout file (if any) is ignored.
|
||||
|
||||
NOTE: The dimensions of each job are determined solely by the maximum extent of
|
||||
the board outline layer for each job.
|
||||
"""
|
||||
sys.exit(1)
|
||||
|
||||
def writeGerberHeader22degrees(fid):
|
||||
fid.write( \
|
||||
"""G75*
|
||||
G70*
|
||||
%OFA0B0*%
|
||||
%FSLAX25Y25*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
%
|
||||
""")
|
||||
|
||||
def writeGerberHeader0degrees(fid):
|
||||
fid.write( \
|
||||
"""G75*
|
||||
G70*
|
||||
%OFA0B0*%
|
||||
%FSLAX25Y25*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,0.0*
|
||||
%
|
||||
""")
|
||||
|
||||
writeGerberHeader = writeGerberHeader22degrees
|
||||
|
||||
def writeApertureMacros(fid, usedDict):
|
||||
keys = config.GAMT.keys()
|
||||
keys.sort()
|
||||
for key in keys:
|
||||
if key in usedDict:
|
||||
config.GAMT[key].writeDef(fid)
|
||||
|
||||
def writeApertures(fid, usedDict):
|
||||
keys = config.GAT.keys()
|
||||
keys.sort()
|
||||
for key in keys:
|
||||
if key in usedDict:
|
||||
config.GAT[key].writeDef(fid)
|
||||
|
||||
def writeGerberFooter(fid):
|
||||
fid.write('M02*\n')
|
||||
|
||||
def writeExcellonHeader(fid):
|
||||
fid.write('%\n')
|
||||
|
||||
def writeExcellonFooter(fid):
|
||||
fid.write('M30\n')
|
||||
|
||||
def writeExcellonTool(fid, tool, size):
|
||||
fid.write('%sC%f\n' % (tool, size))
|
||||
|
||||
def writeFiducials(fid, drawcode, OriginX, OriginY, MaxXExtent, MaxYExtent):
|
||||
"""Place fiducials at arbitrary points. The FiducialPoints list in the config specifies
|
||||
sets of X,Y co-ordinates. Positive values of X/Y represent offsets from the lower left
|
||||
of the panel. Negative values of X/Y represent offsets from the top right. So:
|
||||
FiducialPoints = 0.125,0.125,-0.125,-0.125
|
||||
means to put a fiducial 0.125,0.125 from the lower left and 0.125,0.125 from the top right"""
|
||||
fid.write('%s*\n' % drawcode) # Choose drawing aperture
|
||||
|
||||
fList = config.Config['fiducialpoints'].split(',')
|
||||
for i in range(0, len(fList), 2):
|
||||
x,y = float(fList[i]), float(fList[i+1])
|
||||
if x>=0:
|
||||
x += OriginX
|
||||
else:
|
||||
x = MaxXExtent + x
|
||||
if y>=0:
|
||||
y += OriginX
|
||||
else:
|
||||
y = MaxYExtent + y
|
||||
fid.write('X%07dY%07dD03*\n' % (util.in2gerb(x), util.in2gerb(y)))
|
||||
|
||||
def writeCropMarks(fid, drawing_code, OriginX, OriginY, MaxXExtent, MaxYExtent):
|
||||
"""Add corner crop marks on the given layer"""
|
||||
|
||||
# Draw 125mil lines at each corner, with line edge right up against
|
||||
# panel border. This means the center of the line is D/2 offset
|
||||
# from the panel border, where D is the drawing line diameter.
|
||||
fid.write('%s*\n' % drawing_code) # Choose drawing aperture
|
||||
|
||||
offset = config.GAT[drawing_code].dimx/2.0
|
||||
|
||||
# Lower-left
|
||||
x = OriginX + offset
|
||||
y = OriginY + offset
|
||||
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.125), util.in2gerb(y+0.000)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.125)))
|
||||
|
||||
# Lower-right
|
||||
x = MaxXExtent - offset
|
||||
y = OriginY + offset
|
||||
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.125)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x-0.125), util.in2gerb(y+0.000)))
|
||||
|
||||
# Upper-right
|
||||
x = MaxXExtent - offset
|
||||
y = MaxYExtent - offset
|
||||
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x-0.125), util.in2gerb(y+0.000)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-0.125)))
|
||||
|
||||
# Upper-left
|
||||
x = OriginX + offset
|
||||
y = MaxYExtent - offset
|
||||
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-0.125)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.125), util.in2gerb(y+0.000)))
|
||||
|
||||
def disclaimer():
|
||||
print """
|
||||
****************************************************
|
||||
* R E A D C A R E F U L L Y *
|
||||
* *
|
||||
* This program comes with no warranty. You use *
|
||||
* this program at your own risk. Do not submit *
|
||||
* board files for manufacture until you have *
|
||||
* thoroughly inspected the output of this program *
|
||||
* using a previewing program such as: *
|
||||
* *
|
||||
* Windows: *
|
||||
* - GC-Prevue <http://www.graphicode.com> *
|
||||
* - ViewMate <http://www.pentalogix.com> *
|
||||
* *
|
||||
* Linux: *
|
||||
* - gerbv <http://gerbv.sourceforge.net> *
|
||||
* *
|
||||
* By using this program you agree to take full *
|
||||
* responsibility for the correctness of the data *
|
||||
* that is generated by this program. *
|
||||
****************************************************
|
||||
|
||||
To agree to the above terms, press 'y' then Enter.
|
||||
Any other key will exit the program.
|
||||
|
||||
"""
|
||||
|
||||
s = raw_input()
|
||||
if s == 'y':
|
||||
print
|
||||
return
|
||||
|
||||
print "\nExiting..."
|
||||
sys.exit(0)
|
||||
|
||||
def tile_jobs(Jobs):
|
||||
"""Take a list of raw Job objects and find best tiling by calling tile_search"""
|
||||
|
||||
# We must take the raw jobs and construct a list of 4-tuples (Xdim,Ydim,job,rjob).
|
||||
# This means we must construct a rotated job for each entry. We first sort all
|
||||
# jobs from largest to smallest. This should give us the best tilings first so
|
||||
# we can interrupt the tiling process and get a decent layout.
|
||||
L = []
|
||||
#sortJobs = schwartz.schwartz(Jobs, jobs.Job.jobarea)
|
||||
sortJobs = schwartz.schwartz(Jobs, jobs.Job.maxdimension)
|
||||
sortJobs.reverse()
|
||||
|
||||
for job in sortJobs:
|
||||
Xdim = job.width_in()
|
||||
Ydim = job.height_in()
|
||||
rjob = jobs.rotateJob(job, 90) ##NOTE: This will only try 90 degree rotations though 180 & 270 are available
|
||||
|
||||
for count in range(job.Repeat):
|
||||
L.append( (Xdim,Ydim,job,rjob) )
|
||||
|
||||
PX,PY = config.Config['panelwidth'],config.Config['panelheight']
|
||||
if config.AutoSearchType==RANDOM_SEARCH:
|
||||
tile = tilesearch2.tile_search2(L, PX, PY)
|
||||
else:
|
||||
tile = tilesearch1.tile_search1(L, PX, PY)
|
||||
|
||||
if not tile:
|
||||
raise RuntimeError, 'Panel size %.2f"x%.2f" is too small to hold jobs' % (PX,PY)
|
||||
|
||||
return tile
|
||||
|
||||
def merge(opts, args, gui = None):
|
||||
writeGerberHeader = writeGerberHeader22degrees
|
||||
|
||||
global GUI
|
||||
GUI = gui
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt in ('--octagons',):
|
||||
if arg=='rotate':
|
||||
writeGerberHeader = writeGerberHeader0degrees
|
||||
elif arg=='normal':
|
||||
writeGerberHeader = writeGerberHeader22degrees
|
||||
else:
|
||||
raise RuntimeError, 'Unknown octagon format'
|
||||
elif opt in ('--random-search',):
|
||||
config.AutoSearchType = RANDOM_SEARCH
|
||||
elif opt in ('--full-search',):
|
||||
config.AutoSearchType = EXHAUSTIVE_SEARCH
|
||||
elif opt in ('--rs-fsjobs',):
|
||||
config.RandomSearchExhaustiveJobs = int(arg)
|
||||
elif opt in ('--search-timeout',):
|
||||
config.SearchTimeout = int(arg)
|
||||
elif opt in ('--place-file',):
|
||||
config.AutoSearchType = FROM_FILE
|
||||
config.PlacementFile = arg
|
||||
elif opt in ('--no-trim-gerber',):
|
||||
config.TrimGerber = 0
|
||||
elif opt in ('--no-trim-excellon',):
|
||||
config.TrimExcellon = 0
|
||||
else:
|
||||
raise RuntimeError, "Unknown option: %s" % opt
|
||||
|
||||
if len(args) > 2 or len(args) < 1:
|
||||
raise RuntimeError, 'Invalid number of arguments'
|
||||
|
||||
# Load up the Jobs global dictionary, also filling out GAT, the
|
||||
# global aperture table and GAMT, the global aperture macro table.
|
||||
updateGUI("Reading job files...")
|
||||
config.parseConfigFile(args[0])
|
||||
|
||||
# Force all X and Y coordinates positive by adding absolute value of minimum X and Y
|
||||
for name, job in config.Jobs.iteritems():
|
||||
min_x, min_y = job.mincoordinates()
|
||||
shift_x = shift_y = 0
|
||||
if min_x < 0: shift_x = abs(min_x)
|
||||
if min_y < 0: shift_y = abs(min_y)
|
||||
if (shift_x > 0) or (shift_y > 0):
|
||||
job.fixcoordinates( shift_x, shift_y )
|
||||
|
||||
# Display job properties
|
||||
for job in config.Jobs.values():
|
||||
print 'Job %s:' % job.name,
|
||||
if job.Repeat > 1:
|
||||
print '(%d instances)' % job.Repeat
|
||||
else:
|
||||
print
|
||||
print ' Extents: (%d,%d)-(%d,%d)' % (job.minx,job.miny,job.maxx,job.maxy)
|
||||
print ' Size: %f" x %f"' % (job.width_in(), job.height_in())
|
||||
print
|
||||
|
||||
# Trim drill locations and flash data to board extents
|
||||
if config.TrimExcellon:
|
||||
updateGUI("Trimming Excellon data...")
|
||||
print 'Trimming Excellon data to board outlines ...'
|
||||
for job in config.Jobs.values():
|
||||
job.trimExcellon()
|
||||
|
||||
if config.TrimGerber:
|
||||
updateGUI("Trimming Gerber data...")
|
||||
print 'Trimming Gerber data to board outlines ...'
|
||||
for job in config.Jobs.values():
|
||||
job.trimGerber()
|
||||
|
||||
# We start origin at (0.1", 0.1") just so we don't get numbers close to 0
|
||||
# which could trip up Excellon leading-0 elimination.
|
||||
OriginX = OriginY = 0.1
|
||||
|
||||
# Read the layout file and construct the nested list of jobs. If there
|
||||
# is no layout file, do auto-layout.
|
||||
updateGUI("Performing layout...")
|
||||
print 'Performing layout ...'
|
||||
if len(args) > 1:
|
||||
Layout = parselayout.parseLayoutFile(args[1])
|
||||
|
||||
# Do the layout, updating offsets for each component job.
|
||||
X = OriginX + config.Config['leftmargin']
|
||||
Y = OriginY + config.Config['bottommargin']
|
||||
|
||||
for row in Layout:
|
||||
row.setPosition(X, Y)
|
||||
Y += row.height_in() + config.Config['yspacing']
|
||||
|
||||
# Construct a canonical placement from the layout
|
||||
Place = placement.Placement()
|
||||
Place.addFromLayout(Layout)
|
||||
|
||||
del Layout
|
||||
|
||||
elif config.AutoSearchType == FROM_FILE:
|
||||
Place = placement.Placement()
|
||||
Place.addFromFile(config.PlacementFile, config.Jobs)
|
||||
else:
|
||||
# Do an automatic layout based on our tiling algorithm.
|
||||
tile = tile_jobs(config.Jobs.values())
|
||||
|
||||
Place = placement.Placement()
|
||||
Place.addFromTiling(tile, OriginX + config.Config['leftmargin'], OriginY + config.Config['bottommargin'])
|
||||
|
||||
(MaxXExtent,MaxYExtent) = Place.extents()
|
||||
MaxXExtent += config.Config['rightmargin']
|
||||
MaxYExtent += config.Config['topmargin']
|
||||
|
||||
# Start printing out the Gerbers. In preparation for drawing cut marks
|
||||
# and crop marks, make sure we have an aperture to draw with. Use a 10mil line.
|
||||
# If we're doing a fabrication drawing, we'll need a 1mil line.
|
||||
OutputFiles = []
|
||||
|
||||
try:
|
||||
fullname = config.MergeOutputFiles['placement']
|
||||
except KeyError:
|
||||
fullname = 'merged.placement.txt'
|
||||
Place.write(fullname)
|
||||
OutputFiles.append(fullname)
|
||||
|
||||
# For cut lines
|
||||
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cutlinewidth'])
|
||||
drawing_code_cut = aptable.findInApertureTable(AP)
|
||||
if drawing_code_cut is None:
|
||||
drawing_code_cut = aptable.addToApertureTable(AP)
|
||||
|
||||
# For crop marks
|
||||
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cropmarkwidth'])
|
||||
drawing_code_crop = aptable.findInApertureTable(AP)
|
||||
if drawing_code_crop is None:
|
||||
drawing_code_crop = aptable.addToApertureTable(AP)
|
||||
|
||||
# For fiducials
|
||||
drawing_code_fiducial_copper = drawing_code_fiducial_soldermask = None
|
||||
if config.Config['fiducialpoints']:
|
||||
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialcopperdiameter'])
|
||||
drawing_code_fiducial_copper = aptable.findInApertureTable(AP)
|
||||
if drawing_code_fiducial_copper is None:
|
||||
drawing_code_fiducial_copper = aptable.addToApertureTable(AP)
|
||||
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialmaskdiameter'])
|
||||
drawing_code_fiducial_soldermask = aptable.findInApertureTable(AP)
|
||||
if drawing_code_fiducial_soldermask is None:
|
||||
drawing_code_fiducial_soldermask = aptable.addToApertureTable(AP)
|
||||
|
||||
# For fabrication drawing.
|
||||
AP = aptable.Aperture(aptable.Circle, 'D??', 0.001)
|
||||
drawing_code1 = aptable.findInApertureTable(AP)
|
||||
if drawing_code1 is None:
|
||||
drawing_code1 = aptable.addToApertureTable(AP)
|
||||
|
||||
updateGUI("Writing merged files...")
|
||||
print 'Writing merged output files ...'
|
||||
|
||||
for layername in config.LayerList.keys():
|
||||
lname = layername
|
||||
if lname[0]=='*':
|
||||
lname = lname[1:]
|
||||
|
||||
try:
|
||||
fullname = config.MergeOutputFiles[layername]
|
||||
except KeyError:
|
||||
fullname = 'merged.%s.ger' % lname
|
||||
OutputFiles.append(fullname)
|
||||
#print 'Writing %s ...' % fullname
|
||||
fid = file(fullname, 'wt')
|
||||
writeGerberHeader(fid)
|
||||
|
||||
# Determine which apertures and macros are truly needed
|
||||
apUsedDict = {}
|
||||
apmUsedDict = {}
|
||||
for job in Place.jobs:
|
||||
apd, apmd = job.aperturesAndMacros(layername)
|
||||
apUsedDict.update(apd)
|
||||
apmUsedDict.update(apmd)
|
||||
|
||||
# Increase aperature sizes to match minimum feature dimension
|
||||
if config.MinimumFeatureDimension.has_key(layername):
|
||||
|
||||
print ' Thickening', lname, 'feature dimensions ...'
|
||||
|
||||
# Fix each aperture used in this layer
|
||||
for ap in apUsedDict.keys():
|
||||
new = config.GAT[ap].getAdjusted( config.MinimumFeatureDimension[layername] )
|
||||
if not new: ## current aperture size met minimum requirement
|
||||
continue
|
||||
else: ## new aperture was created
|
||||
new_code = aptable.findOrAddAperture(new) ## get name of existing aperture or create new one if needed
|
||||
del apUsedDict[ap] ## the old aperture is no longer used in this layer
|
||||
apUsedDict[new_code] = None ## the new aperture will be used in this layer
|
||||
|
||||
# Replace all references to the old aperture with the new one
|
||||
for joblayout in Place.jobs:
|
||||
job = joblayout.job ##access job inside job layout
|
||||
temp = []
|
||||
if job.hasLayer(layername):
|
||||
for x in job.commands[layername]:
|
||||
if x == ap:
|
||||
temp.append(new_code) ## replace old aperture with new one
|
||||
else:
|
||||
temp.append(x) ## keep old command
|
||||
job.commands[layername] = temp
|
||||
|
||||
if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']):
|
||||
apUsedDict[drawing_code_cut]=None
|
||||
|
||||
if config.Config['cropmarklayers'] and (layername in config.Config['cropmarklayers']):
|
||||
apUsedDict[drawing_code_crop]=None
|
||||
|
||||
if config.Config['fiducialpoints']:
|
||||
if ((layername=='*toplayer') or (layername=='*bottomlayer')):
|
||||
apUsedDict[drawing_code_fiducial_copper] = None
|
||||
elif ((layername=='*topsoldermask') or (layername=='*bottomsoldermask')):
|
||||
apUsedDict[drawing_code_fiducial_soldermask] = None
|
||||
|
||||
# Write only necessary macro and aperture definitions to Gerber file
|
||||
writeApertureMacros(fid, apmUsedDict)
|
||||
writeApertures(fid, apUsedDict)
|
||||
|
||||
#for row in Layout:
|
||||
# row.writeGerber(fid, layername)
|
||||
|
||||
# # Do cut lines
|
||||
# if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']):
|
||||
# fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture
|
||||
# row.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
# Finally, write actual flash data
|
||||
for job in Place.jobs:
|
||||
|
||||
updateGUI("Writing merged output files...")
|
||||
job.writeGerber(fid, layername)
|
||||
|
||||
if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']):
|
||||
fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture
|
||||
job.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
if config.Config['cropmarklayers']:
|
||||
if layername in config.Config['cropmarklayers']:
|
||||
writeCropMarks(fid, drawing_code_crop, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
if config.Config['fiducialpoints']:
|
||||
if ((layername=='*toplayer') or (layername=='*bottomlayer')):
|
||||
writeFiducials(fid, drawing_code_fiducial_copper, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
elif ((layername=='*topsoldermask') or (layername=='*bottomsoldermask')):
|
||||
writeFiducials(fid, drawing_code_fiducial_soldermask, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
writeGerberFooter(fid)
|
||||
fid.close()
|
||||
|
||||
# Write board outline layer if selected
|
||||
fullname = config.Config['outlinelayerfile']
|
||||
if fullname and fullname.lower() != "none":
|
||||
OutputFiles.append(fullname)
|
||||
#print 'Writing %s ...' % fullname
|
||||
fid = file(fullname, 'wt')
|
||||
writeGerberHeader(fid)
|
||||
|
||||
# Write width-1 aperture to file
|
||||
AP = aptable.Aperture(aptable.Circle, 'D10', 0.001)
|
||||
AP.writeDef(fid)
|
||||
|
||||
# Choose drawing aperture D10
|
||||
fid.write('D10*\n')
|
||||
|
||||
# Draw the rectangle
|
||||
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right
|
||||
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left
|
||||
|
||||
writeGerberFooter(fid)
|
||||
fid.close()
|
||||
|
||||
# Write scoring layer if selected
|
||||
fullname = config.Config['scoringfile']
|
||||
if fullname and fullname.lower() != "none":
|
||||
OutputFiles.append(fullname)
|
||||
#print 'Writing %s ...' % fullname
|
||||
fid = file(fullname, 'wt')
|
||||
writeGerberHeader(fid)
|
||||
|
||||
# Write width-1 aperture to file
|
||||
AP = aptable.Aperture(aptable.Circle, 'D10', 0.001)
|
||||
AP.writeDef(fid)
|
||||
|
||||
# Choose drawing aperture D10
|
||||
fid.write('D10*\n')
|
||||
|
||||
# Draw the scoring lines
|
||||
scoring.writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
writeGerberFooter(fid)
|
||||
fid.close()
|
||||
|
||||
# Get a list of all tools used by merging keys from each job's dictionary
|
||||
# of tools.
|
||||
if 0:
|
||||
Tools = {}
|
||||
for job in config.Jobs.values():
|
||||
for key in job.xcommands.keys():
|
||||
Tools[key] = 1
|
||||
|
||||
Tools = Tools.keys()
|
||||
Tools.sort()
|
||||
else:
|
||||
toolNum = 0
|
||||
|
||||
# First construct global mapping of diameters to tool numbers
|
||||
for job in config.Jobs.values():
|
||||
for tool,diam in job.xdiam.items():
|
||||
if config.GlobalToolRMap.has_key(diam):
|
||||
continue
|
||||
|
||||
toolNum += 1
|
||||
config.GlobalToolRMap[diam] = "T%02d" % toolNum
|
||||
|
||||
# Cluster similar tool sizes to reduce number of drills
|
||||
if config.Config['drillclustertolerance'] > 0:
|
||||
config.GlobalToolRMap = drillcluster.cluster( config.GlobalToolRMap, config.Config['drillclustertolerance'] )
|
||||
drillcluster.remap( Place.jobs, config.GlobalToolRMap.items() )
|
||||
|
||||
# Now construct mapping of tool numbers to diameters
|
||||
for diam,tool in config.GlobalToolRMap.items():
|
||||
config.GlobalToolMap[tool] = diam
|
||||
|
||||
# Tools is just a list of tool names
|
||||
Tools = config.GlobalToolMap.keys()
|
||||
Tools.sort()
|
||||
|
||||
fullname = config.Config['fabricationdrawingfile']
|
||||
if fullname and fullname.lower() != 'none':
|
||||
if len(Tools) > strokes.MaxNumDrillTools:
|
||||
raise RuntimeError, "Only %d different tool sizes supported for fabrication drawing." % strokes.MaxNumDrillTools
|
||||
|
||||
OutputFiles.append(fullname)
|
||||
#print 'Writing %s ...' % fullname
|
||||
fid = file(fullname, 'wt')
|
||||
writeGerberHeader(fid)
|
||||
writeApertures(fid, {drawing_code1: None})
|
||||
fid.write('%s*\n' % drawing_code1) # Choose drawing aperture
|
||||
|
||||
fabdrawing.writeFabDrawing(fid, Place, Tools, OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
writeGerberFooter(fid)
|
||||
fid.close()
|
||||
|
||||
# Finally, print out the Excellon
|
||||
try:
|
||||
fullname = config.MergeOutputFiles['drills']
|
||||
except KeyError:
|
||||
fullname = 'merged.drills.xln'
|
||||
OutputFiles.append(fullname)
|
||||
#print 'Writing %s ...' % fullname
|
||||
fid = file(fullname, 'wt')
|
||||
|
||||
writeExcellonHeader(fid)
|
||||
|
||||
# Ensure each one of our tools is represented in the tool list specified
|
||||
# by the user.
|
||||
for tool in Tools:
|
||||
try:
|
||||
size = config.GlobalToolMap[tool]
|
||||
except:
|
||||
raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool map" % tool
|
||||
|
||||
writeExcellonTool(fid, tool, size)
|
||||
|
||||
#for row in Layout:
|
||||
# row.writeExcellon(fid, size)
|
||||
for job in Place.jobs:
|
||||
job.writeExcellon(fid, size)
|
||||
|
||||
writeExcellonFooter(fid)
|
||||
fid.close()
|
||||
|
||||
updateGUI("Closing files...")
|
||||
|
||||
# Compute stats
|
||||
jobarea = 0.0
|
||||
#for row in Layout:
|
||||
# jobarea += row.jobarea()
|
||||
for job in Place.jobs:
|
||||
jobarea += job.jobarea()
|
||||
|
||||
totalarea = ((MaxXExtent-OriginX)*(MaxYExtent-OriginY))
|
||||
|
||||
ToolStats = {}
|
||||
drillhits = 0
|
||||
for tool in Tools:
|
||||
ToolStats[tool]=0
|
||||
#for row in Layout:
|
||||
# hits = row.drillhits(config.GlobalToolMap[tool])
|
||||
# ToolStats[tool] += hits
|
||||
# drillhits += hits
|
||||
for job in Place.jobs:
|
||||
hits = job.drillhits(config.GlobalToolMap[tool])
|
||||
ToolStats[tool] += hits
|
||||
drillhits += hits
|
||||
|
||||
try:
|
||||
fullname = config.MergeOutputFiles['toollist']
|
||||
except KeyError:
|
||||
fullname = 'merged.toollist.drl'
|
||||
OutputFiles.append(fullname)
|
||||
#print 'Writing %s ...' % fullname
|
||||
fid = file(fullname, 'wt')
|
||||
|
||||
print '-'*50
|
||||
print ' Job Size : %f" x %f"' % (MaxXExtent-OriginX, MaxYExtent-OriginY)
|
||||
print ' Job Area : %.2f sq. in.' % totalarea
|
||||
print ' Area Usage : %.1f%%' % (jobarea/totalarea*100)
|
||||
print ' Drill hits : %d' % drillhits
|
||||
print 'Drill density : %.1f hits/sq.in.' % (drillhits/totalarea)
|
||||
|
||||
print '\nTool List:'
|
||||
smallestDrill = 999.9
|
||||
for tool in Tools:
|
||||
if ToolStats[tool]:
|
||||
fid.write('%s %.4fin\n' % (tool, config.GlobalToolMap[tool]))
|
||||
print ' %s %.4f" %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool])
|
||||
smallestDrill = min(smallestDrill, config.GlobalToolMap[tool])
|
||||
|
||||
fid.close()
|
||||
print "Smallest Tool: %.4fin" % smallestDrill
|
||||
|
||||
print
|
||||
print 'Output Files :'
|
||||
for f in OutputFiles:
|
||||
print ' ', f
|
||||
|
||||
if (MaxXExtent-OriginX)>config.Config['panelwidth'] or (MaxYExtent-OriginY)>config.Config['panelheight']:
|
||||
print '*'*75
|
||||
print '*'
|
||||
print '* ERROR: Merged job exceeds panel dimensions of %.1f"x%.1f"' % (config.Config['panelwidth'],config.Config['panelheight'])
|
||||
print '*'
|
||||
print '*'*75
|
||||
sys.exit(1)
|
||||
|
||||
# Done!
|
||||
return 0
|
||||
|
||||
def updateGUI(text = None):
|
||||
global GUI
|
||||
if GUI != None:
|
||||
GUI.updateProgress(text)
|
||||
|
||||
if __name__=="__main__":
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hv', ['help', 'version', 'octagons=', 'random-search', 'full-search', 'rs-fsjobs=', 'search-timeout=', 'place-file=', 'no-trim-gerber', 'no-trim-excellon'])
|
||||
except getopt.GetoptError:
|
||||
usage()
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt in ('-h', '--help'):
|
||||
usage()
|
||||
elif opt in ('-v', '--version'):
|
||||
print """
|
||||
GerbMerge Version %d.%d -- Combine multiple Gerber/Excellon files
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of this license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
""" % (VERSION_MAJOR, VERSION_MINOR)
|
||||
sys.exit(0)
|
||||
elif opt in ('--octagons', '--random-search','--full-search','--rs-fsjobs','--place-file','--no-trim-gerber','--no-trim-excellon', '--search-timeout'):
|
||||
pass ## arguments are valid
|
||||
else:
|
||||
raise RuntimeError, "Unknown option: %s" % opt
|
||||
|
||||
if len(args) > 2 or len(args) < 1:
|
||||
usage()
|
||||
|
||||
disclaimer()
|
||||
|
||||
sys.exit(merge(opts, args)) ## run germberge
|
||||
# vim: expandtab ts=2 sw=2 ai syntax=python
|
BIN
gerber/gerbmerge/bin/gerbmerge.pyc
Normal file
BIN
gerber/gerbmerge/bin/gerbmerge.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/gerbmerge.pyo
Normal file
BIN
gerber/gerbmerge/bin/gerbmerge.pyo
Normal file
Binary file not shown.
1288
gerber/gerbmerge/bin/jobs.py
Normal file
1288
gerber/gerbmerge/bin/jobs.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
gerber/gerbmerge/bin/jobs.pyc
Normal file
BIN
gerber/gerbmerge/bin/jobs.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/jobs.pyo
Normal file
BIN
gerber/gerbmerge/bin/jobs.pyo
Normal file
Binary file not shown.
167
gerber/gerbmerge/bin/makestroke.py
Normal file
167
gerber/gerbmerge/bin/makestroke.py
Normal file
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/env python
|
||||
"""Support for writing characters and graphics to Gerber files
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import strokes
|
||||
|
||||
# Define percentage of cell height and width to determine
|
||||
# intercharacter spacing
|
||||
SpacingX = 1.20
|
||||
SpacingY = 1.20
|
||||
|
||||
# Arrow dimensions
|
||||
BarLength = 1500 # Length of dimension line
|
||||
ArrowWidth = 750 # How broad the arrow is
|
||||
ArrowLength = 750 # How far back from dimension line it is
|
||||
ArrowStemLength = 1250 # How long the arrow stem extends from center point
|
||||
|
||||
#################################################################
|
||||
|
||||
# Arrow directions
|
||||
FacingLeft=0 # 0 degrees
|
||||
FacingDown=1 # 90 degrees counterclockwise
|
||||
FacingRight=2 # 180 degrees
|
||||
FacingUp=3 # 270 degrees
|
||||
|
||||
SpacingDX = 10*int(round(strokes.MaxWidth*SpacingX))
|
||||
SpacingDY = 10*int(round(strokes.MaxHeight*SpacingY))
|
||||
|
||||
RotatedGlyphs={}
|
||||
|
||||
# Default arrow glyph is at 0 degrees rotation, facing left
|
||||
ArrowGlyph = [ [(0,-BarLength/2), (0, BarLength/2)],
|
||||
[(ArrowLength,ArrowWidth/2), (0,0), (ArrowLength,-ArrowWidth/2)],
|
||||
[(0,0), (ArrowStemLength,0)]
|
||||
]
|
||||
|
||||
def rotateGlyph(glyph, degrees, glyphName):
|
||||
"""Rotate a glyph counterclockwise by given number of degrees. The glyph
|
||||
is a list of lists, where each sub-list is a connected path."""
|
||||
try:
|
||||
return RotatedGlyphs["%.1f_%s" % (degrees, glyphName)]
|
||||
except KeyError:
|
||||
pass # Not cached yet
|
||||
|
||||
rad = degrees/180.0*math.pi
|
||||
cosx = math.cos(rad)
|
||||
sinx = math.sin(rad)
|
||||
|
||||
newglyph = []
|
||||
for path in glyph:
|
||||
newpath = []
|
||||
for X,Y in path:
|
||||
x = int(round(X*cosx - Y*sinx))
|
||||
y = int(round(X*sinx + Y*cosx))
|
||||
newpath.append((x,y))
|
||||
newglyph.append(newpath)
|
||||
|
||||
RotatedGlyphs["%.1f_%s" % (degrees, glyphName)] = newglyph
|
||||
return newglyph
|
||||
|
||||
def writeFlash(fid, X, Y, D):
|
||||
fid.write("X%07dY%07dD%02d*\n" % (X,Y,D))
|
||||
|
||||
def drawPolyline(fid, L, offX, offY, scale=1):
|
||||
for ix in range(len(L)):
|
||||
X,Y = L[ix]
|
||||
X *= scale
|
||||
Y *= scale
|
||||
if ix==0:
|
||||
writeFlash(fid, X+offX, Y+offY, 2)
|
||||
else:
|
||||
writeFlash(fid, X+offX, Y+offY, 1)
|
||||
|
||||
def writeGlyph(fid, glyph, X, Y, degrees, glyphName=None):
|
||||
if not glyphName:
|
||||
glyphName = str(glyph)
|
||||
|
||||
for path in rotateGlyph(glyph, degrees, glyphName):
|
||||
drawPolyline(fid, path, X, Y, 10)
|
||||
|
||||
def writeChar(fid, c, X, Y, degrees):
|
||||
if c==' ': return
|
||||
|
||||
try:
|
||||
glyph = strokes.StrokeMap[c]
|
||||
except:
|
||||
raise RuntimeError, 'No glyph for character %s' % hex(ord(c))
|
||||
|
||||
writeGlyph(fid, glyph, X, Y, degrees, c)
|
||||
|
||||
def writeString(fid, s, X, Y, degrees):
|
||||
posX = X
|
||||
posY = Y
|
||||
rad = degrees/180.0*math.pi
|
||||
dX = int(round(math.cos(rad)*SpacingDX))
|
||||
dY = int(round(math.sin(rad)*SpacingDX))
|
||||
|
||||
if 0:
|
||||
if dX < 0:
|
||||
# Always print text left to right
|
||||
dX = -dX
|
||||
s = list(s)
|
||||
s.reverse()
|
||||
s = string.join(s, '')
|
||||
|
||||
for char in s:
|
||||
writeChar(fid, char, posX, posY, degrees)
|
||||
posX += dX
|
||||
posY += dY
|
||||
|
||||
def drawLine(fid, X1, Y1, X2, Y2):
|
||||
drawPolyline(fid, [(X1,Y1), (X2,Y2)], 0, 0)
|
||||
|
||||
def boundingBox(s, X1, Y1):
|
||||
"Return (X1,Y1),(X2,Y2) for given string"
|
||||
if not s:
|
||||
return (X1, Y1), (X1, Y1)
|
||||
|
||||
X2 = X1 + (len(s)-1)*SpacingDX + 10*strokes.MaxWidth
|
||||
Y2 = Y1 + 10*strokes.MaxHeight # Not including descenders
|
||||
return (X1, Y1), (X2, Y2)
|
||||
|
||||
def drawDimensionArrow(fid, X, Y, facing):
|
||||
writeGlyph(fid, ArrowGlyph, X, Y, facing*90, "Arrow")
|
||||
|
||||
def drawDrillHit(fid, X, Y, toolNum):
|
||||
writeGlyph(fid, strokes.DrillStrokeList[toolNum], X, Y, 0, "Drill%02d" % toolNum)
|
||||
|
||||
if __name__=="__main__":
|
||||
import string
|
||||
s = string.digits+string.letters+string.punctuation
|
||||
#s = "The quick brown fox jumped over the lazy dog!"
|
||||
|
||||
fid = file('test.ger','wt')
|
||||
fid.write("""G75*
|
||||
G70*
|
||||
%OFA0B0*%
|
||||
%FSAX24Y24*%
|
||||
%IPPOS*%
|
||||
%LPD*%
|
||||
%AMOC8*
|
||||
5,1,8,0,0,1.08239X$1,22.5*
|
||||
*%
|
||||
%ADD10C,0.0100*%
|
||||
D10*
|
||||
""")
|
||||
|
||||
writeString(fid, s, 0, 0, 0)
|
||||
drawDimensionArrow(fid, 0, 5000, FacingLeft)
|
||||
drawDimensionArrow(fid, 5000, 5000, FacingRight)
|
||||
drawDimensionArrow(fid, 0, 10000, FacingUp)
|
||||
drawDimensionArrow(fid, 5000, 10000, FacingDown)
|
||||
|
||||
for diam in range(0,strokes.MaxNumDrillTools):
|
||||
writeGlyph(fid, strokes.DrillStrokeList[diam], diam*1250, 15000, 0, "%02d" % diam)
|
||||
|
||||
fid.write("M02*\n")
|
||||
fid.close()
|
BIN
gerber/gerbmerge/bin/makestroke.pyc
Normal file
BIN
gerber/gerbmerge/bin/makestroke.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/makestroke.pyo
Normal file
BIN
gerber/gerbmerge/bin/makestroke.pyo
Normal file
Binary file not shown.
338
gerber/gerbmerge/bin/parselayout.py
Normal file
338
gerber/gerbmerge/bin/parselayout.py
Normal file
|
@ -0,0 +1,338 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Parse the job layout specification file.
|
||||
|
||||
Requires:
|
||||
|
||||
- SimpleParse 2.1 or higher
|
||||
http://simpleparse.sourceforge.net
|
||||
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
import sys
|
||||
import string
|
||||
|
||||
from simpleparse.parser import Parser
|
||||
|
||||
import config
|
||||
import jobs
|
||||
|
||||
declaration = r'''
|
||||
file := (commentline/nullline/rowspec)+
|
||||
rowspec := ts, 'Row', ws, '{'!, ts, comment?, '\n', rowjob+, ts, '}'!, ts, comment?, '\n'
|
||||
rowjob := jobspec/colspec/commentline/nullline
|
||||
colspec := ts, 'Col', ws, '{'!, ts, comment?, '\n', coljob+, ts, '}'!, ts, comment?, '\n'
|
||||
coljob := jobspec/rowspec/commentline/nullline
|
||||
|
||||
jobspec := ts, (paneljobspec/basicjobspec), ts, comment?, '\n'
|
||||
basicjobspec := id, (rotation)?
|
||||
paneljobspec := 'Not yet implemented'
|
||||
#paneljobspec := int, [xX], int, ws, basicjobspec
|
||||
comment := ([#;]/'//'), -'\n'*
|
||||
commentline := ts, comment, '\n'
|
||||
nullline := ts, '\n'
|
||||
rotation := ws, 'Rotate', ('90'/'180'/'270')?
|
||||
ws := [ \t]+
|
||||
ts := [ \t]*
|
||||
id := [a-zA-Z0-9], [a-zA-Z0-9_-]*
|
||||
int := [0-9]+
|
||||
'''
|
||||
|
||||
class Panel: # Meant to be subclassed as either a Row() or Col()
|
||||
def __init__(self):
|
||||
self.x = None
|
||||
self.y = None
|
||||
self.jobs = [] # List (left-to-right or bottom-to-top) of JobLayout() or Row()/Col() objects
|
||||
|
||||
def canonicalize(self): # Return plain list of JobLayout objects at the roots of all trees
|
||||
L = []
|
||||
for job in self.jobs:
|
||||
L = L + job.canonicalize()
|
||||
return L
|
||||
|
||||
def addjob(self, job): # Either a JobLayout class or Panel (sub)class
|
||||
assert isinstance(job, Panel) or isinstance(job, jobs.JobLayout)
|
||||
self.jobs.append(job)
|
||||
|
||||
def addwidths(self):
|
||||
"Return width in inches"
|
||||
width = 0.0
|
||||
for job in self.jobs:
|
||||
width += job.width_in() + config.Config['xspacing']
|
||||
width -= config.Config['xspacing']
|
||||
return width
|
||||
|
||||
def maxwidths(self):
|
||||
"Return maximum width in inches of any one subpanel"
|
||||
width = 0.0
|
||||
for job in self.jobs:
|
||||
width = max(width,job.width_in())
|
||||
return width
|
||||
|
||||
def addheights(self):
|
||||
"Return height in inches"
|
||||
height = 0.0
|
||||
for job in self.jobs:
|
||||
height += job.height_in() + config.Config['yspacing']
|
||||
height -= config.Config['yspacing']
|
||||
return height
|
||||
|
||||
def maxheights(self):
|
||||
"Return maximum height in inches of any one subpanel"
|
||||
height = 0.0
|
||||
for job in self.jobs:
|
||||
height = max(height,job.height_in())
|
||||
return height
|
||||
|
||||
def writeGerber(self, fid, layername):
|
||||
for job in self.jobs:
|
||||
job.writeGerber(fid, layername)
|
||||
|
||||
def writeExcellon(self, fid, tool):
|
||||
for job in self.jobs:
|
||||
job.writeExcellon(fid, tool)
|
||||
|
||||
def writeDrillHits(self, fid, tool, toolNum):
|
||||
for job in self.jobs:
|
||||
job.writeDrillHits(fid, tool, toolNum)
|
||||
|
||||
def writeCutLines(self, fid, drawing_code, X1, Y1, X2, Y2):
|
||||
for job in self.jobs:
|
||||
job.writeCutLines(fid, drawing_code, X1, Y1, X2, Y2)
|
||||
|
||||
def drillhits(self, tool):
|
||||
hits = 0
|
||||
for job in self.jobs:
|
||||
hits += job.drillhits(tool)
|
||||
|
||||
return hits
|
||||
|
||||
def jobarea(self):
|
||||
area = 0.0
|
||||
for job in self.jobs:
|
||||
area += job.jobarea()
|
||||
|
||||
return area
|
||||
|
||||
class Row(Panel):
|
||||
def __init__(self):
|
||||
Panel.__init__(self)
|
||||
self.LR = 1 # Horizontal arrangement
|
||||
|
||||
def width_in(self):
|
||||
return self.addwidths()
|
||||
|
||||
def height_in(self):
|
||||
return self.maxheights()
|
||||
|
||||
def setPosition(self, x, y): # In inches
|
||||
self.x = x
|
||||
self.y = y
|
||||
for job in self.jobs:
|
||||
job.setPosition(x,y)
|
||||
x += job.width_in() + config.Config['xspacing']
|
||||
|
||||
class Col(Panel):
|
||||
def __init__(self):
|
||||
Panel.__init__(self)
|
||||
self.LR = 0 # Vertical arrangement
|
||||
|
||||
def width_in(self):
|
||||
return self.maxwidths()
|
||||
|
||||
def height_in(self):
|
||||
return self.addheights()
|
||||
|
||||
def setPosition(self, x, y): # In inches
|
||||
self.x = x
|
||||
self.y = y
|
||||
for job in self.jobs:
|
||||
job.setPosition(x,y)
|
||||
y += job.height_in() + config.Config['yspacing']
|
||||
|
||||
def canonicalizePanel(panel):
|
||||
L = []
|
||||
for job in panel:
|
||||
L = L + job.canonicalize()
|
||||
return L
|
||||
|
||||
def findJob(jobname, rotated, Jobs=config.Jobs):
|
||||
"""
|
||||
Find a job in config.Jobs, possibly rotating it
|
||||
If job not in config.Jobs add it for future reference
|
||||
Return found job
|
||||
"""
|
||||
|
||||
if rotated == 90:
|
||||
fullname = jobname + '*rotated90'
|
||||
elif rotated == 180:
|
||||
fullname = jobname + '*rotated180'
|
||||
elif rotated == 270:
|
||||
fullname = jobname + '*rotated270'
|
||||
else:
|
||||
fullname = jobname
|
||||
|
||||
try:
|
||||
for existingjob in Jobs.keys():
|
||||
if existingjob.lower() == fullname.lower(): ## job names are case insensitive
|
||||
job = Jobs[existingjob]
|
||||
return jobs.JobLayout(job)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Perhaps we just don't have a rotated job yet
|
||||
if rotated:
|
||||
try:
|
||||
for existingjob in Jobs.keys():
|
||||
if existingjob.lower() == jobname.lower(): ## job names are case insensitive
|
||||
job = Jobs[existingjob]
|
||||
except:
|
||||
raise RuntimeError, "Job name '%s' not found" % jobname
|
||||
else:
|
||||
raise RuntimeError, "Job name '%s' not found" % jobname
|
||||
|
||||
# Make a rotated job
|
||||
job = jobs.rotateJob(job, rotated)
|
||||
Jobs[fullname] = job
|
||||
|
||||
return jobs.JobLayout(job)
|
||||
|
||||
def parseJobSpec(spec, data):
|
||||
for jobspec in spec:
|
||||
if jobspec[0] in ('ts','comment'): continue
|
||||
|
||||
assert jobspec[0] in ('paneljobspec','basicjobspec')
|
||||
if jobspec[0] == 'basicjobspec':
|
||||
namefield = jobspec[3][0]
|
||||
jobname = data[namefield[1]:namefield[2]]
|
||||
|
||||
if len(jobspec[3]) > 1:
|
||||
rotationfield = jobspec[3][1]
|
||||
rotation = data[ rotationfield[1] + 1: rotationfield[2] ]
|
||||
|
||||
if (rotation == "Rotate") or (rotation == "Rotate90"):
|
||||
rotated = 90
|
||||
elif rotation == "Rotate180":
|
||||
rotated = 180
|
||||
elif rotation == "Rotate270":
|
||||
rotated = 270
|
||||
else:
|
||||
raise RuntimeError, "Unsupported rotation: %s" % rotation
|
||||
|
||||
else:
|
||||
rotated = 0
|
||||
|
||||
return findJob(jobname, rotated)
|
||||
else:
|
||||
raise RuntimeError, "Matrix panels not yet supported"
|
||||
|
||||
def parseColSpec(spec, data):
|
||||
jobs = Col()
|
||||
|
||||
for coljob in spec:
|
||||
if coljob[0] in ('ts','ws','comment'): continue
|
||||
|
||||
assert coljob[0] == 'coljob'
|
||||
job = coljob[3][0]
|
||||
if job[0] in ('commentline','nullline'): continue
|
||||
|
||||
assert job[0] in ('jobspec','rowspec')
|
||||
if job[0] == 'jobspec':
|
||||
jobs.addjob(parseJobSpec(job[3],data))
|
||||
else:
|
||||
jobs.addjob(parseRowSpec(job[3],data))
|
||||
|
||||
return jobs
|
||||
|
||||
def parseRowSpec(spec, data):
|
||||
jobs = Row()
|
||||
|
||||
for rowjob in spec:
|
||||
if rowjob[0] in ('ts','ws','comment'): continue
|
||||
|
||||
assert rowjob[0] == 'rowjob'
|
||||
job = rowjob[3][0]
|
||||
if job[0] in ('commentline','nullline'): continue
|
||||
|
||||
assert job[0] in ('jobspec','colspec')
|
||||
if job[0] == 'jobspec':
|
||||
jobs.addjob(parseJobSpec(job[3],data))
|
||||
else:
|
||||
jobs.addjob(parseColSpec(job[3],data))
|
||||
|
||||
return jobs
|
||||
|
||||
def parseLayoutFile(fname):
|
||||
"""config.Jobs is a dictionary of ('jobname', Job Object).
|
||||
|
||||
The return value is a nested array. The primary dimension
|
||||
of the array is one row:
|
||||
|
||||
[ Row1, Row2, Row3 ]
|
||||
|
||||
Each row element consists of a list of jobs or columns (i.e.,
|
||||
JobLayout or Col objects).
|
||||
|
||||
Each column consists of a list of either jobs or rows.
|
||||
These are recursive, so it can look like:
|
||||
|
||||
[
|
||||
Row([JobLayout(), Col([ Row([JobLayout(), JobLayout()]),
|
||||
JobLayout() ]), JobLayout() ]), # That was row 0
|
||||
Row([JobLayout(), JobLayout()]) # That was row 1
|
||||
]
|
||||
|
||||
This is a panel with two rows. In the first row there is
|
||||
a job, a column, and another job, from left to right. In the
|
||||
second row there are two jobs, from left to right.
|
||||
|
||||
The column in the first row has two jobs side by side, then
|
||||
another one above them.
|
||||
"""
|
||||
|
||||
try:
|
||||
fid = file(fname, 'rt')
|
||||
except Exception, detail:
|
||||
raise RuntimeError, "Unable to open layout file: %s\n %s" % (fname, str(detail))
|
||||
|
||||
data = fid.read()
|
||||
fid.close()
|
||||
parser = Parser(declaration, "file")
|
||||
|
||||
# Replace all CR's in data with nothing, to convert DOS line endings
|
||||
# to unix format (all LF's).
|
||||
data = string.replace(data, '\x0D', '')
|
||||
|
||||
tree = parser.parse(data)
|
||||
|
||||
# Last element of tree is number of characters parsed
|
||||
if not tree[0]:
|
||||
raise RuntimeError, "Layout file cannot be parsed"
|
||||
|
||||
if tree[2] != len(data):
|
||||
raise RuntimeError, "Parse error at character %d in layout file" % tree[2]
|
||||
|
||||
Rows = []
|
||||
for rowspec in tree[1]:
|
||||
if rowspec[0] in ('nullline', 'commentline'): continue
|
||||
assert rowspec[0]=='rowspec'
|
||||
|
||||
Rows.append(parseRowSpec(rowspec[3], data))
|
||||
|
||||
return Rows
|
||||
|
||||
if __name__=="__main__":
|
||||
fid = file(sys.argv[1])
|
||||
testdata = fid.read()
|
||||
fid.close()
|
||||
|
||||
parser = Parser(declaration, "file")
|
||||
import pprint
|
||||
pprint.pprint(parser.parse(testdata))
|
BIN
gerber/gerbmerge/bin/parselayout.pyc
Normal file
BIN
gerber/gerbmerge/bin/parselayout.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/parselayout.pyo
Normal file
BIN
gerber/gerbmerge/bin/parselayout.pyo
Normal file
Binary file not shown.
108
gerber/gerbmerge/bin/placement.py
Normal file
108
gerber/gerbmerge/bin/placement.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env python
|
||||
"""A placement is a final arrangement of jobs at given (X,Y) positions.
|
||||
This class is intended to "un-pack" an arragement of jobs constructed
|
||||
manually through Layout/Panel/JobLayout/etc. (i.e., a layout.def file)
|
||||
or automatically through a Tiling. From either source, the result is
|
||||
simply a list of jobs.
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
import parselayout
|
||||
import jobs
|
||||
|
||||
class Placement:
|
||||
def __init__(self):
|
||||
self.jobs = [] # A list of JobLayout objects
|
||||
|
||||
def addFromLayout(self, Layout):
|
||||
# Layout is a recursive list of JobLayout items. At the end
|
||||
# of each tree there is a JobLayout object which has a 'job'
|
||||
# member, which is what we're looking for. Fortunately, the
|
||||
# canonicalize() function flattens the tree.
|
||||
#
|
||||
# Positions of jobs have already been set (we're assuming)
|
||||
# prior to calling this function.
|
||||
self.jobs = self.jobs + parselayout.canonicalizePanel(Layout)
|
||||
|
||||
def addFromTiling(self, T, OriginX, OriginY):
|
||||
# T is a Tiling. Calling its canonicalize() method will construct
|
||||
# a list of JobLayout objects and set the (X,Y) position of each
|
||||
# object.
|
||||
self.jobs = self.jobs + T.canonicalize(OriginX,OriginY)
|
||||
|
||||
def extents(self):
|
||||
"""Return the maximum X and Y value over all jobs"""
|
||||
maxX = 0.0
|
||||
maxY = 0.0
|
||||
|
||||
for job in self.jobs:
|
||||
maxX = max(maxX, job.x+job.width_in())
|
||||
maxY = max(maxY, job.y+job.height_in())
|
||||
|
||||
return (maxX,maxY)
|
||||
|
||||
def write(self, fname):
|
||||
"""Write placement to a file"""
|
||||
fid = file(fname, 'wt')
|
||||
for job in self.jobs:
|
||||
fid.write('%s %.3f %.3f\n' % (job.job.name, job.x, job.y))
|
||||
fid.close()
|
||||
|
||||
def addFromFile(self, fname, Jobs):
|
||||
"""Read placement from a file, placed against jobs in Jobs list"""
|
||||
pat = re.compile(r'\s*(\S+)\s+(\S+)\s+(\S+)')
|
||||
comment = re.compile(r'\s*(?:#.+)?$')
|
||||
|
||||
try:
|
||||
fid = file(fname, 'rt')
|
||||
except:
|
||||
print 'Unable to open placement file: "%s"' % fname
|
||||
sys.exit(1)
|
||||
|
||||
lines = fid.readlines()
|
||||
fid.close()
|
||||
|
||||
for line in lines:
|
||||
if comment.match(line): continue
|
||||
|
||||
match = pat.match(line)
|
||||
if not match:
|
||||
print 'Cannot interpret placement line in placement file:\n %s' % line
|
||||
sys.exit(1)
|
||||
|
||||
jobname, X, Y = match.groups()
|
||||
try:
|
||||
X = float(X)
|
||||
Y = float(Y)
|
||||
except:
|
||||
print 'Illegal (X,Y) co-ordinates in placement file:\n %s' % line
|
||||
sys.exit(1)
|
||||
|
||||
rotated = 0
|
||||
if len(jobname) > 8:
|
||||
if jobname[-8:] == '*rotated':
|
||||
rotated = 90
|
||||
jobname = jobname[:-8]
|
||||
elif jobname[-10:] == '*rotated90':
|
||||
rotated = 90
|
||||
jobname = jobname[:-10]
|
||||
elif jobname[-11:] == '*rotated180':
|
||||
rotated = 180
|
||||
jobname = jobname[:-11]
|
||||
elif jobname[-11:] == '*rotated270':
|
||||
rotated = 270
|
||||
jobname = jobname[:-11]
|
||||
|
||||
addjob = parselayout.findJob(jobname, rotated, Jobs)
|
||||
addjob.setPosition(X,Y)
|
||||
self.jobs.append(addjob)
|
||||
|
BIN
gerber/gerbmerge/bin/placement.pyc
Normal file
BIN
gerber/gerbmerge/bin/placement.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/placement.pyo
Normal file
BIN
gerber/gerbmerge/bin/placement.pyo
Normal file
Binary file not shown.
38
gerber/gerbmerge/bin/schwartz.py
Normal file
38
gerber/gerbmerge/bin/schwartz.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
"""
|
||||
Implement the Schwartizan Transform method of sorting
|
||||
a list by an arbitrary metric (see the Python FAQ section
|
||||
4.51).
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
def stripit(pair):
|
||||
return pair[1]
|
||||
|
||||
def schwartz(List, Metric):
|
||||
def pairing(element, M = Metric):
|
||||
return (M(element), element)
|
||||
|
||||
paired = map(pairing, List)
|
||||
paired.sort()
|
||||
return map(stripit, paired)
|
||||
|
||||
def stripit2(pair):
|
||||
return pair[0]
|
||||
|
||||
def schwartz2(List, Metric):
|
||||
"Returns sorted list and also corresponding metrics"
|
||||
|
||||
def pairing(element, M = Metric):
|
||||
return (M(element), element)
|
||||
|
||||
paired = map(pairing, List)
|
||||
paired.sort()
|
||||
theList = map(stripit, paired)
|
||||
theMetrics = map(stripit2, paired)
|
||||
return (theList, theMetrics)
|
BIN
gerber/gerbmerge/bin/schwartz.pyc
Normal file
BIN
gerber/gerbmerge/bin/schwartz.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/schwartz.pyo
Normal file
BIN
gerber/gerbmerge/bin/schwartz.pyo
Normal file
Binary file not shown.
301
gerber/gerbmerge/bin/scoring.py
Normal file
301
gerber/gerbmerge/bin/scoring.py
Normal file
|
@ -0,0 +1,301 @@
|
|||
#!/usr/bin/env python
|
||||
"""This file handles the writing of the scoring lines Gerber file
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import config
|
||||
import util
|
||||
import makestroke
|
||||
|
||||
# Add a horizontal line if its within the extents of the panel. Also, trim
|
||||
# start and/or end points to the extents.
|
||||
def addHorizontalLine(Lines, x1, x2, y, extents):
|
||||
assert (x1 < x2)
|
||||
|
||||
# For a horizontal line, y must be above extents[1] and below extents[3].
|
||||
if extents[1] < y < extents[3]:
|
||||
# Now trim endpoints to be greater than extents[0] and below extents[2]
|
||||
line = (max(extents[0], x1), y, min(extents[2], x2), y)
|
||||
Lines.append(line)
|
||||
|
||||
# Add a vertical line if its within the extents of the panel. Also, trim
|
||||
# start and/or end points to the extents.
|
||||
def addVerticalLine(Lines, x, y1, y2, extents):
|
||||
assert (y1 < y2)
|
||||
|
||||
# For a vertical line, x must be above extents[0] and below extents[2].
|
||||
if extents[0] < x < extents[2]:
|
||||
# Now trim endpoints to be greater than extents[1] and below extents[3]
|
||||
line = (x, max(extents[1], y1), x, min(extents[3], y2))
|
||||
Lines.append(line)
|
||||
|
||||
def isHorizontal(line):
|
||||
return line[1]==line[3]
|
||||
|
||||
def isVertical(line):
|
||||
return line[0]==line[2]
|
||||
|
||||
def clusterOrdinates(values):
|
||||
"""Create a list of tuples where each tuple is a variable-length list of items
|
||||
from 'values' that are all within 2 mils of each other."""
|
||||
|
||||
# First, make sure the values are sorted. Then, take the first one and go along
|
||||
# the list clustering as many as possible.
|
||||
values.sort()
|
||||
currCluster = None
|
||||
L = []
|
||||
for val in values:
|
||||
if currCluster is None:
|
||||
currCluster = (val,)
|
||||
else:
|
||||
if (val - currCluster[0]) <= 0.002:
|
||||
currCluster = currCluster + (val,)
|
||||
else:
|
||||
L.append(currCluster)
|
||||
currCluster = (val,)
|
||||
|
||||
if currCluster is not None:
|
||||
L.append(currCluster)
|
||||
|
||||
return L
|
||||
|
||||
def mergeHLines(Lines):
|
||||
"""Lines is a list of 4-tuples (lines) that have nearly the same Y ordinate and are to be
|
||||
optimized by combining overlapping lines."""
|
||||
|
||||
# First, make sure lines are sorted by starting X ordinate and that all lines
|
||||
# proceed to the right.
|
||||
Lines.sort()
|
||||
for line in Lines:
|
||||
assert line[0] < line[2]
|
||||
|
||||
# Obtain the average value of the Y ordinate and use that as the Y ordinate for
|
||||
# all lines.
|
||||
yavg = 0.0
|
||||
for line in Lines:
|
||||
yavg += line[1]
|
||||
yavg /= len(Lines)
|
||||
|
||||
NewLines = []
|
||||
|
||||
# Now proceed to pick off one line at a time and try to merge it with
|
||||
# the next one in sequence.
|
||||
currLine = None
|
||||
for line in Lines:
|
||||
if currLine is None:
|
||||
currLine = line
|
||||
else:
|
||||
# If the line to examine starts to the left of (within 0.002") the end
|
||||
# of the current line, extend the current line.
|
||||
if line[0] <= currLine[2]+0.002:
|
||||
currLine = (currLine[0], yavg, max(line[2],currLine[2]), yavg)
|
||||
else:
|
||||
NewLines.append(currLine)
|
||||
currLine = line
|
||||
|
||||
NewLines.append(currLine)
|
||||
|
||||
return NewLines
|
||||
|
||||
def sortByY(A,B):
|
||||
"Helper function to sort two lines (4-tuples) by their starting Y ordinate"
|
||||
return cmp(A[1], B[1])
|
||||
|
||||
def mergeVLines(Lines):
|
||||
"""Lines is a list of 4-tuples (lines) that have nearly the same X ordinate and are to be
|
||||
optimized by combining overlapping lines."""
|
||||
|
||||
# First, make sure lines are sorted by starting Y ordinate and that all lines
|
||||
# proceed up.
|
||||
Lines.sort(sortByY)
|
||||
for line in Lines:
|
||||
assert line[1] < line[3]
|
||||
|
||||
# Obtain the average value of the X ordinate and use that as the X ordinate for
|
||||
# all lines.
|
||||
xavg = 0.0
|
||||
for line in Lines:
|
||||
xavg += line[0]
|
||||
xavg /= len(Lines)
|
||||
|
||||
NewLines = []
|
||||
|
||||
# Now proceed to pick off one line at a time and try to merge it with
|
||||
# the next one in sequence.
|
||||
currLine = None
|
||||
for line in Lines:
|
||||
if currLine is None:
|
||||
currLine = line
|
||||
else:
|
||||
# If the line to examine starts below (within 0.002") the end
|
||||
# of the current line, extend the current line.
|
||||
if line[1] <= currLine[3]+0.002:
|
||||
currLine = (xavg, currLine[1], xavg, max(line[3],currLine[3]))
|
||||
else:
|
||||
NewLines.append(currLine)
|
||||
currLine = line
|
||||
|
||||
NewLines.append(currLine)
|
||||
|
||||
return NewLines
|
||||
|
||||
def mergeLines(Lines):
|
||||
# All lines extend up (vertical) and to the right (horizontal). First, do
|
||||
# simple merges. Sort all lines, which will order the lines with starting
|
||||
# points in increasing X order (i.e., to the right).
|
||||
Lines.sort()
|
||||
|
||||
# Now sort the lines into horizontal lines and vertical lines. For each
|
||||
# ordinate, group all lines by that ordinate in a dictionary. Thus, all
|
||||
# horizontal lines will be grouped together by Y ordinate, and all
|
||||
# vertical lines will be grouped together by X ordinate.
|
||||
HLines = {}
|
||||
VLines = {}
|
||||
|
||||
for line in Lines:
|
||||
if isHorizontal(line):
|
||||
try:
|
||||
HLines[line[1]].append(line)
|
||||
except KeyError:
|
||||
HLines[line[1]] = [line]
|
||||
else:
|
||||
try:
|
||||
VLines[line[0]].append(line)
|
||||
except KeyError:
|
||||
VLines[line[0]] = [line]
|
||||
|
||||
# I don't think the next two blocks of code are necessary (merging lines
|
||||
# that are at exactly the same ordinate) since the last two blocks of
|
||||
# code do the same thing more generically by merging lines at close-enough
|
||||
# ordinates.
|
||||
|
||||
# Extend horizontal lines
|
||||
NewHLines = {}
|
||||
for yval,lines in HLines.items():
|
||||
# yval is the Y ordinate of this group of lines. lines is the set of all
|
||||
# lines with this Y ordinate.
|
||||
NewHLines[yval] = []
|
||||
|
||||
# Try to extend the first element of this list, which will be the leftmost.
|
||||
xline = lines[0]
|
||||
for line in lines[1:]:
|
||||
# If this line's left edge is within 2 mil of the right edge of the line
|
||||
# we're currently trying to grow, then grow it.
|
||||
if abs(line[0] - xline[2]) <= 0.002: # Arbitrary 2mil?
|
||||
# Extend...
|
||||
xline = (xline[0], xline[1], line[2], xline[1])
|
||||
else:
|
||||
# ...otherwise, append the currently-extended line and make this
|
||||
# line the new one we try to extend.
|
||||
NewHLines[yval].append(xline)
|
||||
xline = line
|
||||
NewHLines[yval].append(xline)
|
||||
|
||||
# Extend vertical lines
|
||||
NewVLines = {}
|
||||
for xval,lines in VLines.items():
|
||||
# xval is the X ordinate of this group of lines. lines is the set of all
|
||||
# lines with this X ordinate.
|
||||
NewVLines[xval] = []
|
||||
|
||||
# Try to extend the first element of this list, which will be the bottom-most.
|
||||
xline = lines[0]
|
||||
for line in lines[1:]:
|
||||
# If this line's bottom edge is within 2 mil of the top edge of the line
|
||||
# we're currently trying to grow, then grow it.
|
||||
if abs(line[1] - xline[3]) <= 0.002: # Arbitrary 2mil?
|
||||
# Extend...
|
||||
xline = (xline[0], xline[1], xline[0], line[3])
|
||||
else:
|
||||
# ...otherwise, append the currently-extended line and make this
|
||||
# line the new one we try to extend.
|
||||
NewVLines[xval].append(xline)
|
||||
xline = line
|
||||
NewVLines[xval].append(xline)
|
||||
|
||||
HLines = NewHLines
|
||||
VLines = NewVLines
|
||||
NewHLines = []
|
||||
NewVLines = []
|
||||
|
||||
# Now combine lines that have their endpoints either very near each other
|
||||
# or within each other. We will have to sort all horizontal lines by their
|
||||
# Y ordinates and group them according to Y ordinates that are close enough
|
||||
# to each other.
|
||||
yvals = HLines.keys()
|
||||
clusters = clusterOrdinates(yvals) # A list of clustered tuples containing yvals
|
||||
|
||||
for cluster in clusters:
|
||||
clusterLines = []
|
||||
for yval in cluster:
|
||||
clusterLines.extend(HLines[yval])
|
||||
|
||||
# clusterLines is now a list of lines (4-tuples) that all have nearly the same
|
||||
# Y ordinate. Merge them together.
|
||||
NewHLines.extend(mergeHLines(clusterLines))
|
||||
|
||||
xvals = VLines.keys()
|
||||
clusters = clusterOrdinates(xvals)
|
||||
for cluster in clusters:
|
||||
clusterLines = []
|
||||
for xval in cluster:
|
||||
clusterLines.extend(VLines[xval])
|
||||
|
||||
# clusterLines is now a list of lines (4-tuples) that all have nearly the same
|
||||
# X ordinate. Merge them together.
|
||||
NewVLines.extend(mergeVLines(clusterLines))
|
||||
|
||||
Lines = NewHLines + NewVLines
|
||||
|
||||
return Lines
|
||||
|
||||
# Main entry point. Gerber file has already been opened, header written
|
||||
# out, 1mil tool selected.
|
||||
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent):
|
||||
# For each job, write out 4 score lines, above, to the right, below, and
|
||||
# to the left. After we collect all potential scoring lines, we worry
|
||||
# about merging, etc.
|
||||
dx = config.Config['xspacing']/2.0
|
||||
dy = config.Config['yspacing']/2.0
|
||||
extents = (OriginX, OriginY, MaxXExtent, MaxYExtent)
|
||||
|
||||
Lines = []
|
||||
for layout in Place.jobs:
|
||||
x = layout.x - dx
|
||||
y = layout.y - dy
|
||||
X = layout.x + layout.width_in() + dx
|
||||
Y = layout.y + layout.height_in() + dy
|
||||
|
||||
# Just so we don't get 3.75000000004 and 3.75000000009, we round to
|
||||
# 2.5 limits.
|
||||
x,y,X,Y = [round(val,5) for val in [x,y,X,Y]]
|
||||
|
||||
if 0: # Scoring lines go all the way across the panel now
|
||||
addHorizontalLine(Lines, x, X, Y, extents) # above job
|
||||
addVerticalLine(Lines, X, y, Y, extents) # to the right of job
|
||||
addHorizontalLine(Lines, x, X, y, extents) # below job
|
||||
addVerticalLine(Lines, x, y, Y, extents) # to the left of job
|
||||
else:
|
||||
addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job
|
||||
addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job
|
||||
addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job
|
||||
addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job
|
||||
|
||||
# Combine disparate lines into single lines
|
||||
Lines = mergeLines(Lines)
|
||||
|
||||
#for line in Lines:
|
||||
# print [round(x,3) for x in line]
|
||||
|
||||
# Write 'em out
|
||||
for line in Lines:
|
||||
makestroke.drawPolyline(fid, [(util.in2gerb(line[0]),util.in2gerb(line[1])), \
|
||||
(util.in2gerb(line[2]),util.in2gerb(line[3]))], 0, 0)
|
||||
|
||||
# vim: expandtab ts=2 sw=2 ai syntax=python
|
BIN
gerber/gerbmerge/bin/scoring.pyc
Normal file
BIN
gerber/gerbmerge/bin/scoring.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/scoring.pyo
Normal file
BIN
gerber/gerbmerge/bin/scoring.pyo
Normal file
Binary file not shown.
177
gerber/gerbmerge/bin/specs.py
Normal file
177
gerber/gerbmerge/bin/specs.py
Normal file
|
@ -0,0 +1,177 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Regular expression, SimpleParse, ane message constants.
|
||||
|
||||
Requires:
|
||||
|
||||
- SimpleParse 2.1 or higher
|
||||
http://simpleparse.sourceforge.net
|
||||
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from simpleparse.parser import Parser
|
||||
|
||||
DISCLAIMER = """
|
||||
****************************************************
|
||||
* R E A D C A R E F U L L Y *
|
||||
* *
|
||||
* This program comes with no warranty. You use *
|
||||
* this program at your own risk. Do not submit *
|
||||
* board files for manufacture until you have *
|
||||
* thoroughly inspected the output of this program *
|
||||
* using a previewing program such as: *
|
||||
* *
|
||||
* Windows: *
|
||||
* - GC-Prevue <http://www.graphicode.com> *
|
||||
* - ViewMate <http://www.pentalogix.com> *
|
||||
* *
|
||||
* Linux: *
|
||||
* - gerbv <http://gerbv.sourceforge.net> *
|
||||
* *
|
||||
* By using this program you agree to take full *
|
||||
* responsibility for the correctness of the data *
|
||||
* that is generated by this program. *
|
||||
****************************************************
|
||||
"""[1:-1]
|
||||
|
||||
# [Options] section defaults Data types: "L" = layers (will show layer selection)
|
||||
# "D" = decimal
|
||||
# "DP" = possitive decimal
|
||||
# "I" = integer
|
||||
# "IP" = integer positive
|
||||
# "PI" = path input (will show open dialog)
|
||||
# "PO" = path output (will show save dialog)
|
||||
# "S" = string
|
||||
# "B" = boolean
|
||||
# "BI" = boolean as integer
|
||||
#
|
||||
# THESE DATA TYPES ARE FIXED - CODE MUST CHANGE IF TYPES ARE ADDED/MODIFIED
|
||||
DEFAULT_OPTIONS = {
|
||||
# Spacing in horizontal direction
|
||||
'xspacing': ('0.125', "DP", "XSpacing", "1 XSPACING_HELP"),
|
||||
# Spacing in vertical direction
|
||||
'yspacing': ('0.125', "DP", "YSpacing", "2 YSPACING_HELP"),
|
||||
# X-Dimension maximum panel size (Olimex)
|
||||
'panelwidth': ('12.6', "DP", "PanelWidth", "3 PANEL_WIDTH"),
|
||||
# Y-Dimension maximum panel size (Olimex)
|
||||
'panelheight': ('7.8', "DP", "PanelHeight", "4 PanelHeight"),
|
||||
# e.g., *toplayer,*bottomlayer
|
||||
'cropmarklayers': (None, "L", "CropMarkLayers", "5 CropMarkLayers"),
|
||||
# Width (inches) of crop lines
|
||||
'cropmarkwidth': ('0.01', "DP", "CropMarkWidth", "6 CropMarkWidth"),
|
||||
# as for cropmarklayers
|
||||
'cutlinelayers': (None, "L", "CutLineLayers", "7 CutLineLayers"),
|
||||
# Width (inches) of cut lines
|
||||
'cutlinewidth': ('0.01', "DP", "CutLineWidth", "8 CutLineWidth"),
|
||||
# Minimum dimension for selected layers
|
||||
'minimumfeaturesize': (None, "S", "MinimumFeatureSize", "Use this option to automatically thicken features on particular layers.\nThis is intended for thickening silkscreen to some minimum width.\nThe value of this option must be a comma-separated list\nof layer names followed by minimum feature sizes (in inches) for that layer.\nComment this out to disable thickening. Example usage is:\n\nMinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008"),
|
||||
# Name of file containing default tool list
|
||||
'toollist': (None, "PI", "ToolList", "10 ToolList"),
|
||||
# Tolerance for clustering drill sizes
|
||||
'drillclustertolerance': ('.002', "DP", "DrillClusterTolerance", "11 DrillClusterTolerance"),
|
||||
# Set to 1 to allow multiple jobs to have non-matching layers
|
||||
'allowmissinglayers': (0, "BI", "AllowMissingLayers", "12 AllowMissingLayers"),
|
||||
# Name of file to which to write fabrication drawing, or None
|
||||
'fabricationdrawingfile': (None, "PO", "FabricationDrawingFile", "13 FabricationDrawingFile"),
|
||||
# Name of file containing text to write to fab drawing
|
||||
'fabricationdrawingtext': (None, "PI", "FabricationDrawingText", "14 FabricationDrawingText"),
|
||||
# Number of digits after the decimal point in input Excellon files
|
||||
'excellondecimals': (4, "IP", "ExcellonDecimals", "15 ExcellonDecimals"),
|
||||
# Generate leading zeros in merged Excellon output file
|
||||
'excellonleadingzeros': (0, "IP", "ExcellonLeadingZeros", "16 ExcellonLeadingZeros"),
|
||||
# Name of file to which to write simple box outline, or None
|
||||
'outlinelayerfile': (None, "PO", "OutlineLayerFile", "17 OutlineLayerFile"),
|
||||
# Name of file to which to write scoring data, or None
|
||||
'scoringfile': (None, "PO", "ScoringFile", "18 ScoringFile"),
|
||||
# Inches of extra room to leave on left side of panel for tooling
|
||||
'leftmargin': (0.0, "DP", "LeftMargin", "19 LeftMargin"),
|
||||
# Inches of extra room to leave on top side of panel for tooling
|
||||
'topmargin': (0.0, "DP", "TopMargin", "20 TopMargin"),
|
||||
# Inches of extra room to leave on right side of panel for tooling
|
||||
'rightmargin': (0.0, "DP", "RightMargin", "21 RightMargin"),
|
||||
# Inches of extra room to leave on bottom side of panel for tooling
|
||||
'bottommargin': (0.0, "DP", "BottomMargin", "22 BottomMargin"),
|
||||
# List of X,Y points at which to draw fiducials
|
||||
'fiducialpoints': (None, "S", "FiducialPoints", "23 FiducialPoints"),
|
||||
}
|
||||
DEFAULT_OPTIONS_TYPES = ["IP", "I", "DP", "D", "B", "BI", "S", "PI", "PO", "L"] # List of option types in display order
|
||||
|
||||
# [GerbMergeGUI] section defaults
|
||||
DEFAULT_GERBMERGEGUI = {
|
||||
'unit': "IN", # Unit inidicator: IN, MIL, MM
|
||||
'layout': "AUTOMATIC", # Indicates layout: GRID, AUTOMATIC, MANUAL, GRID_FILE, MANUAL_FILE
|
||||
'runtime': 10, # Seconds to run automatic placement
|
||||
'rows': 1, # Number of rows in grid layout
|
||||
'columns': 1, # Number of columns in grid layout
|
||||
'mergedoutput': False, # Path of output directory
|
||||
'mergedname': False, # Prefix of merged output files
|
||||
'layoutfilepath': "", # Path of layout file
|
||||
'placementfilepath': "", # Path of placement file
|
||||
'configurationfilepath': "", # Path of configuration file
|
||||
'configurationcomplete': False, # Indicates that run dialog may be skipped to upon load
|
||||
}
|
||||
|
||||
# Job names
|
||||
RE_VALID_JOB = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_-]*$')
|
||||
RE_VALID_JOB_MESSAGE = "Vaild Characters: a-z, A-Z, 0-9, underscores, hyphens\nFirst Character must be: a-z, A-Z, 0-9"
|
||||
RESERVED_JOB_NAMES = ("Options", "MergeOutputFiles", "GerbMergeGUI") ##not implemented yet
|
||||
|
||||
# Layer names
|
||||
RE_VALID_LAYER = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_-]*$')
|
||||
RE_VALID_LAYER_MESSAGE = "Vaild Characters: a-z, A-Z, 0-9, underscores, hyphens\nFirst Character must be: a-z, A-Z, 0-9"
|
||||
DEFAULT_LAYERS = [ "BoardOutline",
|
||||
"TopCopper",
|
||||
"BottomCopper",
|
||||
"InnerLayer2",
|
||||
"InnerLayer3",
|
||||
"TopSilkscreen",
|
||||
"BottomSilkscreen",
|
||||
"TopSoldermask",
|
||||
"BottomSoldermask",
|
||||
"TopSolderPasteMask",
|
||||
"BottomSolderPasteMask",
|
||||
"Drills" ]
|
||||
REQUIRED_LAYERS = ["BoardOutline", "Drills"]
|
||||
RESERVED_LAYER_NAMES = () ##add "mergeout", not implemented yet
|
||||
|
||||
#Output names
|
||||
RE_VALID_OUTPUT_NAME = re.compile(r'^[a-zA-Z0-9_-]+$')
|
||||
RE_VALID_OUTPUT_NAME_MESSAGE = "Vaild Characters: a-z, A-Z, 0-9, underscores, hyphens"
|
||||
REQUIRED_LAYERS_OUTPUT = ["BoardOutline", "ToolList", "Placement", "Drills"]
|
||||
|
||||
# Default dictionary of layer names to file extensions
|
||||
FILE_EXTENSIONS = { "boardoutline": "GBO",
|
||||
"topcopper": "GTL",
|
||||
"bottomcopper": "GBL",
|
||||
"innerlayer2": "G2",
|
||||
"innerlayer3": "G3",
|
||||
"topsilkscreen": "GTO",
|
||||
"bottomsilkscreen": "GBO",
|
||||
"topsoldermask": "GTS",
|
||||
"bottomsoldermask": "GBS",
|
||||
"topsolderpastemask": "GTP",
|
||||
"bottomsolderpastemask": "GBP",
|
||||
"drills": "GDD",
|
||||
"placement": "TXT",
|
||||
"toollist": "DRL",
|
||||
}
|
||||
DEFAULT_EXTENSION = "GER"
|
||||
|
||||
#Gerbmerge options
|
||||
PLACE_FILE = "--place-file="
|
||||
NO_TRIM_GERBER = "--no-trim-gerber"
|
||||
NO_TRIM_EXCELLON = "--no-trim-excellon"
|
||||
ROTATED_OCTAGONS = "--octagons=rotate"
|
||||
SEARCH_TIMEOUT = "--search-timeout="
|
||||
|
||||
|
BIN
gerber/gerbmerge/bin/specs.pyc
Normal file
BIN
gerber/gerbmerge/bin/specs.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/specs.pyo
Normal file
BIN
gerber/gerbmerge/bin/specs.pyo
Normal file
Binary file not shown.
38
gerber/gerbmerge/bin/strokes.py
Normal file
38
gerber/gerbmerge/bin/strokes.py
Normal file
File diff suppressed because one or more lines are too long
BIN
gerber/gerbmerge/bin/strokes.pyc
Normal file
BIN
gerber/gerbmerge/bin/strokes.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/strokes.pyo
Normal file
BIN
gerber/gerbmerge/bin/strokes.pyo
Normal file
Binary file not shown.
243
gerber/gerbmerge/bin/tilesearch1.py
Normal file
243
gerber/gerbmerge/bin/tilesearch1.py
Normal file
|
@ -0,0 +1,243 @@
|
|||
#!/usr/bin/env python
|
||||
"""Search for an optimum tiling using brute force exhaustive search
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import config
|
||||
import tiling
|
||||
|
||||
import gerbmerge
|
||||
|
||||
_StartTime = 0.0 # Start time of tiling
|
||||
_CkpointTime = 0.0 # Next time to print stats
|
||||
_Placements = 0L # Number of placements attempted
|
||||
_PossiblePermutations = 0L # Number of different ways of ordering jobs
|
||||
_Permutations = 0L # Number of different job orderings already computed
|
||||
_TBestTiling = None # Best tiling so far
|
||||
_TBestScore = float(sys.maxint) # Smallest area so far
|
||||
_PrintStats = 1 # Print statistics every 3 seconds
|
||||
|
||||
def printTilingStats():
|
||||
global _CkpointTime
|
||||
_CkpointTime = time.time() + 3
|
||||
|
||||
if _TBestTiling:
|
||||
area = _TBestTiling.area()
|
||||
utilization = _TBestTiling.usedArea() / area * 100.0
|
||||
else:
|
||||
area = 999999.0
|
||||
utilization = 0.0
|
||||
|
||||
percent = 100.0*_Permutations/_PossiblePermutations
|
||||
|
||||
print "\r %5.2f%% complete / %ld/%ld Perm/Place / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
|
||||
(percent, _Permutations, _Placements, area, utilization),
|
||||
|
||||
if gerbmerge.GUI is not None:
|
||||
sys.stdout.flush()
|
||||
|
||||
def bestTiling():
|
||||
return _TBestTiling
|
||||
|
||||
def _tile_search1(Jobs, TSoFar, firstAddPoint, cfg=config.Config):
|
||||
"""This recursive function does the following with an existing tiling TSoFar:
|
||||
|
||||
* For each 4-tuple (Xdim,Ydim,job,rjob) in Jobs, the non-rotated 'job' is selected
|
||||
|
||||
* For the non-rotated job, the list of valid add-points is found
|
||||
|
||||
* For each valid add-point, the job is placed at this point in a new,
|
||||
cloned tiling.
|
||||
|
||||
* The function then calls its recursively with the remaining list of
|
||||
jobs.
|
||||
|
||||
* The rotated job is then selected and the list of valid add-points is
|
||||
found. Again, for each valid add-point the job is placed there in
|
||||
a new, cloned tiling.
|
||||
|
||||
* Once again, the function calls itself recursively with the remaining
|
||||
list of jobs.
|
||||
|
||||
* The best tiling encountered from all recursive calls is returned.
|
||||
|
||||
If TSoFar is None it means this combination of jobs is not tileable.
|
||||
|
||||
The side-effect of this function is to set _TBestTiling and _TBestScore
|
||||
to the best tiling encountered so far. _TBestTiling could be None if
|
||||
no valid tilings have been found so far.
|
||||
"""
|
||||
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PrintStats
|
||||
|
||||
if not TSoFar:
|
||||
return (None, float(sys.maxint))
|
||||
|
||||
if not Jobs:
|
||||
# Update the best tiling and score. If the new tiling matches
|
||||
# the best score so far, compare on number of corners, trying to
|
||||
# minimize them.
|
||||
score = TSoFar.area()
|
||||
|
||||
if score < _TBestScore:
|
||||
_TBestTiling,_TBestScore = TSoFar,score
|
||||
elif score == _TBestScore:
|
||||
if TSoFar.corners() < _TBestTiling.corners():
|
||||
_TBestTiling,_TBestScore = TSoFar,score
|
||||
|
||||
_Placements += 1
|
||||
if firstAddPoint:
|
||||
_Permutations += 1
|
||||
return
|
||||
|
||||
xspacing = cfg['xspacing']
|
||||
yspacing = cfg['yspacing']
|
||||
|
||||
minInletSize = tiling.minDimension(Jobs)
|
||||
TSoFar.removeInlets(minInletSize)
|
||||
|
||||
for job_ix in range(len(Jobs)):
|
||||
# Pop off the next job and construct remaining_jobs, a sub-list
|
||||
# of Jobs with the job we've just popped off excluded.
|
||||
Xdim,Ydim,job,rjob = Jobs[job_ix]
|
||||
remaining_jobs = Jobs[:job_ix]+Jobs[job_ix+1:]
|
||||
|
||||
if 0:
|
||||
print "Level %d (%s)" % (level, job.name)
|
||||
TSoFar.joblist()
|
||||
for J in remaining_jobs:
|
||||
print J[2].name, ", ",
|
||||
print
|
||||
print '-'*75
|
||||
|
||||
# Construct add-points for the non-rotated and rotated job.
|
||||
# As an optimization, do not construct add-points for the rotated
|
||||
# job if the job is a square (duh).
|
||||
addpoints1 = TSoFar.validAddPoints(Xdim+xspacing,Ydim+yspacing) # unrotated job
|
||||
if Xdim != Ydim:
|
||||
addpoints2 = TSoFar.validAddPoints(Ydim+xspacing,Xdim+yspacing) # rotated job
|
||||
else:
|
||||
addpoints2 = []
|
||||
|
||||
# Recursively construct tilings for the non-rotated job and
|
||||
# update the best-tiling-so-far as we do so.
|
||||
if addpoints1:
|
||||
for ix in addpoints1:
|
||||
# Clone the tiling we're starting with and add the job at this
|
||||
# add-point.
|
||||
T = TSoFar.clone()
|
||||
T.addJob(ix, Xdim+xspacing, Ydim+yspacing, job)
|
||||
|
||||
# Recursive call with the remaining jobs and this new tiling. The
|
||||
# point behind the last parameter is simply so that _Permutations is
|
||||
# only updated once for each permutation, not once per add-point.
|
||||
# A permutation is some ordering of jobs (N! choices) and some
|
||||
# ordering of non-rotated and rotated within that ordering (2**N
|
||||
# possibilities per ordering).
|
||||
_tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints1[0])
|
||||
elif firstAddPoint:
|
||||
# Premature prune due to not being able to put this job anywhere. We
|
||||
# have pruned off 2^M permutations where M is the length of the remaining
|
||||
# jobs.
|
||||
_Permutations += 2L**len(remaining_jobs)
|
||||
|
||||
if addpoints2:
|
||||
for ix in addpoints2:
|
||||
# Clone the tiling we're starting with and add the job at this
|
||||
# add-point. Remember that the job is rotated so swap X and Y
|
||||
# dimensions.
|
||||
T = TSoFar.clone()
|
||||
T.addJob(ix, Ydim+xspacing, Xdim+yspacing, rjob)
|
||||
|
||||
# Recursive call with the remaining jobs and this new tiling.
|
||||
_tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints2[0])
|
||||
elif firstAddPoint:
|
||||
# Premature prune due to not being able to put this job anywhere. We
|
||||
# have pruned off 2^M permutations where M is the length of the remaining
|
||||
# jobs.
|
||||
_Permutations += 2L**len(remaining_jobs)
|
||||
|
||||
# If we've been at this for 3 seconds, print some status information
|
||||
if _PrintStats and time.time() > _CkpointTime:
|
||||
printTilingStats()
|
||||
|
||||
# Check for timeout
|
||||
if (config.SearchTimeout > 0) and (time.time() - _StartTime > config.SearchTimeout):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
gerbmerge.updateGUI("Performing automatic layout...")
|
||||
|
||||
# end for each job in job list
|
||||
|
||||
def factorial(N):
|
||||
if (N <= 1): return 1L
|
||||
|
||||
prod = long(N)
|
||||
while (N > 2):
|
||||
N -= 1
|
||||
prod *= N
|
||||
|
||||
return prod
|
||||
|
||||
def initialize(printStats=1):
|
||||
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PossiblePermutations, _PrintStats
|
||||
|
||||
_PrintStats = printStats
|
||||
_Placements = 0L
|
||||
_Permutations = 0L
|
||||
_TBestTiling = None
|
||||
_TBestScore = float(sys.maxint)
|
||||
|
||||
def tile_search1(Jobs, X, Y):
|
||||
"""Wrapper around _tile_search1 to handle keyboard interrupt, etc."""
|
||||
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PossiblePermutations
|
||||
|
||||
initialize()
|
||||
|
||||
_StartTime = time.time()
|
||||
_CkpointTime = _StartTime + 3
|
||||
# There are (2**N)*(N!) possible permutations where N is the number of jobs.
|
||||
# This is assuming all jobs are unique and each job has a rotation (i.e., is not
|
||||
# square). Practically, these assumptions make no difference because the software
|
||||
# currently doesn't optimize for cases of repeated jobs.
|
||||
_PossiblePermutations = (2L**len(Jobs))*factorial(len(Jobs))
|
||||
#print "Possible permutations:", _PossiblePermutations
|
||||
|
||||
print '='*70
|
||||
print "Starting placement using exhaustive search."
|
||||
print "There are %ld possible permutations..." % _PossiblePermutations,
|
||||
if _PossiblePermutations < 1e4:
|
||||
print "this'll take no time at all."
|
||||
elif _PossiblePermutations < 1e5:
|
||||
print "surf the web for a few minutes."
|
||||
elif _PossiblePermutations < 1e6:
|
||||
print "take a long lunch."
|
||||
elif _PossiblePermutations < 1e7:
|
||||
print "come back tomorrow."
|
||||
else:
|
||||
print "don't hold your breath."
|
||||
print "Press Ctrl-C to stop and use the best placement so far."
|
||||
print "Estimated maximum possible utilization is %.1f%%." % (tiling.maxUtilization(Jobs)*100)
|
||||
|
||||
try:
|
||||
_tile_search1(Jobs, tiling.Tiling(X,Y), 1)
|
||||
printTilingStats()
|
||||
print
|
||||
except KeyboardInterrupt:
|
||||
printTilingStats()
|
||||
print
|
||||
print "Interrupted."
|
||||
|
||||
computeTime = time.time() - _StartTime
|
||||
print "Computed %ld placements in %d seconds / %.1f placements/second" % (_Placements, computeTime, _Placements/computeTime)
|
||||
print '='*70
|
||||
|
||||
return _TBestTiling
|
BIN
gerber/gerbmerge/bin/tilesearch1.pyc
Normal file
BIN
gerber/gerbmerge/bin/tilesearch1.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/tilesearch1.pyo
Normal file
BIN
gerber/gerbmerge/bin/tilesearch1.pyo
Normal file
Binary file not shown.
147
gerber/gerbmerge/bin/tilesearch2.py
Normal file
147
gerber/gerbmerge/bin/tilesearch2.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env python
|
||||
"""Tile search using random placement and evaluation. Works surprisingly well.
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
|
||||
import config
|
||||
import tiling
|
||||
import tilesearch1
|
||||
|
||||
import gerbmerge
|
||||
|
||||
_StartTime = 0.0 # Start time of tiling
|
||||
_CkpointTime = 0.0 # Next time to print stats
|
||||
_Placements = 0L # Number of placements attempted
|
||||
_TBestTiling = None # Best tiling so far
|
||||
_TBestScore = float(sys.maxint) # Smallest area so far
|
||||
|
||||
def printTilingStats():
|
||||
global _CkpointTime
|
||||
_CkpointTime = time.time() + 3
|
||||
|
||||
if _TBestTiling:
|
||||
area = _TBestTiling.area()
|
||||
utilization = _TBestTiling.usedArea() / area * 100.0
|
||||
else:
|
||||
area = 999999.0
|
||||
utilization = 0.0
|
||||
|
||||
print "\r %ld placements / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
|
||||
(_Placements, area, utilization),
|
||||
|
||||
if gerbmerge.GUI is not None:
|
||||
sys.stdout.flush()
|
||||
|
||||
def _tile_search2(Jobs, X, Y, cfg=config.Config):
|
||||
global _CkpointTime, _Placements, _TBestTiling, _TBestScore
|
||||
|
||||
r = random.Random()
|
||||
N = len(Jobs)
|
||||
|
||||
# M is the number of jobs that will be placed randomly.
|
||||
# N-M is the number of jobs that will be searched exhaustively.
|
||||
M = N - config.RandomSearchExhaustiveJobs
|
||||
M = max(M,0)
|
||||
|
||||
xspacing = cfg['xspacing']
|
||||
yspacing = cfg['yspacing']
|
||||
|
||||
# Must escape with Ctrl-C
|
||||
while 1:
|
||||
T = tiling.Tiling(X,Y)
|
||||
joborder = r.sample(range(N), N)
|
||||
|
||||
minInletSize = tiling.minDimension(Jobs)
|
||||
|
||||
for ix in joborder[:M]:
|
||||
Xdim,Ydim,job,rjob = Jobs[ix]
|
||||
|
||||
T.removeInlets(minInletSize)
|
||||
|
||||
if r.choice([0,1]):
|
||||
addpoints = T.validAddPoints(Xdim+xspacing,Ydim+yspacing)
|
||||
if not addpoints:
|
||||
break
|
||||
|
||||
pt = r.choice(addpoints)
|
||||
T.addJob(pt, Xdim+xspacing, Ydim+yspacing, job)
|
||||
else:
|
||||
addpoints = T.validAddPoints(Ydim+xspacing,Xdim+yspacing)
|
||||
if not addpoints:
|
||||
break
|
||||
|
||||
pt = r.choice(addpoints)
|
||||
T.addJob(pt, Ydim+xspacing, Xdim+yspacing, rjob)
|
||||
else:
|
||||
# Do exhaustive search on remaining jobs
|
||||
if N-M:
|
||||
remainingJobs = []
|
||||
for ix in joborder[M:]:
|
||||
remainingJobs.append(Jobs[ix])
|
||||
|
||||
tilesearch1.initialize(0)
|
||||
tilesearch1._tile_search1(remainingJobs, T, 1)
|
||||
T = tilesearch1.bestTiling()
|
||||
|
||||
if T:
|
||||
score = T.area()
|
||||
|
||||
if score < _TBestScore:
|
||||
_TBestTiling,_TBestScore = T,score
|
||||
elif score == _TBestScore:
|
||||
if T.corners() < _TBestTiling.corners():
|
||||
_TBestTiling,_TBestScore = T,score
|
||||
|
||||
_Placements += 1
|
||||
|
||||
# If we've been at this for 3 seconds, print some status information
|
||||
if time.time() > _CkpointTime:
|
||||
printTilingStats()
|
||||
|
||||
# Check for timeout
|
||||
if (config.SearchTimeout > 0) and ((time.time() - _StartTime) > config.SearchTimeout):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
gerbmerge.updateGUI("Performing automatic layout...")
|
||||
|
||||
# end while 1
|
||||
|
||||
def tile_search2(Jobs, X, Y):
|
||||
"""Wrapper around _tile_search2 to handle keyboard interrupt, etc."""
|
||||
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore
|
||||
|
||||
_StartTime = time.time()
|
||||
_CkpointTime = _StartTime + 3
|
||||
_Placements = 0L
|
||||
_TBestTiling = None
|
||||
_TBestScore = float(sys.maxint)
|
||||
|
||||
print '='*70
|
||||
print "Starting random placement trials. You must press Ctrl-C to"
|
||||
print "stop the process and use the best placement so far."
|
||||
print "Estimated maximum possible utilization is %.1f%%." % (tiling.maxUtilization(Jobs)*100)
|
||||
|
||||
try:
|
||||
_tile_search2(Jobs, X, Y)
|
||||
printTilingStats()
|
||||
print
|
||||
except KeyboardInterrupt:
|
||||
printTilingStats()
|
||||
print
|
||||
print "Interrupted."
|
||||
|
||||
computeTime = time.time() - _StartTime
|
||||
print "Computed %ld placements in %d seconds / %.1f placements/second" % (_Placements, computeTime, _Placements/computeTime)
|
||||
print '='*70
|
||||
|
||||
return _TBestTiling
|
BIN
gerber/gerbmerge/bin/tilesearch2.pyc
Normal file
BIN
gerber/gerbmerge/bin/tilesearch2.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/tilesearch2.pyo
Normal file
BIN
gerber/gerbmerge/bin/tilesearch2.pyo
Normal file
Binary file not shown.
377
gerber/gerbmerge/bin/tiling.py
Normal file
377
gerber/gerbmerge/bin/tiling.py
Normal file
|
@ -0,0 +1,377 @@
|
|||
#!/usr/bin/env python
|
||||
"""A tiling is an arrangement of jobs, where each job may
|
||||
be a copy of another and may be rotated. A tiling consists
|
||||
of two things:
|
||||
|
||||
- a list of where each job is located (the lower-left of
|
||||
each job is the origin)
|
||||
|
||||
- a list of points that begins at (0,Ymax) and ends at
|
||||
(Xmax,0). These points describe the outside boundary
|
||||
of the tiling.
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
import sys
|
||||
import math
|
||||
|
||||
import config
|
||||
import jobs
|
||||
|
||||
# Helper functions to determine if points are right-of, left-of, above, and
|
||||
# below each other. These definitions assume that points are on a line that
|
||||
# is vertical or horizontal.
|
||||
def left_of(p1,p2):
|
||||
return p1[0]<p2[0] and p1[1]==p2[1]
|
||||
|
||||
def right_of(p1,p2):
|
||||
return p1[0]>p2[0] and p1[1]==p2[1]
|
||||
|
||||
def above(p1,p2):
|
||||
return p1[1]>p2[1] and p1[0]==p2[0]
|
||||
|
||||
def below(p1,p2):
|
||||
return p1[1]<p2[1] and p1[0]==p2[0]
|
||||
|
||||
class Tiling:
|
||||
def __init__(self, Xmax, Ymax):
|
||||
# Make maximum dimensions bigger by inter-job spacing so that
|
||||
# we allow jobs (which are seated at the lower left of their cells)
|
||||
# to just fit on the panel, and not disqualify them because their
|
||||
# spacing area slightly exceeds the panel edge.
|
||||
self.xmax = Xmax + config.Config['xspacing']
|
||||
self.ymax = Ymax + config.Config['yspacing']
|
||||
|
||||
self.points = [(0,Ymax), (0,0), (Xmax,0)] # List of (X,Y) co-ordinates
|
||||
self.jobs = [] # List of 3-tuples: ((Xbl,Ybl),(Xtr,Ytr),Job) where
|
||||
# (Xbl,Ybl) is bottom left, (Xtr,Ytr) is top-right of the cell.
|
||||
# The actual job has dimensions (Xtr-Xbl-Config['xspacing'],Ytr-Ybl-Config['yspacing'])
|
||||
# and is located at the lower-left of the cell.
|
||||
|
||||
def canonicalize(self, OriginX, OriginY):
|
||||
"""Return a list of JobLayout objects, after setting each job's (X,Y) origin"""
|
||||
L = []
|
||||
for job in self.jobs:
|
||||
J = jobs.JobLayout(job[2])
|
||||
J.setPosition(job[0][0]+OriginX, job[0][1]+OriginY)
|
||||
L.append(J)
|
||||
|
||||
return L
|
||||
|
||||
def corners(self):
|
||||
return len(self.points)-2
|
||||
|
||||
def clone(self):
|
||||
T = Tiling(self.xmax-config.Config['xspacing'], self.ymax-config.Config['yspacing'])
|
||||
T.points = self.points[:]
|
||||
T.jobs = self.jobs[:]
|
||||
return T
|
||||
|
||||
def dump(self, fid=sys.stdout):
|
||||
fid.write("Points:\n ")
|
||||
count = 0
|
||||
for XY in self.points:
|
||||
fid.write("%s " % str(XY))
|
||||
count += 1
|
||||
if count==8:
|
||||
fid.write("\n ")
|
||||
count=0
|
||||
if count:
|
||||
fid.write("\n")
|
||||
|
||||
fid.write("Jobs:\n")
|
||||
for bl,tr,Job in self.jobs:
|
||||
fid.write(" %s: %s\n" % (str(Job), str(bl)))
|
||||
|
||||
def joblist(self, fid=sys.stdout):
|
||||
for bl,tr,Job in self.jobs:
|
||||
fid.write("%s@(%.1f,%.1f) " % (Job.name,bl[0],bl[1]))
|
||||
fid.write('\n')
|
||||
|
||||
def isOverlap(self, ix, X, Y, cfg=config.Config):
|
||||
"""Determines if a new job with actual dimensions X-by-Y located at self.points[ix]
|
||||
overlaps any existing job or exceeds the boundaries of the panel.
|
||||
|
||||
If it's an L-point, the new job will have position
|
||||
|
||||
p_bl=(self.points[ix][0],self.points[ix][1])
|
||||
|
||||
and top-right co-ordinate:
|
||||
|
||||
p_tr=(self.points[ix][0]+X,self.points[ix][1]+Y)
|
||||
|
||||
If it's a mirror-L point, the new job will have position
|
||||
|
||||
p_bl=(self.points[ix][0]-X,self.points[ix][1])
|
||||
|
||||
and top-right co-ordinate:
|
||||
|
||||
p_tr=(self.points[ix][0],self.points[ix][1]+Y)
|
||||
|
||||
For a test job defined by t_bl and t_tr, the given job overlaps
|
||||
if:
|
||||
p.left_edge<t.right_edge and p.right_edge>t.left_edge
|
||||
and
|
||||
p.bottom_edge<t.top_edge and p.top_edge>t.bottom_edge
|
||||
"""
|
||||
if self.isL(ix):
|
||||
p_bl = self.points[ix]
|
||||
p_tr = (p_bl[0]+X, p_bl[1]+Y)
|
||||
if p_tr[0]>self.xmax or p_tr[1]>self.ymax:
|
||||
return 1
|
||||
else:
|
||||
p_bl = (self.points[ix][0]-X,self.points[ix][1])
|
||||
p_tr = (self.points[ix][0],self.points[ix][1]+Y)
|
||||
if p_bl[0]<0 or p_tr[1]>self.ymax:
|
||||
return 1
|
||||
|
||||
for t_bl,t_tr,Job in self.jobs:
|
||||
if p_bl[0]<t_tr[0] and p_tr[0]>t_bl[0] \
|
||||
and \
|
||||
p_bl[1]<t_tr[1] and p_tr[1]>t_bl[1]:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
def isL(self, ix):
|
||||
"""True if self.points[ix] represents an L-shaped corner where there
|
||||
is free space above and to the right, like this:
|
||||
|
||||
+------+ _____ this point is an L-shaped corner
|
||||
| | /
|
||||
| +______+
|
||||
| |
|
||||
| |
|
||||
. .
|
||||
. .
|
||||
. .
|
||||
"""
|
||||
pts = self.points
|
||||
# This is an L-point if:
|
||||
# Previous point X co-ordinates are the same, and
|
||||
# previous point Y co-ordinate is higher, and
|
||||
# next point Y co-ordinate is the same, and
|
||||
# next point X co-ordinate is to the right
|
||||
return pts[ix-1][0]==pts[ix][0] \
|
||||
and pts[ix-1][1]>pts[ix][1] \
|
||||
and pts[ix+1][1]==pts[ix][1] \
|
||||
and pts[ix+1][0]>pts[ix][0]
|
||||
|
||||
def isMirrorL(self, ix):
|
||||
"""True if self.points[ix] represents a mirrored L-shaped corner where there
|
||||
is free space above and to the left, like this:
|
||||
|
||||
+------+
|
||||
mirrored-L corner __ | |
|
||||
\ | |
|
||||
+______+ |
|
||||
| |
|
||||
| |
|
||||
. .
|
||||
. .
|
||||
. .
|
||||
"""
|
||||
pts = self.points
|
||||
# This is a mirrored L-point if:
|
||||
# Previous point Y co-ordinates are the same, and
|
||||
# previous point X co-ordinate is lower, and
|
||||
# next point X co-ordinate is the same, and
|
||||
# next point X co-ordinate is higher
|
||||
return pts[ix-1][1]==pts[ix][1] \
|
||||
and pts[ix-1][0]<pts[ix][0] \
|
||||
and pts[ix+1][0]==pts[ix][0] \
|
||||
and pts[ix+1][1]>pts[ix][1]
|
||||
|
||||
def validAddPoints(self, X, Y):
|
||||
"""Return a list of all valid indices into self.points at which we can add
|
||||
the job with dimensions X-by-Y). Only points which are either L-points or
|
||||
mirrored-L-points and which would support the given job with no overlaps
|
||||
are returned.
|
||||
"""
|
||||
return [ix for ix in range(1,len(self.points)-1) if (self.isL(ix) or self.isMirrorL(ix)) and not self.isOverlap(ix,X,Y)]
|
||||
|
||||
def mergePoints(self, ix):
|
||||
"""Inspect points self.points[ix] and self.points[ix+1] as well
|
||||
as self.points[ix+3] and self.points[ix+4]. If they are the same, delete
|
||||
both points, thus merging lines formed when the corners of two jobs coincide.
|
||||
"""
|
||||
|
||||
# Do farther-on points first so we can delete things right from the list
|
||||
if self.points[ix+3]==self.points[ix+4]:
|
||||
del self.points[ix+3:ix+5]
|
||||
|
||||
if self.points[ix]==self.points[ix+1]:
|
||||
del self.points[ix:ix+2]
|
||||
|
||||
# Experimental
|
||||
def removeInlets(self, minSize):
|
||||
"""Find sequences of 3 points that define an "inlet", either a left/right-going gap:
|
||||
|
||||
...---------------+ +--------.....
|
||||
| |
|
||||
| |
|
||||
+------------+ +------------+
|
||||
| |
|
||||
+----------------------+ +--------------------+
|
||||
| |
|
||||
. .
|
||||
. .
|
||||
. .
|
||||
or a down-going gap: +-------.....
|
||||
|
|
||||
...-----------+ ...-------------+ |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| +-----.... | |
|
||||
| | | |
|
||||
| | | |
|
||||
+--+ +--+
|
||||
|
||||
that are too small for any job to fit in (as defined by minSize). These inlets
|
||||
can be deleted to form corners where new jobs can be placed.
|
||||
"""
|
||||
pt = self.points
|
||||
done = 0
|
||||
|
||||
while not done:
|
||||
# Repeat this loop each time there is a change
|
||||
for ix in range(0, len(pt)-3):
|
||||
|
||||
# Check for horizontal left-going inlet
|
||||
if right_of(pt[ix],pt[ix+1]) and above(pt[ix+1],pt[ix+2]) and left_of(pt[ix+2],pt[ix+3]):
|
||||
# Make sure minSize requirement is met
|
||||
if pt[ix][1]-pt[ix+3][1] < minSize:
|
||||
# Get rid of middle two points, extend Y-value of highest point down to lowest point
|
||||
pt[ix] = (pt[ix][0],pt[ix+3][1])
|
||||
del pt[ix+1:ix+3]
|
||||
break
|
||||
|
||||
# Check for horizontal right-going inlet
|
||||
if left_of(pt[ix],pt[ix+1]) and below(pt[ix+1],pt[ix+2]) and right_of(pt[ix+2],pt[ix+3]):
|
||||
# Make sure minSize requirement is met
|
||||
if pt[ix+3][1]-pt[ix][1] < minSize:
|
||||
# Get rid of middle two points, exten Y-value of highest point down to lowest point
|
||||
pt[ix+3] = (pt[ix+3][0], pt[ix][1])
|
||||
del pt[ix+1:ix+3]
|
||||
break
|
||||
|
||||
# Check for vertical inlets
|
||||
if above(pt[ix],pt[ix+1]) and left_of(pt[ix+1],pt[ix+2]) and below(pt[ix+2],pt[ix+3]):
|
||||
# Make sure minSize requirement is met
|
||||
if pt[ix+3][0]-pt[ix][0] < minSize:
|
||||
# Is right side lower or higher?
|
||||
if pt[ix+3][1]>=pt[ix][1]: # higher?
|
||||
pt[ix] = (pt[ix+3][0], pt[ix][1]) # Move first point to the right
|
||||
else: # lower?
|
||||
pt[ix+3] = (pt[ix][0], pt[ix+3][1]) # Move last point to the left
|
||||
del pt[ix+1:ix+3]
|
||||
break
|
||||
|
||||
else:
|
||||
done = 1
|
||||
|
||||
def addLJob(self, ix, X, Y, Job, cfg=config.Config):
|
||||
"""Add a job to the tiling at L-point self.points[ix] with actual dimensions X-by-Y.
|
||||
The job is added with its lower-left corner at the point. The existing point
|
||||
is removed from the tiling and new points are added at the top-left, top-right
|
||||
and bottom-right of the new job, with extra space added for inter-job spacing.
|
||||
"""
|
||||
x,y = self.points[ix]
|
||||
x_tr = x+X
|
||||
y_tr = y+Y
|
||||
self.points[ix:ix+1] = [(x,y_tr), (x_tr,y_tr), (x_tr,y)]
|
||||
self.jobs.append( ((x,y),(x_tr,y_tr),Job) )
|
||||
|
||||
self.mergePoints(ix-1)
|
||||
|
||||
def addMirrorLJob(self, ix, X, Y, Job, cfg=config.Config):
|
||||
"""Add a job to the tiling at mirror-L-point self.points[ix] with dimensions X-by-Y.
|
||||
The job is added with its lower-right corner at the point. The existing point
|
||||
is removed from the tiling and new points are added at the bottom-left, top-left
|
||||
and top-right of the new job, with extra space added for inter-job spacing.
|
||||
"""
|
||||
x_tr,y = self.points[ix]
|
||||
x = x_tr-X
|
||||
y_tr = y+Y
|
||||
self.points[ix:ix+1] = [(x,y), (x,y_tr), (x_tr,y_tr)]
|
||||
self.jobs.append( ((x,y),(x_tr,y_tr),Job) )
|
||||
|
||||
self.mergePoints(ix-1)
|
||||
|
||||
def addJob(self, ix, X, Y, Job):
|
||||
"""Add a job to the tiling at point self.points[ix] and with dimensions X-by-Y.
|
||||
If the given point is an L-point, the job will be added with its lower-left
|
||||
corner at the point. If the given point is a mirrored-L point, the job will
|
||||
be added with its lower-right corner at the point.
|
||||
"""
|
||||
if self.isL(ix):
|
||||
self.addLJob(ix, X, Y, Job)
|
||||
else:
|
||||
self.addMirrorLJob(ix, X, Y, Job)
|
||||
|
||||
def bounds(self):
|
||||
"""Return 2-tuple ((minX, minY), (maxX, maxY)) of rectangular region defined by all jobs"""
|
||||
minX = minY = float(sys.maxint)
|
||||
maxX = maxY = 0.0
|
||||
|
||||
for bl,tr,job in self.jobs:
|
||||
minX = min(minX,bl[0])
|
||||
maxX = max(maxX,tr[0])
|
||||
minY = min(minY,bl[1])
|
||||
maxY = max(maxY,tr[1])
|
||||
|
||||
return ( (minX,minY), (maxX-config.Config['xspacing'], maxY-config.Config['yspacing']) )
|
||||
|
||||
def area(self):
|
||||
"""Return area of rectangular region defined by all jobs."""
|
||||
bl,tr = self.bounds()
|
||||
|
||||
DX = tr[0]-bl[0]
|
||||
DY = tr[1]-bl[1]
|
||||
return DX*DY
|
||||
|
||||
def usedArea(self):
|
||||
"""Return total area of just jobs, not spaces in-between."""
|
||||
area = 0.0
|
||||
for job in self.jobs:
|
||||
area += job[2].jobarea()
|
||||
|
||||
return area
|
||||
|
||||
# Function to estimate the maximum possible utilization given a list of jobs.
|
||||
# Jobs list is 4-tuple (Xdim,Ydim,job,rjob).
|
||||
def maxUtilization(Jobs):
|
||||
xspacing = config.Config['xspacing']
|
||||
yspacing = config.Config['yspacing']
|
||||
|
||||
usedArea = totalArea = 0.0
|
||||
for Xdim,Ydim,job,rjob in Jobs:
|
||||
usedArea += job.jobarea()
|
||||
totalArea += job.jobarea()
|
||||
totalArea += job.width_in()*xspacing + job.height_in()*yspacing + xspacing*yspacing
|
||||
|
||||
# Reduce total area by strip of unused spacing around top and side. Assume
|
||||
# final result will be approximately square.
|
||||
sq_side = math.sqrt(totalArea)
|
||||
totalArea -= sq_side*xspacing + sq_side*yspacing + xspacing*yspacing
|
||||
|
||||
return usedArea/totalArea
|
||||
|
||||
# Utility function to compute the minimum dimension along any axis of all jobs.
|
||||
# Used to remove inlets.
|
||||
def minDimension(Jobs):
|
||||
M = float(sys.maxint)
|
||||
for Xdim,Ydim,job,rjob in Jobs:
|
||||
M = min(M,Xdim)
|
||||
M = min(M,Ydim)
|
||||
return M
|
||||
|
||||
# vim: expandtab ts=2 sw=2
|
BIN
gerber/gerbmerge/bin/tiling.pyc
Normal file
BIN
gerber/gerbmerge/bin/tiling.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/tiling.pyo
Normal file
BIN
gerber/gerbmerge/bin/tiling.pyo
Normal file
Binary file not shown.
20
gerber/gerbmerge/bin/util.py
Normal file
20
gerber/gerbmerge/bin/util.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Various utility functions
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This program is licensed under the GNU General Public License (GPL)
|
||||
Version 3. See http://www.fsf.org for details of the license.
|
||||
|
||||
Rugged Circuits LLC
|
||||
http://ruggedcircuits.com/gerbmerge
|
||||
"""
|
||||
|
||||
def in2gerb(value):
|
||||
"""Convert inches to 2.5 Gerber units"""
|
||||
return int(round(value*1e5))
|
||||
|
||||
def gerb2in(value):
|
||||
"""Convert 2.5 Gerber units to inches"""
|
||||
return float(value)*1e-5
|
BIN
gerber/gerbmerge/bin/util.pyc
Normal file
BIN
gerber/gerbmerge/bin/util.pyc
Normal file
Binary file not shown.
BIN
gerber/gerbmerge/bin/util.pyo
Normal file
BIN
gerber/gerbmerge/bin/util.pyo
Normal file
Binary file not shown.
10
gerber/gerbmerge/gerbmerge-win32.bat
Normal file
10
gerber/gerbmerge/gerbmerge-win32.bat
Normal file
|
@ -0,0 +1,10 @@
|
|||
::@echo off
|
||||
|
||||
:: panel processed using gerbv python script
|
||||
:: from CAM job made via eagle PCB[2]
|
||||
|
||||
c:\python27\python .\bin\gerbmerge.py tami.cfg tami.def
|
||||
rm 4x4\*
|
||||
mv ATtami.* 4x4
|
||||
:: c:\python27\python C:\Python27\Lib\site-packages\gerbmerge\gerbmerge.py tami.cfg tami.def
|
||||
pause
|
16
gerber/gerbmerge/placement.ATtami.nfo
Normal file
16
gerber/gerbmerge/placement.ATtami.nfo
Normal file
|
@ -0,0 +1,16 @@
|
|||
Proj1 0.110 0.110
|
||||
Proj1 0.110 1.073
|
||||
Proj1 0.110 2.035
|
||||
Proj1 0.110 2.998
|
||||
Proj1 1.071 0.110
|
||||
Proj1 1.071 1.073
|
||||
Proj1 1.071 2.035
|
||||
Proj1 1.071 2.998
|
||||
Proj1 2.033 0.110
|
||||
Proj1 2.033 1.073
|
||||
Proj1 2.033 2.035
|
||||
Proj1 2.033 2.998
|
||||
Proj1 2.994 0.110
|
||||
Proj1 2.994 1.073
|
||||
Proj1 2.994 2.035
|
||||
Proj1 2.994 2.998
|
14
gerber/gerbmerge/readme.txt
Normal file
14
gerber/gerbmerge/readme.txt
Normal file
|
@ -0,0 +1,14 @@
|
|||
== ATtami-ver-17 8/19/2014 ==
|
||||
gerber files for 4x4 panel (10x10cm)
|
||||
|
||||
panel processed using gerbv[1] python script
|
||||
from CAM job made via eagle PCB[2]
|
||||
includes .gvp file which is a gerber viewer[3]
|
||||
|
||||
|
||||
contact yair99@gmail.com
|
||||
|
||||
|
||||
[1] http://174.136.57.11/~ruggedci/gerbmerge/
|
||||
[2] http://www.cadsoftusa.com/
|
||||
[3] http://gerbv.sourceforge.net/
|
273
gerber/gerbmerge/tami.cfg
Normal file
273
gerber/gerbmerge/tami.cfg
Normal file
|
@ -0,0 +1,273 @@
|
|||
# This configuration file demonstrates panelizing a single job.
|
||||
|
||||
##############################################################################
|
||||
# In the [DEFAULT] section you can create global names to save typing the same
|
||||
# directory name, for example, over and over.
|
||||
##############################################################################
|
||||
[DEFAULT]
|
||||
|
||||
# Change projdir to wherever your project files are, for example:
|
||||
#
|
||||
# projdir = /home/stuff/projects/test
|
||||
#
|
||||
# or relative pathname from where you are running GerbMerge
|
||||
#
|
||||
# projdir = testdata
|
||||
#
|
||||
# or if all files are in the current directory (as in this example):
|
||||
#
|
||||
# projdir = .
|
||||
projdir = .
|
||||
|
||||
# For convenience, this is the base name of the merged output files.
|
||||
MergeOut = ATtami
|
||||
|
||||
#############################################################################
|
||||
# The [Options] section defines settings that control how the input files are
|
||||
# read and how the output files are generated.
|
||||
#############################################################################
|
||||
[Options]
|
||||
|
||||
################################################################
|
||||
#
|
||||
# Settings that are very important
|
||||
#
|
||||
################################################################
|
||||
|
||||
# Option indicating name of file that maps Excellon tool codes to drill sizes.
|
||||
# This is not necessary if the Excellon files have embedded tool sizes, or if a
|
||||
# tool list is specified as part of the job description. The ToolList option
|
||||
# here is the "last resort" for mapping tool codes to tool sizes. Most recent
|
||||
# PCB programs embed drill size information right in the Excellon file, so this
|
||||
# option should not be necessary and can be commented out.
|
||||
#ToolList=proj1.drl
|
||||
|
||||
# Optional indication of the number of decimal places in input Excellon drill
|
||||
# files. The default is 4 which works for recent versions of Eagle (since
|
||||
# version 4.11r12), as well as Orcad and PCB. Older versions of Eagle use 3
|
||||
# decimal places.
|
||||
#ExcellonDecimals = 4
|
||||
|
||||
################################################################
|
||||
#
|
||||
# Settings that are somewhat important
|
||||
#
|
||||
################################################################
|
||||
|
||||
# Which layers to draw cut lines on. Omit this option or set to 'None' for no
|
||||
# cut lines. Cut lines are borders around each job that serve as guides for
|
||||
# cutting the panel into individual jobs. Option 'CutLineWidth' sets the
|
||||
# thickness of these cut lines.
|
||||
#
|
||||
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
|
||||
# letters below.
|
||||
#CutLineLayers = *topsilkscreen,*bottomsilkscreen
|
||||
|
||||
# Which layers to draw crop marks on. Omit this option or set to 'None' for no
|
||||
# crop marks. Crop marks are small L-shaped marks at the 4 corners of the final
|
||||
# panel. These practically define the extents of the panel and are required by
|
||||
# some board manufacturers. Crop marks are also required if you want to leave
|
||||
# extra space around the final panel for tooling or handling. Option
|
||||
# 'CropMarkWidth' sets the thickness of these crop marks.
|
||||
#
|
||||
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
|
||||
# letters below.
|
||||
#CropMarkLayers = *topsilkscreen,*bottomsilkscreen
|
||||
|
||||
# Set this option to the name of a file in which to write a Gerber fabrication
|
||||
# drawing. Some board manufacturers require a fabrication drawing with panel
|
||||
# dimensions and drill hit marks and drill legend. There's no harm in creating
|
||||
# this file...you can ignore it if you don't need it.
|
||||
#FabricationDrawingFile = %(mergeout)s.fab
|
||||
|
||||
# If FabricationDrawingFile is specified, you can provide an optional file name
|
||||
# of a file containing arbitrary text to add to the fabrication drawing. This
|
||||
# text can indicate manufacturing information, contact information, etc.
|
||||
#FabricationDrawingText = %(projdir)s/fabdwg.txt
|
||||
|
||||
# Option to generate leading zeros in the output Excellon drill file, i.e., to
|
||||
# NOT use leading-zero suppression. Some Gerber viewers cannot properly guess
|
||||
# the Excellon file format when there are no leading zeros. Set this option to
|
||||
# 1 if your Gerber viewer is putting the drill holes in far off places that do
|
||||
# not line up with component pads.
|
||||
ExcellonLeadingZeros = 0
|
||||
|
||||
# Optional additional Gerber layer on which to draw a rectangle defining the
|
||||
# extents of the entire panelized job. This will create a Gerber file (with
|
||||
# name specified by this option) that simply contains a rectangle defining the
|
||||
# outline of the final panel. This outline file is useful for circuit board
|
||||
# milling to indicate a path for the router tool. There's no harm in creating
|
||||
# this file...you can ignore it if you don't need it.
|
||||
# OutlineLayerFile = %(mergeout)s.oln
|
||||
|
||||
# Optional additional Gerber layer on which to draw horizontal and vertical
|
||||
# lines describing where to score (i.e., V-groove) the panel so that jobs
|
||||
# can easily snap apart. These scoring lines will be drawn half-way between
|
||||
# job borders.
|
||||
ScoringFile = %(mergeout)s.sco
|
||||
|
||||
# Set the maximum dimensions of the final panel, if known. You can set the
|
||||
# dimensions of the maximum panel size supported by your board manufacturer,
|
||||
# and GerbMerge will print an error message if your layout exceeds these
|
||||
# dimensions. Alternatively, when using automatic placement, the panel sizes
|
||||
# listed here constrain the random placements such that only placements that
|
||||
# fit within the given panel dimensions will be considered. The dimensions are
|
||||
# specified in inches.
|
||||
# TAMI = 10x10cm in out case
|
||||
PanelWidth = 3.93701
|
||||
PanelHeight = 3.93701
|
||||
|
||||
# Set the amount of extra space to leave around the edges of the panel to
|
||||
# simplify tooling and handling. These margins are specified in inches, and
|
||||
# default to 0" if not specified. These spacings will only be visible to the
|
||||
# board manufacturer if you enable crop marks (see CropMarkLayers above) or use.
|
||||
LeftMargin = 0.01
|
||||
RightMargin = 0.01
|
||||
TopMargin = 0.01
|
||||
BottomMargin = 0.01
|
||||
|
||||
################################################################
|
||||
#
|
||||
# Settings that are probably not important
|
||||
#
|
||||
################################################################
|
||||
|
||||
# Set the inter-job spacing (inches) in both the X-dimension (width) and
|
||||
# Y-dimension (height). Normally these would be the same unless you're trying
|
||||
# really hard to make your jobs fit into a panel of exact size and you need to
|
||||
# tweak these spacings to make it work. 0.125" is probably generous, about half
|
||||
# that is practical for using a band saw, but you probably want to leave it at
|
||||
# 0.125" if you have copper features close to the board edges and/or are using
|
||||
# less precise tools, like a hacksaw, for separating the boards.
|
||||
XSpacing = 0.0625
|
||||
YSpacing = 0.0625
|
||||
|
||||
# Width of cut lines, in inches. The default value is 0.01". These are drawn on
|
||||
# the layers specified by CutLineLayers.
|
||||
CutLineWidth = 0.01
|
||||
|
||||
# Width of crop marks, in inches. The default value is 0.01". These are drawn on
|
||||
# the layers specified by CropMarkLayers.
|
||||
CropMarkWidth = 0.01
|
||||
|
||||
# This option is intended to reduce the probability of forgetting to include a
|
||||
# layer in a job description when panelizing two or more different jobs.
|
||||
# Unless this option is set to 1, an error will be raised if some jobs do not
|
||||
# have the same layer names as the others, i.e., are missing layers. For
|
||||
# example, if one job has a top-side soldermask layer and another doesn't, that
|
||||
# could be a mistake. Setting this option to 1 prevents this situation from
|
||||
# raising an error.
|
||||
AllowMissingLayers = 0
|
||||
|
||||
# This option is intended to reduce the number of drills in the output by
|
||||
# eliminating drill sizes that are too close to make a difference. For example,
|
||||
# it probably does not make sense to have two separate 0.031" and 0.0315"
|
||||
# drills. The DrillClusterTolerance value specifies how much tolerance is
|
||||
# allowed in drill sizes, in units of inches. Multiple drill tools that span
|
||||
# twice this tolerance will be clustered into a single drill tool. For example,
|
||||
# a set of 0.031", 0.0315", 0.032", and 0.034" drills will all be replaced by a
|
||||
# single drill tool of diameter (0.031"+0.034")/2 = 0.0325". It is guaranteed
|
||||
# that all original drill sizes will be no farther than DrillClusterTolerance
|
||||
# from the drill tool size generated by clustering.
|
||||
#
|
||||
# Setting DrillClusterTolerance to 0 disables clustering.
|
||||
DrillClusterTolerance = 0.002
|
||||
|
||||
# Use this option to automatically thicken features on particular layers. This
|
||||
# is intended for thickening silkscreen to some minimum width. The value of
|
||||
# this option must be a comma-separated list of layer names followed by minimum
|
||||
# feature sizes (in inches) for that layer. Comment this out to disable thickening.
|
||||
MinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008
|
||||
|
||||
##############################################################################
|
||||
# This section sets the name of merged output files. Each assignment below
|
||||
# specifies a layer name and the file name that is to be written for that
|
||||
# merged layer. Except for the BoardOutline and Drills layer names, all other
|
||||
# layer names must begin with an asterisk '*'. The special layer name Placement
|
||||
# is used to specify the placement file that can be used with the
|
||||
# '--place-file' command-line option in a future invocation of GerbMerge. The
|
||||
# special layer name ToolList is used to specify the file name that represents
|
||||
# the tool list for the panelized job.
|
||||
#
|
||||
# By default, if this section is omitted or no layername=filename assignment is
|
||||
# made, the following files are generated:
|
||||
#
|
||||
# BoardOutline = merged.boardoutline.ger
|
||||
# Drills = merged.drills.xln
|
||||
# Placement = merged.placement.txt
|
||||
# ToolList = merged.toollist.drl
|
||||
# *layername = merged.layername.ger
|
||||
# (for example: 'merged.toplayer.ger', 'merged.silkscreen.ger')
|
||||
#
|
||||
# Any assignment that does not begin with '*' or is not one of the reserved
|
||||
# names BoardOutline, Drills, ToolList, or Placement is a generic string
|
||||
# assignment that can be used for string substitutions, to save typing.
|
||||
##############################################################################
|
||||
[MergeOutputFiles]
|
||||
Prefix = %(mergeout)s
|
||||
|
||||
*TopLayer=%(prefix)s.GTL
|
||||
*BottomLayer=%(prefix)s.GBL
|
||||
*TopSilkscreen=%(prefix)s.GTO
|
||||
*BottomSilkscreen=%(prefix)s.GBO
|
||||
*TopSoldermask=%(prefix)s.GTS
|
||||
*BottomSoldermask=%(prefix)s.GBS
|
||||
Drills=%(prefix)s.TXT
|
||||
BoardOutline=%(prefix)s.GML
|
||||
ToolList = %(prefix)s.drl
|
||||
Placement = placement.%(prefix)s.nfo
|
||||
|
||||
##############################################################################
|
||||
# The remainder of the file specifies the jobs to be panelized. Each job is
|
||||
# specified in its own section. To each job you can assign a job name, which
|
||||
# will be the name of the section in square brackets (e.g., [Proj1]). This job
|
||||
# name is used in the layout file (if used) to refer to the job.
|
||||
#
|
||||
# Job names are case-sensitive, but do not create job names that are the same
|
||||
# except for the case of the characters, as this may cause problems during
|
||||
# layout. Job names may only contain the following characters:
|
||||
#
|
||||
# a-z A-Z 0-9 _
|
||||
#
|
||||
# In addition, job names must begin with a letter (a-z or A-Z).
|
||||
##############################################################################
|
||||
[Proj1]
|
||||
|
||||
# You can set any options you like to make generating filenames easier, like
|
||||
# Prefix. This is just a helper option, not a reserved name. Note, however,
|
||||
# that you must write %(prefix)s below, in ALL LOWERCASE.
|
||||
#
|
||||
# Note how we are making use of the 'projdir' string defined way up at the top
|
||||
# in the [DEFAULT] section to save some typing. By setting 'projdir=somedir'
|
||||
# the expression '%(projdir)s/proj1' expands to 'somedir/proj1'.
|
||||
Prefix=%(projdir)s/1x1/proj1
|
||||
|
||||
# List all the layers that participate in this job. Required layers are Drills
|
||||
# and BoardOutline and have no '*' at the beginning. Optional layers have
|
||||
# names chosen by you and begin with '*'. You should choose consistent layer
|
||||
# names across all jobs.
|
||||
*TopLayer=%(prefix)s.GTL
|
||||
*BottomLayer=%(prefix)s.GBL
|
||||
*TopSilkscreen=%(prefix)s.GTO
|
||||
*BottomSilkscreen=%(prefix)s.GBO
|
||||
*TopSoldermask=%(prefix)s.GTS
|
||||
*BottomSoldermask=%(prefix)s.GBS
|
||||
Drills=%(prefix)s.TXT
|
||||
BoardOutline=%(prefix)s.GML
|
||||
|
||||
# If this job does not have drill tool sizes embedded in the Excellon file, it
|
||||
# needs to have a separate tool list file that maps tool names (e.g., 'T01') to
|
||||
# tool diameter. This may be the global tool list specified in the [Options]
|
||||
# section with the ToolList parameter. If this job doesn't have embedded tool
|
||||
# sizes, and uses a different tool list than the global one, you can specify it
|
||||
# here.
|
||||
#ToolList=proj1.drl
|
||||
|
||||
# If this job has a different ExcellonDecimals setting than the global setting
|
||||
# in the [Options] section above, it can be overridden here.
|
||||
#ExcellonDecimals = 3
|
||||
|
||||
# You can set a 'Repeat' parameter for this job when using automatic placement
|
||||
# (i.e., no *.def file) to indicate how many times this job should appear in
|
||||
# the final panel. When using manual placement, this option is ignored.
|
||||
#Repeat = 5
|
28
gerber/gerbmerge/tami.def
Normal file
28
gerber/gerbmerge/tami.def
Normal file
|
@ -0,0 +1,28 @@
|
|||
# This example simply takes the small Proj1 board and panelizes
|
||||
Row {
|
||||
Col {
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
}
|
||||
Col {
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
}
|
||||
Col {
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
}
|
||||
Col {
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
Proj1
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user