summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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