summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHamilton Kibbe <hamilton.kibbe@gmail.com>2015-12-22 02:45:48 -0500
committerHamilton Kibbe <hamilton.kibbe@gmail.com>2015-12-22 02:47:23 -0500
commit6f876edd09d9b81649691e529f85653f14b8fd1c (patch)
tree808e3ea789816452f52e834d4c5c1744bdee6541
parentaf5541ac93b222c05229ee05c9def8dbae5f6e25 (diff)
downloadgerbonara-6f876edd09d9b81649691e529f85653f14b8fd1c.tar.gz
gerbonara-6f876edd09d9b81649691e529f85653f14b8fd1c.tar.bz2
gerbonara-6f876edd09d9b81649691e529f85653f14b8fd1c.zip
Add PCB interface
this incorporates some of @chintal's layers.py changes PCB.from_directory() simplifies loading of multiple gerbers the PCB() class should be pretty helpful going forward... the context classes could use some cleaning up, although I'd like to wait until the freecad stuff gets merged, that way we can try to refactor the context base to support more use cases
-rw-r--r--Makefile1
-rw-r--r--examples/cairo_example.pngbin104949 -> 104401 bytes
-rw-r--r--examples/cairo_example.py1
-rw-r--r--examples/gerbers/bottom_copper.GBL1811
-rw-r--r--examples/gerbers/bottom_mask.GBS66
-rw-r--r--examples/gerbers/shld.drd354
-rw-r--r--examples/pcb_bottom.pngbin0 -> 39174 bytes
-rw-r--r--examples/pcb_example.py39
-rw-r--r--examples/pcb_top.pngbin0 -> 99269 bytes
-rw-r--r--gerber/__init__.py3
-rw-r--r--gerber/common.py3
-rw-r--r--gerber/exceptions.py1
-rw-r--r--gerber/ipc356.py89
-rw-r--r--gerber/layers.py246
-rw-r--r--gerber/pcb.py107
-rw-r--r--gerber/render/cairo_backend.py62
-rw-r--r--gerber/render/render.py19
-rw-r--r--gerber/render/theme.py34
-rw-r--r--gerber/tests/test_layers.py33
-rw-r--r--gerber/utils.py16
20 files changed, 2413 insertions, 472 deletions
diff --git a/Makefile b/Makefile
index 8d3fd33..87f0124 100644
--- a/Makefile
+++ b/Makefile
@@ -31,4 +31,5 @@ doc-clean:
.PHONY: examples
examples:
PYTHONPATH=. $(PYTHON) examples/cairo_example.py
+ PYTHONPATH=. $(PYTHON) examples/pcb_example.py
diff --git a/examples/cairo_example.png b/examples/cairo_example.png
index fc4536a..f33cc69 100644
--- a/examples/cairo_example.png
+++ b/examples/cairo_example.png
Binary files differ
diff --git a/examples/cairo_example.py b/examples/cairo_example.py
index f2f6723..14a7037 100644
--- a/examples/cairo_example.py
+++ b/examples/cairo_example.py
@@ -36,7 +36,6 @@ mask = read(os.path.join(GERBER_FOLDER, 'soldermask.GTS'))
silk = read(os.path.join(GERBER_FOLDER, 'silkscreen.GTO'))
drill = read(os.path.join(GERBER_FOLDER, 'ncdrill.DRD'))
-
# Create a new drawing context
ctx = GerberCairoContext()
diff --git a/examples/gerbers/bottom_copper.GBL b/examples/gerbers/bottom_copper.GBL
new file mode 100644
index 0000000..0d98da3
--- /dev/null
+++ b/examples/gerbers/bottom_copper.GBL
@@ -0,0 +1,1811 @@
+G75*
+%MOIN*%
+%OFA0B0*%
+%FSLAX24Y24*%
+%IPPOS*%
+%LPD*%
+%AMOC8*
+5,1,8,0,0,1.08239X$1,22.5*
+%
+%ADD10C,0.0000*%
+%ADD11C,0.0110*%
+%ADD12C,0.0004*%
+%ADD13C,0.0554*%
+%ADD14C,0.0600*%
+%ADD15C,0.0160*%
+%ADD16C,0.0396*%
+%ADD17C,0.0240*%
+D10*
+X000300Y003064D02*
+X000300Y018064D01*
+X022800Y018064D01*
+X022800Y003064D01*
+X000300Y003064D01*
+X001720Y005114D02*
+X001722Y005164D01*
+X001728Y005214D01*
+X001738Y005263D01*
+X001752Y005311D01*
+X001769Y005358D01*
+X001790Y005403D01*
+X001815Y005447D01*
+X001843Y005488D01*
+X001875Y005527D01*
+X001909Y005564D01*
+X001946Y005598D01*
+X001986Y005628D01*
+X002028Y005655D01*
+X002072Y005679D01*
+X002118Y005700D01*
+X002165Y005716D01*
+X002213Y005729D01*
+X002263Y005738D01*
+X002312Y005743D01*
+X002363Y005744D01*
+X002413Y005741D01*
+X002462Y005734D01*
+X002511Y005723D01*
+X002559Y005708D01*
+X002605Y005690D01*
+X002650Y005668D01*
+X002693Y005642D01*
+X002734Y005613D01*
+X002773Y005581D01*
+X002809Y005546D01*
+X002841Y005508D01*
+X002871Y005468D01*
+X002898Y005425D01*
+X002921Y005381D01*
+X002940Y005335D01*
+X002956Y005287D01*
+X002968Y005238D01*
+X002976Y005189D01*
+X002980Y005139D01*
+X002980Y005089D01*
+X002976Y005039D01*
+X002968Y004990D01*
+X002956Y004941D01*
+X002940Y004893D01*
+X002921Y004847D01*
+X002898Y004803D01*
+X002871Y004760D01*
+X002841Y004720D01*
+X002809Y004682D01*
+X002773Y004647D01*
+X002734Y004615D01*
+X002693Y004586D01*
+X002650Y004560D01*
+X002605Y004538D01*
+X002559Y004520D01*
+X002511Y004505D01*
+X002462Y004494D01*
+X002413Y004487D01*
+X002363Y004484D01*
+X002312Y004485D01*
+X002263Y004490D01*
+X002213Y004499D01*
+X002165Y004512D01*
+X002118Y004528D01*
+X002072Y004549D01*
+X002028Y004573D01*
+X001986Y004600D01*
+X001946Y004630D01*
+X001909Y004664D01*
+X001875Y004701D01*
+X001843Y004740D01*
+X001815Y004781D01*
+X001790Y004825D01*
+X001769Y004870D01*
+X001752Y004917D01*
+X001738Y004965D01*
+X001728Y005014D01*
+X001722Y005064D01*
+X001720Y005114D01*
+X001670Y016064D02*
+X001672Y016114D01*
+X001678Y016164D01*
+X001688Y016213D01*
+X001702Y016261D01*
+X001719Y016308D01*
+X001740Y016353D01*
+X001765Y016397D01*
+X001793Y016438D01*
+X001825Y016477D01*
+X001859Y016514D01*
+X001896Y016548D01*
+X001936Y016578D01*
+X001978Y016605D01*
+X002022Y016629D01*
+X002068Y016650D01*
+X002115Y016666D01*
+X002163Y016679D01*
+X002213Y016688D01*
+X002262Y016693D01*
+X002313Y016694D01*
+X002363Y016691D01*
+X002412Y016684D01*
+X002461Y016673D01*
+X002509Y016658D01*
+X002555Y016640D01*
+X002600Y016618D01*
+X002643Y016592D01*
+X002684Y016563D01*
+X002723Y016531D01*
+X002759Y016496D01*
+X002791Y016458D01*
+X002821Y016418D01*
+X002848Y016375D01*
+X002871Y016331D01*
+X002890Y016285D01*
+X002906Y016237D01*
+X002918Y016188D01*
+X002926Y016139D01*
+X002930Y016089D01*
+X002930Y016039D01*
+X002926Y015989D01*
+X002918Y015940D01*
+X002906Y015891D01*
+X002890Y015843D01*
+X002871Y015797D01*
+X002848Y015753D01*
+X002821Y015710D01*
+X002791Y015670D01*
+X002759Y015632D01*
+X002723Y015597D01*
+X002684Y015565D01*
+X002643Y015536D01*
+X002600Y015510D01*
+X002555Y015488D01*
+X002509Y015470D01*
+X002461Y015455D01*
+X002412Y015444D01*
+X002363Y015437D01*
+X002313Y015434D01*
+X002262Y015435D01*
+X002213Y015440D01*
+X002163Y015449D01*
+X002115Y015462D01*
+X002068Y015478D01*
+X002022Y015499D01*
+X001978Y015523D01*
+X001936Y015550D01*
+X001896Y015580D01*
+X001859Y015614D01*
+X001825Y015651D01*
+X001793Y015690D01*
+X001765Y015731D01*
+X001740Y015775D01*
+X001719Y015820D01*
+X001702Y015867D01*
+X001688Y015915D01*
+X001678Y015964D01*
+X001672Y016014D01*
+X001670Y016064D01*
+X020060Y012714D02*
+X020062Y012764D01*
+X020068Y012814D01*
+X020078Y012863D01*
+X020091Y012912D01*
+X020109Y012959D01*
+X020130Y013005D01*
+X020154Y013048D01*
+X020182Y013090D01*
+X020213Y013130D01*
+X020247Y013167D01*
+X020284Y013201D01*
+X020324Y013232D01*
+X020366Y013260D01*
+X020409Y013284D01*
+X020455Y013305D01*
+X020502Y013323D01*
+X020551Y013336D01*
+X020600Y013346D01*
+X020650Y013352D01*
+X020700Y013354D01*
+X020750Y013352D01*
+X020800Y013346D01*
+X020849Y013336D01*
+X020898Y013323D01*
+X020945Y013305D01*
+X020991Y013284D01*
+X021034Y013260D01*
+X021076Y013232D01*
+X021116Y013201D01*
+X021153Y013167D01*
+X021187Y013130D01*
+X021218Y013090D01*
+X021246Y013048D01*
+X021270Y013005D01*
+X021291Y012959D01*
+X021309Y012912D01*
+X021322Y012863D01*
+X021332Y012814D01*
+X021338Y012764D01*
+X021340Y012714D01*
+X021338Y012664D01*
+X021332Y012614D01*
+X021322Y012565D01*
+X021309Y012516D01*
+X021291Y012469D01*
+X021270Y012423D01*
+X021246Y012380D01*
+X021218Y012338D01*
+X021187Y012298D01*
+X021153Y012261D01*
+X021116Y012227D01*
+X021076Y012196D01*
+X021034Y012168D01*
+X020991Y012144D01*
+X020945Y012123D01*
+X020898Y012105D01*
+X020849Y012092D01*
+X020800Y012082D01*
+X020750Y012076D01*
+X020700Y012074D01*
+X020650Y012076D01*
+X020600Y012082D01*
+X020551Y012092D01*
+X020502Y012105D01*
+X020455Y012123D01*
+X020409Y012144D01*
+X020366Y012168D01*
+X020324Y012196D01*
+X020284Y012227D01*
+X020247Y012261D01*
+X020213Y012298D01*
+X020182Y012338D01*
+X020154Y012380D01*
+X020130Y012423D01*
+X020109Y012469D01*
+X020091Y012516D01*
+X020078Y012565D01*
+X020068Y012614D01*
+X020062Y012664D01*
+X020060Y012714D01*
+X020170Y016064D02*
+X020172Y016114D01*
+X020178Y016164D01*
+X020188Y016213D01*
+X020202Y016261D01*
+X020219Y016308D01*
+X020240Y016353D01*
+X020265Y016397D01*
+X020293Y016438D01*
+X020325Y016477D01*
+X020359Y016514D01*
+X020396Y016548D01*
+X020436Y016578D01*
+X020478Y016605D01*
+X020522Y016629D01*
+X020568Y016650D01*
+X020615Y016666D01*
+X020663Y016679D01*
+X020713Y016688D01*
+X020762Y016693D01*
+X020813Y016694D01*
+X020863Y016691D01*
+X020912Y016684D01*
+X020961Y016673D01*
+X021009Y016658D01*
+X021055Y016640D01*
+X021100Y016618D01*
+X021143Y016592D01*
+X021184Y016563D01*
+X021223Y016531D01*
+X021259Y016496D01*
+X021291Y016458D01*
+X021321Y016418D01*
+X021348Y016375D01*
+X021371Y016331D01*
+X021390Y016285D01*
+X021406Y016237D01*
+X021418Y016188D01*
+X021426Y016139D01*
+X021430Y016089D01*
+X021430Y016039D01*
+X021426Y015989D01*
+X021418Y015940D01*
+X021406Y015891D01*
+X021390Y015843D01*
+X021371Y015797D01*
+X021348Y015753D01*
+X021321Y015710D01*
+X021291Y015670D01*
+X021259Y015632D01*
+X021223Y015597D01*
+X021184Y015565D01*
+X021143Y015536D01*
+X021100Y015510D01*
+X021055Y015488D01*
+X021009Y015470D01*
+X020961Y015455D01*
+X020912Y015444D01*
+X020863Y015437D01*
+X020813Y015434D01*
+X020762Y015435D01*
+X020713Y015440D01*
+X020663Y015449D01*
+X020615Y015462D01*
+X020568Y015478D01*
+X020522Y015499D01*
+X020478Y015523D01*
+X020436Y015550D01*
+X020396Y015580D01*
+X020359Y015614D01*
+X020325Y015651D01*
+X020293Y015690D01*
+X020265Y015731D01*
+X020240Y015775D01*
+X020219Y015820D01*
+X020202Y015867D01*
+X020188Y015915D01*
+X020178Y015964D01*
+X020172Y016014D01*
+X020170Y016064D01*
+X020060Y008714D02*
+X020062Y008764D01*
+X020068Y008814D01*
+X020078Y008863D01*
+X020091Y008912D01*
+X020109Y008959D01*
+X020130Y009005D01*
+X020154Y009048D01*
+X020182Y009090D01*
+X020213Y009130D01*
+X020247Y009167D01*
+X020284Y009201D01*
+X020324Y009232D01*
+X020366Y009260D01*
+X020409Y009284D01*
+X020455Y009305D01*
+X020502Y009323D01*
+X020551Y009336D01*
+X020600Y009346D01*
+X020650Y009352D01*
+X020700Y009354D01*
+X020750Y009352D01*
+X020800Y009346D01*
+X020849Y009336D01*
+X020898Y009323D01*
+X020945Y009305D01*
+X020991Y009284D01*
+X021034Y009260D01*
+X021076Y009232D01*
+X021116Y009201D01*
+X021153Y009167D01*
+X021187Y009130D01*
+X021218Y009090D01*
+X021246Y009048D01*
+X021270Y009005D01*
+X021291Y008959D01*
+X021309Y008912D01*
+X021322Y008863D01*
+X021332Y008814D01*
+X021338Y008764D01*
+X021340Y008714D01*
+X021338Y008664D01*
+X021332Y008614D01*
+X021322Y008565D01*
+X021309Y008516D01*
+X021291Y008469D01*
+X021270Y008423D01*
+X021246Y008380D01*
+X021218Y008338D01*
+X021187Y008298D01*
+X021153Y008261D01*
+X021116Y008227D01*
+X021076Y008196D01*
+X021034Y008168D01*
+X020991Y008144D01*
+X020945Y008123D01*
+X020898Y008105D01*
+X020849Y008092D01*
+X020800Y008082D01*
+X020750Y008076D01*
+X020700Y008074D01*
+X020650Y008076D01*
+X020600Y008082D01*
+X020551Y008092D01*
+X020502Y008105D01*
+X020455Y008123D01*
+X020409Y008144D01*
+X020366Y008168D01*
+X020324Y008196D01*
+X020284Y008227D01*
+X020247Y008261D01*
+X020213Y008298D01*
+X020182Y008338D01*
+X020154Y008380D01*
+X020130Y008423D01*
+X020109Y008469D01*
+X020091Y008516D01*
+X020078Y008565D01*
+X020068Y008614D01*
+X020062Y008664D01*
+X020060Y008714D01*
+X020170Y005064D02*
+X020172Y005114D01*
+X020178Y005164D01*
+X020188Y005213D01*
+X020202Y005261D01*
+X020219Y005308D01*
+X020240Y005353D01*
+X020265Y005397D01*
+X020293Y005438D01*
+X020325Y005477D01*
+X020359Y005514D01*
+X020396Y005548D01*
+X020436Y005578D01*
+X020478Y005605D01*
+X020522Y005629D01*
+X020568Y005650D01*
+X020615Y005666D01*
+X020663Y005679D01*
+X020713Y005688D01*
+X020762Y005693D01*
+X020813Y005694D01*
+X020863Y005691D01*
+X020912Y005684D01*
+X020961Y005673D01*
+X021009Y005658D01*
+X021055Y005640D01*
+X021100Y005618D01*
+X021143Y005592D01*
+X021184Y005563D01*
+X021223Y005531D01*
+X021259Y005496D01*
+X021291Y005458D01*
+X021321Y005418D01*
+X021348Y005375D01*
+X021371Y005331D01*
+X021390Y005285D01*
+X021406Y005237D01*
+X021418Y005188D01*
+X021426Y005139D01*
+X021430Y005089D01*
+X021430Y005039D01*
+X021426Y004989D01*
+X021418Y004940D01*
+X021406Y004891D01*
+X021390Y004843D01*
+X021371Y004797D01*
+X021348Y004753D01*
+X021321Y004710D01*
+X021291Y004670D01*
+X021259Y004632D01*
+X021223Y004597D01*
+X021184Y004565D01*
+X021143Y004536D01*
+X021100Y004510D01*
+X021055Y004488D01*
+X021009Y004470D01*
+X020961Y004455D01*
+X020912Y004444D01*
+X020863Y004437D01*
+X020813Y004434D01*
+X020762Y004435D01*
+X020713Y004440D01*
+X020663Y004449D01*
+X020615Y004462D01*
+X020568Y004478D01*
+X020522Y004499D01*
+X020478Y004523D01*
+X020436Y004550D01*
+X020396Y004580D01*
+X020359Y004614D01*
+X020325Y004651D01*
+X020293Y004690D01*
+X020265Y004731D01*
+X020240Y004775D01*
+X020219Y004820D01*
+X020202Y004867D01*
+X020188Y004915D01*
+X020178Y004964D01*
+X020172Y005014D01*
+X020170Y005064D01*
+D11*
+X019495Y004010D02*
+X019298Y003813D01*
+X019101Y004010D01*
+X019101Y003419D01*
+X018850Y003419D02*
+X018654Y003419D01*
+X018752Y003419D02*
+X018752Y004010D01*
+X018850Y004010D02*
+X018654Y004010D01*
+X018421Y004010D02*
+X018125Y004010D01*
+X018027Y003911D01*
+X018027Y003518D01*
+X018125Y003419D01*
+X018421Y003419D01*
+X018421Y004010D01*
+X017776Y004010D02*
+X017579Y004010D01*
+X017678Y004010D02*
+X017678Y003419D01*
+X017776Y003419D02*
+X017579Y003419D01*
+X016702Y003715D02*
+X016308Y003715D01*
+X015413Y004010D02*
+X015413Y003419D01*
+X015118Y003419D01*
+X015019Y003518D01*
+X015019Y003911D01*
+X015118Y004010D01*
+X015413Y004010D01*
+X014768Y004010D02*
+X014768Y003419D01*
+X014375Y003419D02*
+X014375Y004010D01*
+X014571Y003813D01*
+X014768Y004010D01*
+X014124Y004010D02*
+X013730Y003419D01*
+X014124Y003419D02*
+X013730Y004010D01*
+X012835Y004010D02*
+X012835Y003419D01*
+X012539Y003419D01*
+X012441Y003518D01*
+X012441Y003616D01*
+X012539Y003715D01*
+X012835Y003715D01*
+X012835Y004010D02*
+X012539Y004010D01*
+X012441Y003911D01*
+X012441Y003813D01*
+X012539Y003715D01*
+X012190Y003813D02*
+X012190Y003419D01*
+X012190Y003616D02*
+X011993Y003813D01*
+X011895Y003813D01*
+X011653Y003813D02*
+X011555Y003813D01*
+X011555Y003419D01*
+X011653Y003419D02*
+X011456Y003419D01*
+X011223Y003518D02*
+X011223Y003715D01*
+X011125Y003813D01*
+X010830Y003813D01*
+X010830Y004010D02*
+X010830Y003419D01*
+X011125Y003419D01*
+X011223Y003518D01*
+X011555Y004010D02*
+X011555Y004108D01*
+X010579Y003715D02*
+X010579Y003518D01*
+X010480Y003419D01*
+X010185Y003419D01*
+X010185Y003321D02*
+X010185Y003813D01*
+X010480Y003813D01*
+X010579Y003715D01*
+X010185Y003321D02*
+X010283Y003222D01*
+X010382Y003222D01*
+X009934Y003518D02*
+X009934Y003715D01*
+X009836Y003813D01*
+X009639Y003813D01*
+X009541Y003715D01*
+X009541Y003616D01*
+X009934Y003616D01*
+X009934Y003518D02*
+X009836Y003419D01*
+X009639Y003419D01*
+X019495Y003419D02*
+X019495Y004010D01*
+D12*
+X022869Y007639D02*
+X022869Y013789D01*
+D13*
+X018200Y011964D03*
+X017200Y011464D03*
+X017200Y010464D03*
+X018200Y009964D03*
+X018200Y010964D03*
+X017200Y009464D03*
+D14*
+X017350Y016514D02*
+X017350Y017114D01*
+X018350Y017114D02*
+X018350Y016514D01*
+X007350Y016664D02*
+X007350Y017264D01*
+X006350Y017264D02*
+X006350Y016664D01*
+X005350Y016664D02*
+X005350Y017264D01*
+X001800Y012564D02*
+X001200Y012564D01*
+X001200Y011564D02*
+X001800Y011564D01*
+X001800Y010564D02*
+X001200Y010564D01*
+X001200Y009564D02*
+X001800Y009564D01*
+X001800Y008564D02*
+X001200Y008564D01*
+D15*
+X001031Y008136D02*
+X000780Y008136D01*
+X000780Y007978D02*
+X019853Y007978D01*
+X019804Y008027D02*
+X020012Y007818D01*
+X020268Y007671D01*
+X020553Y007594D01*
+X020847Y007594D01*
+X021132Y007671D01*
+X021388Y007818D01*
+X021596Y008027D01*
+X021744Y008282D01*
+X021820Y008567D01*
+X021820Y008862D01*
+X021744Y009147D01*
+X021596Y009402D01*
+X021388Y009611D01*
+X021132Y009758D01*
+X020847Y009834D01*
+X020553Y009834D01*
+X020268Y009758D01*
+X020012Y009611D01*
+X019804Y009402D01*
+X019656Y009147D01*
+X019580Y008862D01*
+X019580Y008567D01*
+X019656Y008282D01*
+X019804Y008027D01*
+X019740Y008136D02*
+X001969Y008136D01*
+X001891Y008104D02*
+X002061Y008174D01*
+X002190Y008304D01*
+X002260Y008473D01*
+X002260Y008656D01*
+X002190Y008825D01*
+X002061Y008954D01*
+X001891Y009024D01*
+X001108Y009024D01*
+X000939Y008954D01*
+X000810Y008825D01*
+X000780Y008752D01*
+X000780Y009376D01*
+X000810Y009304D01*
+X000939Y009174D01*
+X001108Y009104D01*
+X001891Y009104D01*
+X002061Y009174D01*
+X002190Y009304D01*
+X002260Y009473D01*
+X002260Y009656D01*
+X002190Y009825D01*
+X002061Y009954D01*
+X001891Y010024D01*
+X001108Y010024D01*
+X000939Y009954D01*
+X000810Y009825D01*
+X000780Y009752D01*
+X000780Y010376D01*
+X000810Y010304D01*
+X000939Y010174D01*
+X001108Y010104D01*
+X001891Y010104D01*
+X002061Y010174D01*
+X002190Y010304D01*
+X002260Y010473D01*
+X002260Y010656D01*
+X002190Y010825D01*
+X002061Y010954D01*
+X001891Y011024D01*
+X001108Y011024D01*
+X000939Y010954D01*
+X000810Y010825D01*
+X000780Y010752D01*
+X000780Y011376D01*
+X000810Y011304D01*
+X000939Y011174D01*
+X001108Y011104D01*
+X001891Y011104D01*
+X002061Y011174D01*
+X002190Y011304D01*
+X002260Y011473D01*
+X002260Y011656D01*
+X002190Y011825D01*
+X002061Y011954D01*
+X001891Y012024D01*
+X001108Y012024D01*
+X000939Y011954D01*
+X000810Y011825D01*
+X000780Y011752D01*
+X000780Y012376D01*
+X000810Y012304D01*
+X000939Y012174D01*
+X001108Y012104D01*
+X001891Y012104D01*
+X002061Y012174D01*
+X002190Y012304D01*
+X002260Y012473D01*
+X002260Y012656D01*
+X002190Y012825D01*
+X002061Y012954D01*
+X001891Y013024D01*
+X001108Y013024D01*
+X000939Y012954D01*
+X000810Y012825D01*
+X000780Y012752D01*
+X000780Y015356D01*
+X000786Y015335D01*
+X001068Y014922D01*
+X001068Y014922D01*
+X001068Y014922D01*
+X001460Y014609D01*
+X001926Y014426D01*
+X002426Y014389D01*
+X002914Y014500D01*
+X003347Y014751D01*
+X003347Y014751D01*
+X003688Y015118D01*
+X003905Y015569D01*
+X003980Y016064D01*
+X003905Y016560D01*
+X003688Y017011D01*
+X003347Y017378D01*
+X002990Y017584D01*
+X005019Y017584D01*
+X004960Y017525D01*
+X004890Y017356D01*
+X004890Y016573D01*
+X004960Y016404D01*
+X005089Y016274D01*
+X005258Y016204D01*
+X005441Y016204D01*
+X005611Y016274D01*
+X005740Y016404D01*
+X005810Y016573D01*
+X005810Y017356D01*
+X005740Y017525D01*
+X005681Y017584D01*
+X006019Y017584D01*
+X005960Y017525D01*
+X005890Y017356D01*
+X005890Y016573D01*
+X005960Y016404D01*
+X006089Y016274D01*
+X006258Y016204D01*
+X006441Y016204D01*
+X006611Y016274D01*
+X006740Y016404D01*
+X006810Y016573D01*
+X006810Y017356D01*
+X006740Y017525D01*
+X006681Y017584D01*
+X006991Y017584D01*
+X006984Y017577D01*
+X006939Y017516D01*
+X006905Y017449D01*
+X006882Y017377D01*
+X006870Y017302D01*
+X006870Y016984D01*
+X007330Y016984D01*
+X007330Y016944D01*
+X007370Y016944D01*
+X007370Y016184D01*
+X007388Y016184D01*
+X007462Y016196D01*
+X007534Y016219D01*
+X007602Y016254D01*
+X007663Y016298D01*
+X007716Y016352D01*
+X007761Y016413D01*
+X007795Y016480D01*
+X007818Y016552D01*
+X007830Y016627D01*
+X007830Y016944D01*
+X007370Y016944D01*
+X007370Y016984D01*
+X007830Y016984D01*
+X007830Y017302D01*
+X007818Y017377D01*
+X007795Y017449D01*
+X007761Y017516D01*
+X007716Y017577D01*
+X007709Y017584D01*
+X018249Y017584D01*
+X018238Y017583D01*
+X018166Y017559D01*
+X018098Y017525D01*
+X018037Y017480D01*
+X017984Y017427D01*
+X017939Y017366D01*
+X017905Y017299D01*
+X017882Y017227D01*
+X017870Y017152D01*
+X017870Y016834D01*
+X018330Y016834D01*
+X018330Y016794D01*
+X018370Y016794D01*
+X018370Y016034D01*
+X018388Y016034D01*
+X018462Y016046D01*
+X018534Y016069D01*
+X018602Y016104D01*
+X018663Y016148D01*
+X018716Y016202D01*
+X018761Y016263D01*
+X018795Y016330D01*
+X018818Y016402D01*
+X018830Y016477D01*
+X018830Y016794D01*
+X018370Y016794D01*
+X018370Y016834D01*
+X018830Y016834D01*
+X018830Y017152D01*
+X018818Y017227D01*
+X018795Y017299D01*
+X018761Y017366D01*
+X018716Y017427D01*
+X018663Y017480D01*
+X018602Y017525D01*
+X018534Y017559D01*
+X018462Y017583D01*
+X018451Y017584D01*
+X020126Y017584D01*
+X019960Y017519D01*
+X019568Y017207D01*
+X019286Y016793D01*
+X019139Y016315D01*
+X019139Y015814D01*
+X019286Y015335D01*
+X019568Y014922D01*
+X019568Y014922D01*
+X019568Y014922D01*
+X019960Y014609D01*
+X020426Y014426D01*
+X020926Y014389D01*
+X021414Y014500D01*
+X021847Y014751D01*
+X021847Y014751D01*
+X022188Y015118D01*
+X022320Y015392D01*
+X022320Y005737D01*
+X022188Y006011D01*
+X021847Y006378D01*
+X021414Y006628D01*
+X021414Y006628D01*
+X020926Y006740D01*
+X020926Y006740D01*
+X020426Y006702D01*
+X019960Y006519D01*
+X019568Y006207D01*
+X019286Y005793D01*
+X019139Y005315D01*
+X019139Y004814D01*
+X019231Y004514D01*
+X009450Y004514D01*
+X009450Y003928D01*
+X009326Y003804D01*
+X009326Y003544D01*
+X002937Y003544D01*
+X002964Y003550D01*
+X003397Y003801D01*
+X003397Y003801D01*
+X003738Y004168D01*
+X003955Y004619D01*
+X004030Y005114D01*
+X003955Y005610D01*
+X003738Y006061D01*
+X003397Y006428D01*
+X002964Y006678D01*
+X002964Y006678D01*
+X002476Y006790D01*
+X002476Y006790D01*
+X001976Y006752D01*
+X001510Y006569D01*
+X001118Y006257D01*
+X000836Y005843D01*
+X000780Y005660D01*
+X000780Y008376D01*
+X000810Y008304D01*
+X000939Y008174D01*
+X001108Y008104D01*
+X001891Y008104D01*
+X002181Y008295D02*
+X019653Y008295D01*
+X019610Y008453D02*
+X013735Y008453D01*
+X013753Y008461D02*
+X013854Y008561D01*
+X013908Y008693D01*
+X013908Y008836D01*
+X013854Y008967D01*
+X013753Y009068D01*
+X013621Y009122D01*
+X013588Y009122D01*
+X011930Y010780D01*
+X011930Y012938D01*
+X011954Y012961D01*
+X012008Y013093D01*
+X012008Y013236D01*
+X011954Y013367D01*
+X019783Y013367D01*
+X019804Y013402D02*
+X019656Y013147D01*
+X019580Y012862D01*
+X019580Y012567D01*
+X019656Y012282D01*
+X019804Y012027D01*
+X020012Y011818D01*
+X020268Y011671D01*
+X020553Y011594D01*
+X020847Y011594D01*
+X021132Y011671D01*
+X021388Y011818D01*
+X021596Y012027D01*
+X021744Y012282D01*
+X021820Y012567D01*
+X021820Y012862D01*
+X021744Y013147D01*
+X021596Y013402D01*
+X021388Y013611D01*
+X021132Y013758D01*
+X020847Y013834D01*
+X020553Y013834D01*
+X020268Y013758D01*
+X020012Y013611D01*
+X019804Y013402D01*
+X019927Y013525D02*
+X000780Y013525D01*
+X000780Y013367D02*
+X011346Y013367D01*
+X011292Y013236D01*
+X011292Y013093D01*
+X011346Y012961D01*
+X011370Y012938D01*
+X011370Y010609D01*
+X011413Y010506D01*
+X013192Y008726D01*
+X013192Y008693D01*
+X013246Y008561D01*
+X013347Y008461D01*
+X013479Y008406D01*
+X013621Y008406D01*
+X013753Y008461D01*
+X013874Y008612D02*
+X019580Y008612D01*
+X019580Y008770D02*
+X013908Y008770D01*
+X013869Y008929D02*
+X019598Y008929D01*
+X019640Y009087D02*
+X017432Y009087D01*
+X017448Y009094D02*
+X017571Y009217D01*
+X017637Y009377D01*
+X017637Y009551D01*
+X017571Y009712D01*
+X017558Y009724D01*
+X017826Y009724D01*
+X017829Y009717D01*
+X017952Y009594D01*
+X018113Y009527D01*
+X018287Y009527D01*
+X018448Y009594D01*
+X018571Y009717D01*
+X018637Y009877D01*
+X018637Y010051D01*
+X018571Y010212D01*
+X018448Y010335D01*
+X018287Y010401D01*
+X018113Y010401D01*
+X017952Y010335D01*
+X017829Y010212D01*
+X017826Y010204D01*
+X017576Y010204D01*
+X017591Y010225D01*
+X017624Y010289D01*
+X017646Y010357D01*
+X017657Y010428D01*
+X017657Y010456D01*
+X017209Y010456D01*
+X017209Y010473D01*
+X017657Y010473D01*
+X017657Y010500D01*
+X017646Y010571D01*
+X017624Y010640D01*
+X017591Y010704D01*
+X017549Y010762D01*
+X017498Y010813D01*
+X017440Y010855D01*
+X017375Y010888D01*
+X017307Y010910D01*
+X017236Y010921D01*
+X017209Y010921D01*
+X017209Y010473D01*
+X017191Y010473D01*
+X017191Y010456D01*
+X016743Y010456D01*
+X016743Y010428D01*
+X016754Y010357D01*
+X016776Y010289D01*
+X016809Y010225D01*
+X016824Y010204D01*
+X016066Y010204D01*
+X016053Y010218D01*
+X015921Y010272D01*
+X015779Y010272D01*
+X015647Y010218D01*
+X015546Y010117D01*
+X015492Y009986D01*
+X015492Y009843D01*
+X015546Y009711D01*
+X015647Y009611D01*
+X015779Y009556D01*
+X015921Y009556D01*
+X016053Y009611D01*
+X016154Y009711D01*
+X016159Y009724D01*
+X016842Y009724D01*
+X016829Y009712D01*
+X016763Y009551D01*
+X016763Y009377D01*
+X016829Y009217D01*
+X016952Y009094D01*
+X017113Y009027D01*
+X017287Y009027D01*
+X017448Y009094D01*
+X017583Y009246D02*
+X019714Y009246D01*
+X019806Y009404D02*
+X017637Y009404D01*
+X017632Y009563D02*
+X018027Y009563D01*
+X017827Y009721D02*
+X017561Y009721D01*
+X017645Y010355D02*
+X018002Y010355D01*
+X018113Y010527D02*
+X018287Y010527D01*
+X018448Y010594D01*
+X018571Y010717D01*
+X018637Y010877D01*
+X018637Y011051D01*
+X018571Y011212D01*
+X018448Y011335D01*
+X018287Y011401D01*
+X018113Y011401D01*
+X017952Y011335D01*
+X017829Y011212D01*
+X017763Y011051D01*
+X017763Y010877D01*
+X017829Y010717D01*
+X017952Y010594D01*
+X018113Y010527D01*
+X017874Y010672D02*
+X017607Y010672D01*
+X017655Y010514D02*
+X022320Y010514D01*
+X022320Y010672D02*
+X018526Y010672D01*
+X018618Y010831D02*
+X022320Y010831D01*
+X022320Y010989D02*
+X018637Y010989D01*
+X018597Y011148D02*
+X022320Y011148D01*
+X022320Y011306D02*
+X018476Y011306D01*
+X018448Y011594D02*
+X018287Y011527D01*
+X018113Y011527D01*
+X017952Y011594D01*
+X017829Y011717D01*
+X017763Y011877D01*
+X017763Y012051D01*
+X017829Y012212D01*
+X017952Y012335D01*
+X018113Y012401D01*
+X018287Y012401D01*
+X018448Y012335D01*
+X018571Y012212D01*
+X018637Y012051D01*
+X018637Y011877D01*
+X018571Y011717D01*
+X018448Y011594D01*
+X018477Y011623D02*
+X020444Y011623D01*
+X020075Y011782D02*
+X018598Y011782D01*
+X018637Y011940D02*
+X019890Y011940D01*
+X019762Y012099D02*
+X018617Y012099D01*
+X018525Y012257D02*
+X019671Y012257D01*
+X019620Y012416D02*
+X011930Y012416D01*
+X011930Y012574D02*
+X019580Y012574D01*
+X019580Y012733D02*
+X011930Y012733D01*
+X011930Y012891D02*
+X019588Y012891D01*
+X019630Y013050D02*
+X011990Y013050D01*
+X012008Y013208D02*
+X019692Y013208D01*
+X020139Y013684D02*
+X000780Y013684D01*
+X000780Y013842D02*
+X022320Y013842D01*
+X022320Y013684D02*
+X021261Y013684D01*
+X021473Y013525D02*
+X022320Y013525D01*
+X022320Y013367D02*
+X021617Y013367D01*
+X021708Y013208D02*
+X022320Y013208D01*
+X022320Y013050D02*
+X021770Y013050D01*
+X021812Y012891D02*
+X022320Y012891D01*
+X022320Y012733D02*
+X021820Y012733D01*
+X021820Y012574D02*
+X022320Y012574D01*
+X022320Y012416D02*
+X021780Y012416D01*
+X021729Y012257D02*
+X022320Y012257D01*
+X022320Y012099D02*
+X021638Y012099D01*
+X021510Y011940D02*
+X022320Y011940D01*
+X022320Y011782D02*
+X021325Y011782D01*
+X020956Y011623D02*
+X022320Y011623D01*
+X022320Y011465D02*
+X017637Y011465D01*
+X017637Y011551D02*
+X017637Y011377D01*
+X017571Y011217D01*
+X017448Y011094D01*
+X017287Y011027D01*
+X017113Y011027D01*
+X016952Y011094D01*
+X016829Y011217D01*
+X016763Y011377D01*
+X016763Y011551D01*
+X016829Y011712D01*
+X016952Y011835D01*
+X017113Y011901D01*
+X017287Y011901D01*
+X017448Y011835D01*
+X017571Y011712D01*
+X017637Y011551D01*
+X017607Y011623D02*
+X017923Y011623D01*
+X017802Y011782D02*
+X017501Y011782D01*
+X017763Y011940D02*
+X011930Y011940D01*
+X011930Y011782D02*
+X016899Y011782D01*
+X016793Y011623D02*
+X011930Y011623D01*
+X011930Y011465D02*
+X016763Y011465D01*
+X016792Y011306D02*
+X011930Y011306D01*
+X011930Y011148D02*
+X016898Y011148D01*
+X017025Y010888D02*
+X016960Y010855D01*
+X016902Y010813D01*
+X016851Y010762D01*
+X016809Y010704D01*
+X016776Y010640D01*
+X016754Y010571D01*
+X016743Y010500D01*
+X016743Y010473D01*
+X017191Y010473D01*
+X017191Y010921D01*
+X017164Y010921D01*
+X017093Y010910D01*
+X017025Y010888D01*
+X016927Y010831D02*
+X011930Y010831D01*
+X011930Y010989D02*
+X017763Y010989D01*
+X017782Y010831D02*
+X017473Y010831D01*
+X017502Y011148D02*
+X017803Y011148D01*
+X017924Y011306D02*
+X017608Y011306D01*
+X017209Y010831D02*
+X017191Y010831D01*
+X017191Y010672D02*
+X017209Y010672D01*
+X017209Y010514D02*
+X017191Y010514D01*
+X016793Y010672D02*
+X012038Y010672D01*
+X012196Y010514D02*
+X016745Y010514D01*
+X016755Y010355D02*
+X012355Y010355D01*
+X012513Y010197D02*
+X015626Y010197D01*
+X015514Y010038D02*
+X012672Y010038D01*
+X012830Y009880D02*
+X015492Y009880D01*
+X015542Y009721D02*
+X012989Y009721D01*
+X013147Y009563D02*
+X015763Y009563D01*
+X015937Y009563D02*
+X016768Y009563D01*
+X016763Y009404D02*
+X013306Y009404D01*
+X013464Y009246D02*
+X016817Y009246D01*
+X016968Y009087D02*
+X013706Y009087D01*
+X013148Y008770D02*
+X002213Y008770D01*
+X002260Y008612D02*
+X013226Y008612D01*
+X013365Y008453D02*
+X002252Y008453D01*
+X002086Y008929D02*
+X012990Y008929D01*
+X012831Y009087D02*
+X000780Y009087D01*
+X000780Y008929D02*
+X000914Y008929D01*
+X000787Y008770D02*
+X000780Y008770D01*
+X000780Y008295D02*
+X000819Y008295D01*
+X000780Y007819D02*
+X020011Y007819D01*
+X020304Y007661D02*
+X000780Y007661D01*
+X000780Y007502D02*
+X022320Y007502D01*
+X022320Y007344D02*
+X000780Y007344D01*
+X000780Y007185D02*
+X022320Y007185D01*
+X022320Y007027D02*
+X000780Y007027D01*
+X000780Y006868D02*
+X022320Y006868D01*
+X022320Y006710D02*
+X021056Y006710D01*
+X021547Y006551D02*
+X022320Y006551D01*
+X022320Y006393D02*
+X021821Y006393D01*
+X021847Y006378D02*
+X021847Y006378D01*
+X021981Y006234D02*
+X022320Y006234D01*
+X022320Y006076D02*
+X022128Y006076D01*
+X022188Y006011D02*
+X022188Y006011D01*
+X022233Y005917D02*
+X022320Y005917D01*
+X022309Y005759D02*
+X022320Y005759D01*
+X020528Y006710D02*
+X002825Y006710D01*
+X003184Y006551D02*
+X020042Y006551D01*
+X019960Y006519D02*
+X019960Y006519D01*
+X019801Y006393D02*
+X003430Y006393D01*
+X003397Y006428D02*
+X003397Y006428D01*
+X003577Y006234D02*
+X019603Y006234D01*
+X019568Y006207D02*
+X019568Y006207D01*
+X019479Y006076D02*
+X003724Y006076D01*
+X003738Y006061D02*
+X003738Y006061D01*
+X003807Y005917D02*
+X019371Y005917D01*
+X019286Y005793D02*
+X019286Y005793D01*
+X019276Y005759D02*
+X003883Y005759D01*
+X003955Y005610D02*
+X003955Y005610D01*
+X003957Y005600D02*
+X019227Y005600D01*
+X019178Y005442D02*
+X003981Y005442D01*
+X004005Y005283D02*
+X019139Y005283D01*
+X019139Y005125D02*
+X004028Y005125D01*
+X004008Y004966D02*
+X019139Y004966D01*
+X019141Y004808D02*
+X003984Y004808D01*
+X003960Y004649D02*
+X019190Y004649D01*
+X020426Y006702D02*
+X020426Y006702D01*
+X021096Y007661D02*
+X022320Y007661D01*
+X022320Y007819D02*
+X021389Y007819D01*
+X021547Y007978D02*
+X022320Y007978D01*
+X022320Y008136D02*
+X021660Y008136D01*
+X021747Y008295D02*
+X022320Y008295D01*
+X022320Y008453D02*
+X021790Y008453D01*
+X021820Y008612D02*
+X022320Y008612D01*
+X022320Y008770D02*
+X021820Y008770D01*
+X021802Y008929D02*
+X022320Y008929D01*
+X022320Y009087D02*
+X021760Y009087D01*
+X021686Y009246D02*
+X022320Y009246D01*
+X022320Y009404D02*
+X021594Y009404D01*
+X021435Y009563D02*
+X022320Y009563D01*
+X022320Y009721D02*
+X021196Y009721D01*
+X020204Y009721D02*
+X018573Y009721D01*
+X018637Y009880D02*
+X022320Y009880D01*
+X022320Y010038D02*
+X018637Y010038D01*
+X018577Y010197D02*
+X022320Y010197D01*
+X022320Y010355D02*
+X018398Y010355D01*
+X018200Y009964D02*
+X015900Y009964D01*
+X015850Y009914D01*
+X016158Y009721D02*
+X016839Y009721D01*
+X018373Y009563D02*
+X019965Y009563D01*
+X017783Y012099D02*
+X011930Y012099D01*
+X011930Y012257D02*
+X017875Y012257D01*
+X020426Y014426D02*
+X020426Y014426D01*
+X020299Y014476D02*
+X002808Y014476D01*
+X002914Y014500D02*
+X002914Y014500D01*
+X003147Y014635D02*
+X019928Y014635D01*
+X019960Y014609D02*
+X019960Y014609D01*
+X019729Y014793D02*
+X003387Y014793D01*
+X003534Y014952D02*
+X019548Y014952D01*
+X019440Y015110D02*
+X003681Y015110D01*
+X003688Y015118D02*
+X003688Y015118D01*
+X003761Y015269D02*
+X019332Y015269D01*
+X019286Y015335D02*
+X019286Y015335D01*
+X019258Y015427D02*
+X003837Y015427D01*
+X003905Y015569D02*
+X003905Y015569D01*
+X003908Y015586D02*
+X019209Y015586D01*
+X019160Y015744D02*
+X003932Y015744D01*
+X003956Y015903D02*
+X019139Y015903D01*
+X019139Y016061D02*
+X018509Y016061D01*
+X018370Y016061D02*
+X018330Y016061D01*
+X018330Y016034D02*
+X018330Y016794D01*
+X017870Y016794D01*
+X017870Y016477D01*
+X017882Y016402D01*
+X017905Y016330D01*
+X017939Y016263D01*
+X017984Y016202D01*
+X018037Y016148D01*
+X018098Y016104D01*
+X018166Y016069D01*
+X018238Y016046D01*
+X018312Y016034D01*
+X018330Y016034D01*
+X018191Y016061D02*
+X017458Y016061D01*
+X017441Y016054D02*
+X017611Y016124D01*
+X017740Y016254D01*
+X017810Y016423D01*
+X017810Y017206D01*
+X017740Y017375D01*
+X017611Y017504D01*
+X017441Y017574D01*
+X017258Y017574D01*
+X017089Y017504D01*
+X016960Y017375D01*
+X016890Y017206D01*
+X016890Y016423D01*
+X016960Y016254D01*
+X017089Y016124D01*
+X017258Y016054D01*
+X017441Y016054D01*
+X017242Y016061D02*
+X003980Y016061D01*
+X003980Y016064D02*
+X003980Y016064D01*
+X003957Y016220D02*
+X005221Y016220D01*
+X005479Y016220D02*
+X006221Y016220D01*
+X006479Y016220D02*
+X007165Y016220D01*
+X007166Y016219D02*
+X007238Y016196D01*
+X007312Y016184D01*
+X007330Y016184D01*
+X007330Y016944D01*
+X006870Y016944D01*
+X006870Y016627D01*
+X006882Y016552D01*
+X006905Y016480D01*
+X006939Y016413D01*
+X006984Y016352D01*
+X007037Y016298D01*
+X007098Y016254D01*
+X007166Y016219D01*
+X007330Y016220D02*
+X007370Y016220D01*
+X007370Y016378D02*
+X007330Y016378D01*
+X007330Y016537D02*
+X007370Y016537D01*
+X007370Y016695D02*
+X007330Y016695D01*
+X007330Y016854D02*
+X007370Y016854D01*
+X007830Y016854D02*
+X016890Y016854D01*
+X016890Y017012D02*
+X007830Y017012D01*
+X007830Y017171D02*
+X016890Y017171D01*
+X016941Y017329D02*
+X007826Y017329D01*
+X007775Y017488D02*
+X017073Y017488D01*
+X017627Y017488D02*
+X018047Y017488D01*
+X017921Y017329D02*
+X017759Y017329D01*
+X017810Y017171D02*
+X017873Y017171D01*
+X017870Y017012D02*
+X017810Y017012D01*
+X017810Y016854D02*
+X017870Y016854D01*
+X017870Y016695D02*
+X017810Y016695D01*
+X017810Y016537D02*
+X017870Y016537D01*
+X017889Y016378D02*
+X017792Y016378D01*
+X017706Y016220D02*
+X017971Y016220D01*
+X018330Y016220D02*
+X018370Y016220D01*
+X018370Y016378D02*
+X018330Y016378D01*
+X018330Y016537D02*
+X018370Y016537D01*
+X018370Y016695D02*
+X018330Y016695D01*
+X018830Y016695D02*
+X019256Y016695D01*
+X019286Y016793D02*
+X019286Y016793D01*
+X019328Y016854D02*
+X018830Y016854D01*
+X018830Y017012D02*
+X019436Y017012D01*
+X019544Y017171D02*
+X018827Y017171D01*
+X018779Y017329D02*
+X019722Y017329D01*
+X019568Y017207D02*
+X019568Y017207D01*
+X019921Y017488D02*
+X018653Y017488D01*
+X018830Y016537D02*
+X019207Y016537D01*
+X019158Y016378D02*
+X018811Y016378D01*
+X018729Y016220D02*
+X019139Y016220D01*
+X019960Y017519D02*
+X019960Y017519D01*
+X022261Y015269D02*
+X022320Y015269D01*
+X022320Y015110D02*
+X022181Y015110D01*
+X022188Y015118D02*
+X022188Y015118D01*
+X022320Y014952D02*
+X022034Y014952D01*
+X021887Y014793D02*
+X022320Y014793D01*
+X022320Y014635D02*
+X021647Y014635D01*
+X021414Y014500D02*
+X021414Y014500D01*
+X021308Y014476D02*
+X022320Y014476D01*
+X022320Y014318D02*
+X000780Y014318D01*
+X000780Y014476D02*
+X001799Y014476D01*
+X001926Y014426D02*
+X001926Y014426D01*
+X001460Y014609D02*
+X001460Y014609D01*
+X001428Y014635D02*
+X000780Y014635D01*
+X000780Y014793D02*
+X001229Y014793D01*
+X001048Y014952D02*
+X000780Y014952D01*
+X000780Y015110D02*
+X000940Y015110D01*
+X000832Y015269D02*
+X000780Y015269D01*
+X000786Y015335D02*
+X000786Y015335D01*
+X000780Y014159D02*
+X022320Y014159D01*
+X022320Y014001D02*
+X000780Y014001D01*
+X000780Y013208D02*
+X011292Y013208D01*
+X011310Y013050D02*
+X000780Y013050D01*
+X000780Y012891D02*
+X000876Y012891D01*
+X000856Y012257D02*
+X000780Y012257D01*
+X000780Y012099D02*
+X011370Y012099D01*
+X011370Y012257D02*
+X002144Y012257D01*
+X002236Y012416D02*
+X011370Y012416D01*
+X011370Y012574D02*
+X002260Y012574D01*
+X002228Y012733D02*
+X011370Y012733D01*
+X011370Y012891D02*
+X002124Y012891D01*
+X002075Y011940D02*
+X011370Y011940D01*
+X011370Y011782D02*
+X002208Y011782D01*
+X002260Y011623D02*
+X011370Y011623D01*
+X011370Y011465D02*
+X002257Y011465D01*
+X002191Y011306D02*
+X011370Y011306D01*
+X011370Y011148D02*
+X001997Y011148D01*
+X001976Y010989D02*
+X011370Y010989D01*
+X011370Y010831D02*
+X002184Y010831D01*
+X002253Y010672D02*
+X011370Y010672D01*
+X011409Y010514D02*
+X002260Y010514D01*
+X002211Y010355D02*
+X011563Y010355D01*
+X011722Y010197D02*
+X002083Y010197D01*
+X002135Y009880D02*
+X012039Y009880D01*
+X012197Y009721D02*
+X002233Y009721D01*
+X002260Y009563D02*
+X012356Y009563D01*
+X012514Y009404D02*
+X002232Y009404D01*
+X002132Y009246D02*
+X012673Y009246D01*
+X011880Y010038D02*
+X000780Y010038D01*
+X000780Y009880D02*
+X000865Y009880D01*
+X000917Y010197D02*
+X000780Y010197D01*
+X000780Y010355D02*
+X000789Y010355D01*
+X000780Y010831D02*
+X000816Y010831D01*
+X000780Y010989D02*
+X001024Y010989D01*
+X001003Y011148D02*
+X000780Y011148D01*
+X000780Y011306D02*
+X000809Y011306D01*
+X000780Y011782D02*
+X000792Y011782D01*
+X000780Y011940D02*
+X000925Y011940D01*
+X002426Y014389D02*
+X002426Y014389D01*
+X003933Y016378D02*
+X004985Y016378D01*
+X004905Y016537D02*
+X003909Y016537D01*
+X003840Y016695D02*
+X004890Y016695D01*
+X004890Y016854D02*
+X003764Y016854D01*
+X003688Y017011D02*
+X003688Y017011D01*
+X003687Y017012D02*
+X004890Y017012D01*
+X004890Y017171D02*
+X003539Y017171D01*
+X003392Y017329D02*
+X004890Y017329D01*
+X004945Y017488D02*
+X003157Y017488D01*
+X003347Y017378D02*
+X003347Y017378D01*
+X005715Y016378D02*
+X005985Y016378D01*
+X005905Y016537D02*
+X005795Y016537D01*
+X005810Y016695D02*
+X005890Y016695D01*
+X005890Y016854D02*
+X005810Y016854D01*
+X005810Y017012D02*
+X005890Y017012D01*
+X005890Y017171D02*
+X005810Y017171D01*
+X005810Y017329D02*
+X005890Y017329D01*
+X005945Y017488D02*
+X005755Y017488D01*
+X006755Y017488D02*
+X006925Y017488D01*
+X006874Y017329D02*
+X006810Y017329D01*
+X006810Y017171D02*
+X006870Y017171D01*
+X006870Y017012D02*
+X006810Y017012D01*
+X006810Y016854D02*
+X006870Y016854D01*
+X006870Y016695D02*
+X006810Y016695D01*
+X006795Y016537D02*
+X006887Y016537D01*
+X006964Y016378D02*
+X006715Y016378D01*
+X007535Y016220D02*
+X016994Y016220D01*
+X016908Y016378D02*
+X007736Y016378D01*
+X007813Y016537D02*
+X016890Y016537D01*
+X016890Y016695D02*
+X007830Y016695D01*
+X011346Y013367D02*
+X011447Y013468D01*
+X011579Y013522D01*
+X011721Y013522D01*
+X011853Y013468D01*
+X011954Y013367D01*
+X020926Y014389D02*
+X020926Y014389D01*
+X009450Y004491D02*
+X003894Y004491D01*
+X003955Y004619D02*
+X003955Y004619D01*
+X003817Y004332D02*
+X009450Y004332D01*
+X009450Y004174D02*
+X003741Y004174D01*
+X003738Y004168D02*
+X003738Y004168D01*
+X003596Y004015D02*
+X009450Y004015D01*
+X009379Y003857D02*
+X003449Y003857D01*
+X003220Y003698D02*
+X009326Y003698D01*
+X002964Y003550D02*
+X002964Y003550D01*
+X000810Y005759D02*
+X000780Y005759D01*
+X000836Y005843D02*
+X000836Y005843D01*
+X000887Y005917D02*
+X000780Y005917D01*
+X000780Y006076D02*
+X000995Y006076D01*
+X001103Y006234D02*
+X000780Y006234D01*
+X000780Y006393D02*
+X001289Y006393D01*
+X001118Y006257D02*
+X001118Y006257D01*
+X000780Y006551D02*
+X001488Y006551D01*
+X001510Y006569D02*
+X001510Y006569D01*
+X001868Y006710D02*
+X000780Y006710D01*
+X001976Y006752D02*
+X001976Y006752D01*
+X000868Y009246D02*
+X000780Y009246D01*
+D16*
+X004150Y011564D03*
+X006500Y013714D03*
+X010000Y015114D03*
+X011650Y013164D03*
+X013300Y011464D03*
+X013350Y010114D03*
+X013550Y008764D03*
+X013500Y006864D03*
+X012100Y005314D03*
+X009250Y004064D03*
+X015200Y004514D03*
+X015650Y006264D03*
+X015850Y009914D03*
+X014250Y014964D03*
+D17*
+X011650Y013164D02*
+X011650Y010664D01*
+X013550Y008764D01*
+M02*
diff --git a/examples/gerbers/bottom_mask.GBS b/examples/gerbers/bottom_mask.GBS
new file mode 100644
index 0000000..b06654f
--- /dev/null
+++ b/examples/gerbers/bottom_mask.GBS
@@ -0,0 +1,66 @@
+G75*
+%MOIN*%
+%OFA0B0*%
+%FSLAX24Y24*%
+%IPPOS*%
+%LPD*%
+%AMOC8*
+5,1,8,0,0,1.08239X$1,22.5*
+%
+%ADD10C,0.0634*%
+%ADD11C,0.1360*%
+%ADD12C,0.0680*%
+%ADD13C,0.1340*%
+%ADD14C,0.0476*%
+D10*
+X017200Y009464D03*
+X018200Y009964D03*
+X018200Y010964D03*
+X017200Y010464D03*
+X017200Y011464D03*
+X018200Y011964D03*
+D11*
+X020700Y012714D03*
+X020700Y008714D03*
+D12*
+X018350Y016514D02*
+X018350Y017114D01*
+X017350Y017114D02*
+X017350Y016514D01*
+X007350Y016664D02*
+X007350Y017264D01*
+X006350Y017264D02*
+X006350Y016664D01*
+X005350Y016664D02*
+X005350Y017264D01*
+X001800Y012564D02*
+X001200Y012564D01*
+X001200Y011564D02*
+X001800Y011564D01*
+X001800Y010564D02*
+X001200Y010564D01*
+X001200Y009564D02*
+X001800Y009564D01*
+X001800Y008564D02*
+X001200Y008564D01*
+D13*
+X002350Y005114D03*
+X002300Y016064D03*
+X020800Y016064D03*
+X020800Y005064D03*
+D14*
+X015650Y006264D03*
+X013500Y006864D03*
+X012100Y005314D03*
+X009250Y004064D03*
+X015200Y004514D03*
+X013550Y008764D03*
+X013350Y010114D03*
+X013300Y011464D03*
+X011650Y013164D03*
+X010000Y015114D03*
+X006500Y013714D03*
+X004150Y011564D03*
+X014250Y014964D03*
+X015850Y009914D03*
+M02*
diff --git a/examples/gerbers/shld.drd b/examples/gerbers/shld.drd
deleted file mode 100644
index a919b5b..0000000
--- a/examples/gerbers/shld.drd
+++ /dev/null
@@ -1,354 +0,0 @@
-%
-M48
-M72
-T01C0.03200
-T02C0.03543
-T03C0.04000
-%
-T01
-X11212Y16343
-X80212Y16343
-X21212Y16343
-X99212Y22143
-X99212Y12143
-X40212Y16343
-T02
-X10812Y191043
-X70812Y111043
-X130812Y111043
-X80812Y141043
-X110812Y71043
-X160812Y51043
-X20812Y171043
-X30812Y91043
-X50812Y111043
-X50812Y121043
-X20812Y161043
-X90812Y111043
-X70812Y61043
-X40812Y171043
-X50812Y81043
-X160812Y61043
-X40812Y191043
-X30812Y31043
-X90812Y131043
-X10812Y31043
-X150812Y111043
-X170812Y51043
-X110812Y151043
-X10812Y51043
-X150812Y51043
-X140812Y121043
-X170812Y61043
-X30812Y61043
-X70812Y91043
-X70812Y101043
-X160812Y161043
-X40812Y81043
-X220812Y151043
-X180812Y71043
-X30812Y151043
-X50812Y161043
-X150812Y131043
-X40812Y61043
-X130812Y91043
-X90812Y61043
-X80812Y101043
-X30812Y191043
-X130812Y151043
-X60812Y31043
-X50812Y91043
-X40812Y111043
-X220812Y141043
-X30812Y81043
-X140812Y81043
-X60812Y61043
-X210812Y131043
-X160812Y71043
-X90812Y41043
-X120812Y151043
-X10812Y161043
-X80812Y151043
-X50812Y71043
-X160812Y151043
-X110812Y111043
-X30812Y121043
-X10812Y41043
-X20812Y41043
-X40812Y51043
-X10812Y151043
-X200812Y101043
-X70812Y41043
-X120812Y51043
-X40812Y41043
-X80812Y91043
-X170812Y161043
-X100812Y71043
-X40812Y31043
-X30812Y141043
-X180812Y131043
-X10812Y61043
-X120812Y141043
-X200812Y151043
-X90812Y121043
-X50812Y31043
-X170812Y121043
-X170812Y111043
-X60812Y121043
-X40812Y101043
-X120812Y121043
-X100812Y161043
-X10812Y81043
-X130812Y131043
-X60812Y81043
-X200812Y111043
-X140812Y51043
-X150812Y71043
-X160812Y111043
-X120812Y111043
-X130812Y101043
-X20812Y51043
-X20812Y201043
-X90812Y71043
-X190812Y61043
-X170812Y81043
-X70812Y71043
-X50812Y101043
-X150812Y81043
-X60812Y131043
-X190812Y121043
-X170812Y131043
-X130812Y121043
-X20812Y91043
-X70812Y151043
-X70812Y141043
-X180812Y111043
-X10812Y181043
-X40812Y131043
-X80812Y121043
-X120812Y61043
-X160812Y101043
-X90812Y31043
-X10812Y91043
-X80812Y71043
-X100812Y121043
-X100812Y51043
-X160812Y121043
-X40812Y71043
-X50812Y51043
-X180812Y81043
-X90812Y51043
-X60812Y71043
-X40812Y161043
-X190812Y141043
-X20812Y31043
-X100812Y151043
-X200812Y141043
-X180812Y151043
-X60812Y51043
-X120812Y131043
-X150812Y141043
-X180812Y51043
-X150812Y101043
-X170812Y101043
-X150812Y151043
-X30812Y111043
-X90812Y151043
-X80812Y131043
-X170812Y151043
-X80812Y51043
-X10812Y201043
-X60812Y151043
-X140812Y111043
-X100812Y91043
-X90812Y161043
-X130812Y81043
-X190812Y111043
-X140812Y101043
-X20812Y71043
-X150812Y121043
-X90812Y141043
-X60812Y111043
-X110812Y121043
-X30812Y71043
-X30812Y51043
-X210812Y141043
-X50812Y61043
-X140812Y131043
-X30812Y201043
-X190812Y101043
-X70812Y81043
-X20812Y121043
-X20812Y191043
-X80812Y161043
-X80812Y81043
-X20812Y151043
-X40812Y121043
-X80812Y31043
-X80812Y111043
-X190812Y151043
-X30812Y181043
-X60812Y91043
-X110812Y61043
-X180812Y61043
-X10812Y141043
-X50812Y131043
-X130812Y51043
-X50812Y151043
-X110812Y51043
-X70812Y131043
-X60812Y41043
-X200812Y161043
-X80812Y61043
-X140812Y161043
-X190812Y81043
-X20812Y141043
-X70812Y161043
-X140812Y151043
-X20812Y61043
-X20812Y81043
-X100812Y131043
-X200812Y131043
-X140812Y141043
-X40812Y151043
-X40812Y91043
-X60812Y101043
-X160812Y81043
-X130812Y71043
-X30812Y41043
-X10812Y71043
-X180812Y141043
-X170812Y141043
-X180812Y91043
-X180812Y101043
-X150812Y61043
-X120812Y161043
-X90812Y101043
-X200812Y121043
-X190812Y91043
-X160812Y141043
-X130812Y161043
-X20812Y101043
-X90812Y81043
-X190812Y161043
-X30812Y171043
-X40812Y181043
-X70812Y51043
-X110812Y101043
-X60812Y141043
-X120812Y101043
-X30812Y161043
-X100812Y141043
-X220812Y131043
-X50812Y141043
-X30812Y101043
-X60812Y161043
-X150812Y161043
-X20812Y131043
-X150812Y91043
-X100812Y61043
-X10812Y131043
-X30812Y131043
-X100812Y41043
-X140812Y61043
-X210812Y151043
-X70812Y121043
-X100812Y101043
-X180812Y121043
-X40812Y201043
-X190812Y71043
-X10812Y171043
-X110812Y141043
-X130812Y61043
-X110812Y81043
-X80812Y41043
-X50812Y41043
-X110812Y131043
-X190812Y131043
-X130812Y141043
-X140812Y91043
-X20812Y111043
-X140812Y71043
-X170812Y91043
-X120812Y91043
-X190812Y51043
-X120812Y81043
-X160812Y91043
-X100812Y81043
-X120812Y71043
-X10812Y121043
-X170812Y71043
-X110812Y91043
-X100812Y111043
-X110812Y161043
-X70812Y31043
-X90812Y91043
-X40812Y141043
-X20812Y181043
-X210812Y161043
-X180812Y161043
-X160812Y131043
-T03
-X86712Y189043
-X213012Y23043
-X126732Y201114
-X96712Y189043
-X86732Y201114
-X56732Y201114
-X142812Y23443
-X106712Y189043
-X112754Y11450
-X182720Y200950
-X106732Y201114
-X207259Y55639
-X207259Y81239
-X203131Y11150
-X76732Y201114
-X192720Y200950
-X66712Y189043
-X96732Y201114
-X193131Y11150
-X66732Y201114
-X203012Y23043
-X122754Y11450
-X76712Y189043
-X173131Y11150
-X192712Y188843
-X116712Y189043
-X116732Y201114
-X213131Y11150
-X162720Y200950
-X225059Y55639
-X183131Y11150
-X126712Y189043
-X183012Y23043
-X212712Y188843
-X163131Y11150
-X213563Y110846
-X122812Y23443
-X132812Y23443
-X182712Y188843
-X212720Y200950
-X202720Y200950
-X193012Y23043
-X213563Y120846
-X172720Y200950
-X225059Y81239
-X223563Y120846
-X56712Y189043
-X172712Y188843
-X213563Y100846
-X142720Y200950
-X163012Y23043
-X142754Y11450
-X223563Y110846
-X132754Y11450
-X142712Y188843
-X162712Y188843
-X152712Y188843
-X223563Y100846
-X202712Y188843
-X112812Y23443
-X173012Y23043
-X152720Y200950
-M30
diff --git a/examples/pcb_bottom.png b/examples/pcb_bottom.png
new file mode 100644
index 0000000..1e8c369
--- /dev/null
+++ b/examples/pcb_bottom.png
Binary files differ
diff --git a/examples/pcb_example.py b/examples/pcb_example.py
new file mode 100644
index 0000000..5341da0
--- /dev/null
+++ b/examples/pcb_example.py
@@ -0,0 +1,39 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be>
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+"""
+This example demonstrates the use of pcb-tools with cairo to render composite
+images using the PCB interface
+"""
+
+import os
+from gerber import PCB
+from gerber.render import GerberCairoContext, theme
+
+GERBER_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gerbers'))
+
+
+# Create a new drawing context
+ctx = GerberCairoContext()
+
+# Create a new PCB
+pcb = PCB.from_directory(GERBER_FOLDER)
+
+pcb.theme = theme.THEMES['OSH Park']
+ctx.render_layers(pcb.top_layers, os.path.join(os.path.dirname(__file__), 'pcb_top.png'))
+ctx.render_layers(pcb.bottom_layers, os.path.join(os.path.dirname(__file__), 'pcb_bottom.png'))
+
diff --git a/examples/pcb_top.png b/examples/pcb_top.png
new file mode 100644
index 0000000..bf1d687
--- /dev/null
+++ b/examples/pcb_top.png
Binary files differ
diff --git a/gerber/__init__.py b/gerber/__init__.py
index b5a9014..5cfdad7 100644
--- a/gerber/__init__.py
+++ b/gerber/__init__.py
@@ -23,4 +23,5 @@ gerber-tools provides utilities for working with Gerber (RS-274X) and Excellon
files in python.
"""
-from .common import read, loads \ No newline at end of file
+from .common import read, loads
+from .pcb import PCB
diff --git a/gerber/common.py b/gerber/common.py
index f8979dc..04b6423 100644
--- a/gerber/common.py
+++ b/gerber/common.py
@@ -17,6 +17,7 @@
from . import rs274x
from . import excellon
+from . import ipc356
from .exceptions import ParseError
from .utils import detect_file_format
@@ -43,6 +44,8 @@ def read(filename):
return rs274x.read(filename)
elif fmt == 'excellon':
return excellon.read(filename)
+ elif fmt == 'ipc_d_356':
+ return ipc356.read(filename)
else:
raise ParseError('Unable to detect file format')
diff --git a/gerber/exceptions.py b/gerber/exceptions.py
index fdd548c..65ae905 100644
--- a/gerber/exceptions.py
+++ b/gerber/exceptions.py
@@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
class ParseError(Exception):
pass
diff --git a/gerber/ipc356.py b/gerber/ipc356.py
index b8a7ba3..7dadd22 100644
--- a/gerber/ipc356.py
+++ b/gerber/ipc356.py
@@ -27,8 +27,11 @@ _NNAME = re.compile(r'^NNAME\d+$')
# Board Edge Coordinates
_COORD = re.compile(r'X?(?P<x>[\d\s]*)?Y?(?P<y>[\d\s]*)?')
-_SM_FIELD = {'0': 'none', '1': 'primary side', '2': 'secondary side', '3': 'both'}
-
+_SM_FIELD = {
+ '0': 'none',
+ '1': 'primary side',
+ '2': 'secondary side',
+ '3': 'both'}
def read(filename):
@@ -51,17 +54,17 @@ def read(filename):
class IPC_D_356(CamFile):
@classmethod
- def from_file(self, filename):
- p = IPC_D_356_Parser()
- return p.parse(filename)
-
+ def from_file(cls, filename):
+ parser = IPC_D_356_Parser()
+ return parser.parse(filename)
- def __init__(self, statements, settings, primitives=None):
+ def __init__(self, statements, settings, primitives=None, filename=None):
self.statements = statements
self.units = settings.units
self.angle_units = settings.angle_units
self.primitives = [TestRecord((rec.x_coord, rec.y_coord), rec.net_name,
rec.access) for rec in self.test_records]
+ self.filename = filename
@property
def settings(self):
@@ -95,8 +98,6 @@ class IPC_D_356(CamFile):
adjacent_nets.add(record.net)
nets.append(IPC356_Net(net, adjacent_nets))
return nets
-
-
@property
def components(self):
@@ -109,14 +110,12 @@ class IPC_D_356(CamFile):
@property
def outlines(self):
- return [stmt for stmt in self.statements
+ return [stmt for stmt in self.statements
if isinstance(stmt, IPC356_Outline)]
-
-
@property
def adjacency_records(self):
- return [record for record in self.statements
+ return [record for record in self.statements
if isinstance(record, IPC356_Adjacency)]
def render(self, ctx, layer='both', filename=None):
@@ -133,6 +132,7 @@ class IPC_D_356(CamFile):
class IPC_D_356_Parser(object):
# TODO: Allow multi-line statements (e.g. Altium board edge)
+
def __init__(self):
self.units = 'inch'
self.angle_units = 'degrees'
@@ -158,8 +158,7 @@ class IPC_D_356_Parser(object):
oldline = line
self._parse_line(oldline)
- return IPC_D_356(self.statements, self.settings)
-
+ return IPC_D_356(self.statements, self.settings, filename=filename)
def _parse_line(self, line):
if not len(line):
@@ -201,18 +200,23 @@ class IPC_D_356_Parser(object):
elif line[0:3] == '378':
# Conductor
- self.statements.append(IPC356_Conductor.from_line(line, self.settings))
-
+ self.statements.append(
+ IPC356_Conductor.from_line(
+ line, self.settings))
+
elif line[0:3] == '379':
# Net Adjacency
self.statements.append(IPC356_Adjacency.from_line(line))
-
+
elif line[0:3] == '389':
# Outline
- self.statements.append(IPC356_Outline.from_line(line, self.settings))
+ self.statements.append(
+ IPC356_Outline.from_line(
+ line, self.settings))
class IPC356_Comment(object):
+
@classmethod
def from_line(cls, line):
if line[0] != 'C':
@@ -228,6 +232,7 @@ class IPC356_Comment(object):
class IPC356_Parameter(object):
+
@classmethod
def from_line(cls, line):
if line[0] != 'P':
@@ -246,13 +251,14 @@ class IPC356_Parameter(object):
class IPC356_TestRecord(object):
+
@classmethod
def from_line(cls, line, settings):
offset = 0
units = settings.units
angle = settings.angle_units
- feature_types = {'1':'through-hole', '2': 'smt',
- '3':'tooling-feature', '4':'tooling-hole'}
+ feature_types = {'1': 'through-hole', '2': 'smt',
+ '3': 'tooling-feature', '4': 'tooling-hole'}
access = ['both', 'top', 'layer2', 'layer3', 'layer4', 'layer5',
'layer6', 'layer7', 'bottom']
record = {}
@@ -290,21 +296,21 @@ class IPC356_TestRecord(object):
if len(line) >= (43 + offset):
end = len(line) - 1 if len(line) < (50 + offset) else (49 + offset)
coord = int(line[42 + offset:end].strip())
- record['x_coord'] = (coord * 0.0001 if units == 'inch'
+ record['x_coord'] = (coord * 0.0001 if units == 'inch'
else coord * 0.001)
if len(line) >= (51 + offset):
end = len(line) - 1 if len(line) < (58 + offset) else (57 + offset)
coord = int(line[50 + offset:end].strip())
- record['y_coord'] = (coord * 0.0001 if units == 'inch'
- else coord * 0.001)
+ record['y_coord'] = (coord * 0.0001 if units == 'inch'
+ else coord * 0.001)
if len(line) >= (59 + offset):
end = len(line) - 1 if len(line) < (63 + offset) else (62 + offset)
dim = line[58 + offset:end].strip()
if dim != '':
record['rect_x'] = (int(dim) * 0.0001 if units == 'inch'
- else int(dim) * 0.001)
+ else int(dim) * 0.001)
if len(line) >= (64 + offset):
end = len(line) - 1 if len(line) < (68 + offset) else (67 + offset)
@@ -321,7 +327,7 @@ class IPC356_TestRecord(object):
else math.degrees(rot))
if len(line) >= (74 + offset):
- end = 74 + offset
+ end = 74 + offset
sm_info = line[73 + offset:end].strip()
record['soldermask_info'] = _SM_FIELD.get(sm_info)
@@ -337,7 +343,8 @@ class IPC356_TestRecord(object):
def __repr__(self):
return '<IPC-D-356 %s Test Record: %s>' % (self.net_name,
- self.feature_type)
+ self.feature_type)
+
class IPC356_Outline(object):
@@ -365,24 +372,27 @@ class IPC356_Outline(object):
class IPC356_Conductor(object):
+
@classmethod
def from_line(cls, line, settings):
if line[0:3] != '378':
raise ValueError('Not a valid IPC-D-356 Conductor statement')
-
+
scale = 0.0001 if settings.units == 'inch' else 0.001
net_name = line[3:17].strip()
layer = int(line[19:21])
-
+
# Parse out aperture definiting
raw_aperture = line[22:].split()[0]
aperture_dict = _COORD.match(raw_aperture).groupdict()
x = 0
y = 0
- x = int(aperture_dict['x']) * scale if aperture_dict['x'] is not '' else None
- y = int(aperture_dict['y']) * scale if aperture_dict['y'] is not '' else None
+ x = int(aperture_dict['x']) * \
+ scale if aperture_dict['x'] is not '' else None
+ y = int(aperture_dict['y']) * \
+ scale if aperture_dict['y'] is not '' else None
aperture = (x, y)
-
+
# Parse out conductor shapes
shapes = []
coord_list = ' '.join(line[22:].split()[1:])
@@ -399,7 +409,7 @@ class IPC356_Conductor(object):
shape.append((x * scale, y * scale))
shapes.append(tuple(shape))
return cls(net_name, layer, aperture, tuple(shapes))
-
+
def __init__(self, net_name, layer, aperture, shapes):
self.net_name = net_name
self.layer = layer
@@ -417,18 +427,19 @@ class IPC356_Adjacency(object):
if line[0:3] != '379':
raise ValueError('Not a valid IPC-D-356 Conductor statement')
nets = line[3:].strip().split()
-
+
return cls(nets[0], nets[1:])
def __init__(self, net, adjacent_nets):
self.net = net
self.adjacent_nets = adjacent_nets
-
+
def __repr__(self):
return '<IPC-D-356 %s Adjacency Record>' % self.net
class IPC356_EndOfFile(object):
+
def __init__(self):
pass
@@ -437,12 +448,14 @@ class IPC356_EndOfFile(object):
def __repr__(self):
return '<IPC-D-356 EOF>'
-
+
+
class IPC356_Net(object):
+
def __init__(self, name, adjacent_nets):
self.name = name
- self.adjacent_nets = set(adjacent_nets) if adjacent_nets is not None else set()
-
+ self.adjacent_nets = set(
+ adjacent_nets) if adjacent_nets is not None else set()
def __repr__(self):
return '<IPC-D-356 Net %s>' % self.name
diff --git a/gerber/layers.py b/gerber/layers.py
index b10cf16..c6a5bf7 100644
--- a/gerber/layers.py
+++ b/gerber/layers.py
@@ -15,40 +15,212 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-top_copper_ext = ['gtl', 'cmp', 'top', ]
-top_copper_name = ['art01', 'top', 'GTL', 'layer1', 'soldcom', 'comp', ]
-
-bottom_copper_ext = ['gbl', 'sld', 'bot', 'sol', ]
-bottom_coppper_name = ['art02', 'bottom', 'bot', 'GBL', 'layer2', 'soldsold', ]
-
-internal_layer_ext = ['in', 'gt1', 'gt2', 'gt3', 'gt4', 'gt5', 'gt6', 'g1',
- 'g2', 'g3', 'g4', 'g5', 'g6', ]
-internal_layer_name = ['art', 'internal']
-
-power_plane_name = ['pgp', 'pwr', ]
-ground_plane_name = ['gp1', 'gp2', 'gp3', 'gp4', 'gt5', 'gp6', 'gnd',
- 'ground', ]
-
-top_silk_ext = ['gto', 'sst', 'plc', 'ts', 'skt', ]
-top_silk_name = ['sst01', 'topsilk', 'silk', 'slk', 'sst', ]
-
-bottom_silk_ext = ['gbo', 'ssb', 'pls', 'bs', 'skb', ]
-bottom_silk_name = ['sst', 'bsilk', 'ssb', 'botsilk', ]
-
-top_mask_ext = ['gts', 'stc', 'tmk', 'smt', 'tr', ]
-top_mask_name = ['sm01', 'cmask', 'tmask', 'mask1', 'maskcom', 'topmask',
- 'mst', ]
-
-bottom_mask_ext = ['gbs', 'sts', 'bmk', 'smb', 'br', ]
-bottom_mask_name = ['sm', 'bmask', 'mask2', 'masksold', 'botmask', 'msb', ]
-
-top_paste_ext = ['gtp', 'tm']
-top_paste_name = ['sp01', 'toppaste', 'pst']
-
-bottom_paste_ext = ['gbp', 'bm']
-bottom_paste_name = ['sp02', 'botpaste', 'psb']
-
-board_outline_ext = ['gko']
-board_outline_name = ['BDR', 'border', 'out', ]
-
-
+import os
+import re
+from collections import namedtuple
+
+from .excellon import ExcellonFile
+from .ipc356 import IPC_D_356
+from .render.render import Renderable
+
+Hint = namedtuple('Hint', 'layer ext name')
+
+hints = [
+ Hint(layer='top',
+ ext=['gtl', 'cmp', 'top', ],
+ name=['art01', 'top', 'GTL', 'layer1', 'soldcom', 'comp', ]
+ ),
+ Hint(layer='bottom',
+ ext=['gbl', 'sld', 'bot', 'sol', 'bottom', ],
+ name=['art02', 'bottom', 'bot', 'GBL', 'layer2', 'soldsold', ]
+ ),
+ Hint(layer='internal',
+ ext=['in', 'gt1', 'gt2', 'gt3', 'gt4', 'gt5', 'gt6', 'g1',
+ 'g2', 'g3', 'g4', 'g5', 'g6', ],
+ name=['art', 'internal', 'pgp', 'pwr', 'gp1', 'gp2', 'gp3', 'gp4',
+ 'gt5', 'gp6', 'gnd', 'ground', ]
+ ),
+ Hint(layer='topsilk',
+ ext=['gto', 'sst', 'plc', 'ts', 'skt', 'topsilk', ],
+ name=['sst01', 'topsilk', 'silk', 'slk', 'sst', ]
+ ),
+ Hint(layer='bottomsilk',
+ ext=['gbo', 'ssb', 'pls', 'bs', 'skb', 'bottomsilk', ],
+ name=['bsilk', 'ssb', 'botsilk', ]
+ ),
+ Hint(layer='topmask',
+ ext=['gts', 'stc', 'tmk', 'smt', 'tr', 'topmask', ],
+ name=['sm01', 'cmask', 'tmask', 'mask1', 'maskcom', 'topmask',
+ 'mst', ]
+ ),
+ Hint(layer='bottommask',
+ ext=['gbs', 'sts', 'bmk', 'smb', 'br', 'bottommask', ],
+ name=['sm', 'bmask', 'mask2', 'masksold', 'botmask', 'msb', ]
+ ),
+ Hint(layer='toppaste',
+ ext=['gtp', 'tm', 'toppaste', ],
+ name=['sp01', 'toppaste', 'pst']
+ ),
+ Hint(layer='bottompaste',
+ ext=['gbp', 'bm', 'bottompaste', ],
+ name=['sp02', 'botpaste', 'psb']
+ ),
+ Hint(layer='outline',
+ ext=['gko', 'outline', ],
+ name=['BDR', 'border', 'out', ]
+ ),
+ Hint(layer='ipc_netlist',
+ ext=['ipc'],
+ name=[],
+ ),
+]
+
+
+def guess_layer_class(filename):
+ try:
+ directory, name = os.path.split(filename)
+ name, ext = os.path.splitext(name.lower())
+ for hint in hints:
+ patterns = [r'^(\w*[.-])*{}([.-]\w*)?$'.format(x) for x in hint.name]
+ if ext[1:] in hint.ext or any(re.findall(p, name, re.IGNORECASE) for p in patterns):
+ return hint.layer
+ except:
+ pass
+ return 'unknown'
+
+
+def sort_layers(layers):
+ layer_order = ['outline', 'toppaste', 'topsilk', 'topmask', 'top',
+ 'internal', 'bottom', 'bottommask', 'bottomsilk',
+ 'bottompaste', 'drill', ]
+ output = []
+ drill_layers = [layer for layer in layers if layer.layer_class == 'drill']
+ internal_layers = list(sorted([layer for layer in layers if layer.layer_class == 'internal']))
+
+ for layer_class in layer_order:
+ if layer_class == 'internal':
+ output += internal_layers
+ elif layer_class == 'drill':
+ output += drill_layers
+ else:
+ for layer in layers:
+ if layer.layer_class == layer_class:
+ output.append(layer)
+ return output
+
+
+class PCBLayer(Renderable):
+ """ Base class for PCB Layers
+
+ Parameters
+ ----------
+ source : CAMFile
+ CAMFile representing the layer
+
+
+ Attributes
+ ----------
+ filename : string
+ Source Filename
+
+ """
+ @classmethod
+ def from_gerber(cls, camfile):
+ filename = camfile.filename
+ layer_class = guess_layer_class(filename)
+ if isinstance(camfile, ExcellonFile) or (layer_class == 'drill'):
+ return DrillLayer.from_gerber(camfile)
+ elif layer_class == 'internal':
+ return InternalLayer.from_gerber(camfile)
+ if isinstance(camfile, IPC_D_356):
+ layer_class = 'ipc_netlist'
+ return cls(filename, layer_class, camfile)
+
+ def __init__(self, filename=None, layer_class=None, cam_source=None, **kwargs):
+ super(PCBLayer, self).__init__(**kwargs)
+ self.filename = filename
+ self.layer_class = layer_class
+ self.cam_source = cam_source
+ self.surface = None
+ self.primitives = cam_source.primitives if cam_source is not None else []
+
+ @property
+ def bounds(self):
+ if self.cam_source is not None:
+ return self.cam_source.bounds
+ else:
+ return None
+
+
+class DrillLayer(PCBLayer):
+ @classmethod
+ def from_gerber(cls, camfile):
+ return cls(camfile.filename, camfile)
+
+ def __init__(self, filename=None, cam_source=None, layers=None, **kwargs):
+ super(DrillLayer, self).__init__(filename, 'drill', cam_source, **kwargs)
+ self.layers = layers if layers is not None else ['top', 'bottom']
+
+
+class InternalLayer(PCBLayer):
+ @classmethod
+ def from_gerber(cls, camfile):
+ filename = camfile.filename
+ try:
+ order = int(re.search(r'\d+', filename).group())
+ except:
+ order = 0
+ return cls(filename, camfile, order)
+
+ def __init__(self, filename=None, cam_source=None, order=0, **kwargs):
+ super(InternalLayer, self).__init__(filename, 'internal', cam_source, **kwargs)
+ self.order = order
+
+ def __eq__(self, other):
+ if not hasattr(other, 'order'):
+ raise TypeError()
+ return (self.order == other.order)
+
+ def __ne__(self, other):
+ if not hasattr(other, 'order'):
+ raise TypeError()
+ return (self.order != other.order)
+
+ def __gt__(self, other):
+ if not hasattr(other, 'order'):
+ raise TypeError()
+ return (self.order > other.order)
+
+ def __lt__(self, other):
+ if not hasattr(other, 'order'):
+ raise TypeError()
+ return (self.order < other.order)
+
+ def __ge__(self, other):
+ if not hasattr(other, 'order'):
+ raise TypeError()
+ return (self.order >= other.order)
+
+ def __le__(self, other):
+ if not hasattr(other, 'order'):
+ raise TypeError()
+ return (self.order <= other.order)
+
+
+class LayerSet(Renderable):
+ def __init__(self, name, layers, **kwargs):
+ super(LayerSet, self).__init__(**kwargs)
+ self.name = name
+ self.layers = list(layers)
+
+ def __len__(self):
+ return len(self.layers)
+
+ def __getitem__(self, item):
+ return self.layers[item]
+
+ def to_render(self):
+ return self.layers
+
+ def apply_theme(self, theme):
+ pass
diff --git a/gerber/pcb.py b/gerber/pcb.py
new file mode 100644
index 0000000..990a05c
--- /dev/null
+++ b/gerber/pcb.py
@@ -0,0 +1,107 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+from .exceptions import ParseError
+from .layers import PCBLayer, LayerSet, sort_layers
+from .common import read as gerber_read
+from .utils import listdir
+from .render import theme
+
+
+class PCB(object):
+
+ @classmethod
+ def from_directory(cls, directory, board_name=None, verbose=False):
+ layers = []
+ names = set()
+ # Validate
+ directory = os.path.abspath(directory)
+ if not os.path.isdir(directory):
+ raise TypeError('{} is not a directory.'.format(directory))
+ # Load gerber files
+ for filename in listdir(directory, True, True):
+ try:
+ camfile = gerber_read(os.path.join(directory, filename))
+ layer = PCBLayer.from_gerber(camfile)
+ layers.append(layer)
+ names.add(os.path.splitext(filename)[0])
+ if verbose:
+ print('Added {} layer <{}>'.format(layer.layer_class, filename))
+ except ParseError:
+ if verbose:
+ print('Skipping file {}'.format(filename))
+ # Try to guess board name
+ if board_name is None:
+ if len(names) == 1:
+ board_name = names.pop()
+ else:
+ board_name = os.path.basename(directory)
+ # Return PCB
+ return cls(layers, board_name)
+
+ def __init__(self, layers, name=None):
+ self.layers = sort_layers(layers)
+ self.name = name
+ self._theme = theme.THEMES['Default']
+ self.theme = self._theme
+
+ def __len__(self):
+ return len(self.layers)
+
+ @property
+ def theme(self):
+ return self._theme
+
+ @theme.setter
+ def theme(self, theme):
+ self._theme = theme
+ for layer in self.layers:
+ layer.settings = theme[layer.layer_class]
+
+ @property
+ def top_layers(self):
+ board_layers = [l for l in reversed(self.layers) if l.layer_class in ('topsilk', 'topmask', 'top')]
+ drill_layers = [l for l in self.drill_layers if 'top' in l.layers]
+ return board_layers + drill_layers
+
+ @property
+ def bottom_layers(self):
+ board_layers = [l for l in self.layers if l.layer_class in ('bottomsilk', 'bottommask', 'bottom')]
+ drill_layers = [l for l in self.drill_layers if 'bottom' in l.layers]
+ return board_layers + drill_layers
+
+ @property
+ def drill_layers(self):
+ return [l for l in self.layers if l.layer_class == 'drill']
+
+ @property
+ def layer_count(self):
+ """ Number of *COPPER* layers
+ """
+ return len([l for l in self.layers if l.layer_class in ('top', 'bottom', 'internal')])
+
+ @property
+ def board_bounds(self):
+ for layer in self.layers:
+ if layer.layer_class == 'outline':
+ return layer.bounds
+ for layer in self.layers:
+ if layer.layer_class == 'top':
+ return layer.bounds
+
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py
index 8283ae0..7acf29a 100644
--- a/gerber/render/cairo_backend.py
+++ b/gerber/render/cairo_backend.py
@@ -17,7 +17,7 @@
import cairocffi as cairo
-from operator import mul
+from operator import mul, div
import math
import tempfile
@@ -39,16 +39,16 @@ class GerberCairoContext(GerberContext):
self.bg = False
self.mask = None
self.mask_ctx = None
- self.origin_in_pixels = None
- self.size_in_pixels = None
+ self.origin_in_inch = None
+ self.size_in_inch = None
- def set_bounds(self, bounds):
+ def set_bounds(self, bounds, new_surface=False):
origin_in_inch = (bounds[0][0], bounds[1][0])
size_in_inch = (abs(bounds[0][1] - bounds[0][0]), abs(bounds[1][1] - bounds[1][0]))
size_in_pixels = map(mul, size_in_inch, self.scale)
- self.origin_in_pixels = tuple(map(mul, origin_in_inch, self.scale)) if self.origin_in_pixels is None else self.origin_in_pixels
- self.size_in_pixels = size_in_pixels if self.size_in_pixels is None else self.size_in_pixels
- if self.surface is None:
+ self.origin_in_inch = origin_in_inch if self.origin_in_inch is None else self.origin_in_inch
+ self.size_in_inch = size_in_inch if self.size_in_inch is None else self.size_in_inch
+ if (self.surface is None) or new_surface:
self.surface_buffer = tempfile.NamedTemporaryFile()
self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1])
self.ctx = cairo.Context(self.surface)
@@ -61,6 +61,36 @@ class GerberCairoContext(GerberContext):
self.mask_ctx.scale(1, -1)
self.mask_ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1])
+ def render_layers(self, layers, filename):
+ """ Render a set of layers
+ """
+ self.set_bounds(layers[0].bounds, True)
+ self._paint_background(True)
+ for layer in layers:
+ self._render_layer(layer)
+ self.dump(filename)
+
+ @property
+ def origin_in_pixels(self):
+ return tuple(map(mul, self.origin_in_inch, self.scale)) if self.origin_in_inch is not None else (0.0, 0.0)
+
+ @property
+ def size_in_pixels(self):
+ return tuple(map(mul, self.size_in_inch, self.scale)) if self.size_in_inch is not None else (0.0, 0.0)
+
+ def _render_layer(self, layer):
+ self.color = layer.settings.color
+ self.alpha = layer.settings.alpha
+ self.invert = layer.settings.invert
+ if layer.settings.mirror:
+ raise Warning('mirrored layers aren\'t supported yet...')
+ if self.invert:
+ self._clear_mask()
+ for p in layer.primitives:
+ self.render(p)
+ if self.invert:
+ self._render_mask()
+
def _render_line(self, line, color):
start = map(mul, line.start, self.scale)
end = map(mul, line.end, self.scale)
@@ -178,12 +208,13 @@ class GerberCairoContext(GerberContext):
self._render_circle(circle, color)
def _render_test_record(self, primitive, color):
- self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
- self.ctx.set_font_size(200)
- self._render_circle(Circle(primitive.position, 0.01), color)
+ position = tuple(map(add, primitive.position, self.origin_in_inch))
+ self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
+ self.ctx.set_font_size(13)
+ self._render_circle(Circle(position, 0.015), color)
self.ctx.set_source_rgb(*color)
self.ctx.set_operator(cairo.OPERATOR_OVER if primitive.level_polarity == "dark" else cairo.OPERATOR_CLEAR)
- self.ctx.move_to(*[self.scale[0] * (coord + 0.01) for coord in primitive.position])
+ self.ctx.move_to(*[self.scale[0] * (coord + 0.015) for coord in position])
self.ctx.scale(1, -1)
self.ctx.show_text(primitive.net_name)
self.ctx.scale(1, -1)
@@ -196,14 +227,15 @@ class GerberCairoContext(GerberContext):
def _render_mask(self):
self.ctx.set_operator(cairo.OPERATOR_OVER)
ptn = cairo.SurfacePattern(self.mask)
- ptn.set_matrix(cairo.Matrix(xx=1.0, yy=-1.0, x0=-self.origin_in_pixels[0], y0=self.size_in_pixels[1] + self.origin_in_pixels[1]))
+ ptn.set_matrix(cairo.Matrix(xx=1.0, yy=-1.0, x0=-self.origin_in_pixels[0],
+ y0=self.size_in_pixels[1] + self.origin_in_pixels[1]))
self.ctx.set_source(ptn)
self.ctx.paint()
- def _paint_background(self):
- if not self.bg:
+ def _paint_background(self, force=False):
+ if (not self.bg) or force:
self.bg = True
- self.ctx.set_source_rgba(*self.background_color)
+ self.ctx.set_source_rgba(*self.background_color, alpha=1.0)
self.ctx.paint()
def dump(self, filename):
diff --git a/gerber/render/render.py b/gerber/render/render.py
index 737061e..c76ead5 100644
--- a/gerber/render/render.py
+++ b/gerber/render/render.py
@@ -60,7 +60,6 @@ class GerberContext(object):
def __init__(self, units='inch'):
self._units = units
self._color = (0.7215, 0.451, 0.200)
- self._drill_color = (0.25, 0.25, 0.25)
self._background_color = (0.0, 0.0, 0.0)
self._alpha = 1.0
self._invert = False
@@ -150,7 +149,7 @@ class GerberContext(object):
elif isinstance(primitive, Polygon):
self._render_polygon(primitive, color)
elif isinstance(primitive, Drill):
- self._render_drill(primitive, self.drill_color)
+ self._render_drill(primitive, color)
elif isinstance(primitive, TestRecord):
self._render_test_record(primitive, color)
else:
@@ -185,15 +184,7 @@ class GerberContext(object):
class Renderable(object):
- def __init__(self, color=None, alpha=None, invert=False):
- self.color = color
- self.alpha = alpha
- self.invert = invert
-
- def to_render(self):
- """ Override this in subclass. Should return a list of Primitives or Renderables
- """
- raise NotImplementedError('to_render() must be implemented in subclass')
-
- def apply_theme(self, theme):
- raise NotImplementedError('apply_theme() must be implemented in subclass')
+ def __init__(self, settings=None):
+ self.settings = settings
+ self.primitives = []
+
diff --git a/gerber/render/theme.py b/gerber/render/theme.py
index eae3735..5978831 100644
--- a/gerber/render/theme.py
+++ b/gerber/render/theme.py
@@ -19,6 +19,9 @@
COLORS = {
'black': (0.0, 0.0, 0.0),
'white': (1.0, 1.0, 1.0),
+ 'red': (1.0, 0.0, 0.0),
+ 'green': (0.0, 1.0, 0.0),
+ 'blue' : (0.0, 0.0, 1.0),
'fr-4': (0.290, 0.345, 0.0),
'green soldermask': (0.0, 0.612, 0.396),
'blue soldermask': (0.059, 0.478, 0.651),
@@ -31,29 +34,38 @@ COLORS = {
class RenderSettings(object):
- def __init__(self, color, alpha=1.0, invert=False):
+ def __init__(self, color, alpha=1.0, invert=False, mirror=False):
self.color = color
self.alpha = alpha
- self.invert = False
+ self.invert = invert
+ self.mirror = mirror
class Theme(object):
- def __init__(self, **kwargs):
- self.background = kwargs.get('background', RenderSettings(COLORS['black'], 0.0))
+ def __init__(self, name=None, **kwargs):
+ self.name = 'Default' if name is None else name
+ self.background = kwargs.get('background', RenderSettings(COLORS['black'], alpha=0.0))
self.topsilk = kwargs.get('topsilk', RenderSettings(COLORS['white']))
self.bottomsilk = kwargs.get('bottomsilk', RenderSettings(COLORS['white']))
- self.topmask = kwargs.get('topmask', RenderSettings(COLORS['green soldermask'], 0.8, True))
- self.bottommask = kwargs.get('bottommask', RenderSettings(COLORS['green soldermask'], 0.8, True))
+ self.topmask = kwargs.get('topmask', RenderSettings(COLORS['green soldermask'], alpha=0.8, invert=True))
+ self.bottommask = kwargs.get('bottommask', RenderSettings(COLORS['green soldermask'], alpha=0.8, invert=True))
self.top = kwargs.get('top', RenderSettings(COLORS['hasl copper']))
self.bottom = kwargs.get('top', RenderSettings(COLORS['hasl copper']))
- self.drill = kwargs.get('drill', self.background)
+ self.drill = kwargs.get('drill', RenderSettings(COLORS['black']))
+ self.ipc_netlist = kwargs.get('ipc_netlist', RenderSettings(COLORS['red']))
+ def __getitem__(self, key):
+ return getattr(self, key)
THEMES = {
'Default': Theme(),
- 'Osh Park': Theme(top=COLORS['enig copper'],
- bottom=COLORS['enig copper'],
- topmask=COLORS['purple soldermask'],
- bottommask=COLORS['purple soldermask']),
+ 'OSH Park': Theme(name='OSH Park',
+ top=RenderSettings(COLORS['enig copper']),
+ bottom=RenderSettings(COLORS['enig copper']),
+ topmask=RenderSettings(COLORS['purple soldermask'], alpha=0.8, invert=True),
+ bottommask=RenderSettings(COLORS['purple soldermask'], alpha=0.8, invert=True)),
+ 'Blue': Theme(name='Blue',
+ topmask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True),
+ bottommask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True)),
}
diff --git a/gerber/tests/test_layers.py b/gerber/tests/test_layers.py
new file mode 100644
index 0000000..c77084d
--- /dev/null
+++ b/gerber/tests/test_layers.py
@@ -0,0 +1,33 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Author: Hamilton Kibbe <ham@hamiltonkib.be>
+
+from .tests import *
+from ..layers import guess_layer_class, hints
+
+
+def test_guess_layer_class():
+ """ Test layer type inferred correctly from filename
+ """
+
+ # Add any specific test cases here (filename, layer_class)
+ test_vectors = [(None, 'unknown'), ('NCDRILL.TXT', 'unknown'),
+ ('example_board.gtl', 'top'),
+ ('exampmle_board.sst', 'topsilk'),
+ ('ipc-d-356.ipc', 'ipc_netlist'),]
+
+ for hint in hints:
+ for ext in hint.ext:
+ assert_equal(hint.layer, guess_layer_class('board.{}'.format(ext)))
+ for name in hint.name:
+ assert_equal(hint.layer, guess_layer_class('{}.pho'.format(name)))
+
+ for filename, layer_class in test_vectors:
+ assert_equal(layer_class, guess_layer_class(filename))
+
+
+def test_sort_layers():
+ """ Test layer ordering
+ """
+ pass
diff --git a/gerber/utils.py b/gerber/utils.py
index 1c0af52..6653683 100644
--- a/gerber/utils.py
+++ b/gerber/utils.py
@@ -26,6 +26,7 @@ files.
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
# License:
+import os
from math import radians, sin, cos
from operator import sub
@@ -219,7 +220,10 @@ def detect_file_format(data):
if 'M48' in line:
return 'excellon'
elif '%FS' in line:
- return'rs274x'
+ return 'rs274x'
+ elif ((len(line.split()) >= 2) and
+ (line.split()[0] == 'P') and (line.split()[1] == 'JOB')):
+ return 'ipc_d_356'
return 'unknown'
@@ -288,3 +292,13 @@ def rotate_point(point, angle, center=(0.0, 0.0)):
x = center[0] + (cos(angle) * xdelta) - (sin(angle) * ydelta)
y = center[1] + (sin(angle) * xdelta) - (cos(angle) * ydelta)
return (x, y)
+
+
+def listdir(directory, ignore_hidden=True, ignore_os=True):
+ os_files = ('.DS_Store', 'Thumbs.db', 'ethumbs.db')
+ files = os.listdir(directory)
+ if ignore_hidden:
+ files = [f for f in files if not f.startswith('.')]
+ if ignore_os:
+ files = [f for f in files if not f in os_files]
+ return files