diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | examples/cairo_example.png | bin | 104949 -> 104401 bytes | |||
-rw-r--r-- | examples/cairo_example.py | 1 | ||||
-rw-r--r-- | examples/gerbers/bottom_copper.GBL | 1811 | ||||
-rw-r--r-- | examples/gerbers/bottom_mask.GBS | 66 | ||||
-rw-r--r-- | examples/gerbers/shld.drd | 354 | ||||
-rw-r--r-- | examples/pcb_bottom.png | bin | 0 -> 39174 bytes | |||
-rw-r--r-- | examples/pcb_example.py | 39 | ||||
-rw-r--r-- | examples/pcb_top.png | bin | 0 -> 99269 bytes | |||
-rw-r--r-- | gerber/__init__.py | 3 | ||||
-rw-r--r-- | gerber/common.py | 3 | ||||
-rw-r--r-- | gerber/exceptions.py | 1 | ||||
-rw-r--r-- | gerber/ipc356.py | 89 | ||||
-rw-r--r-- | gerber/layers.py | 246 | ||||
-rw-r--r-- | gerber/pcb.py | 94 | ||||
-rw-r--r-- | gerber/render/cairo_backend.py | 124 | ||||
-rw-r--r-- | gerber/render/render.py | 15 | ||||
-rw-r--r-- | gerber/render/theme.py | 45 | ||||
-rw-r--r-- | gerber/tests/test_layers.py | 33 | ||||
-rw-r--r-- | gerber/utils.py | 16 |
20 files changed, 2438 insertions, 503 deletions
@@ -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 Binary files differindex fc4536a..f33cc69 100644 --- a/examples/cairo_example.png +++ b/examples/cairo_example.png 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 Binary files differnew file mode 100644 index 0000000..1e8c369 --- /dev/null +++ b/examples/pcb_bottom.png 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 Binary files differnew file mode 100644 index 0000000..bf1d687 --- /dev/null +++ b/examples/pcb_top.png 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..2b73893 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
+
+
+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(object):
+ """ 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(object):
+ 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..0518dd4 --- /dev/null +++ b/gerber/pcb.py @@ -0,0 +1,94 @@ +#! /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 + + +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 + + def __len__(self): + return len(self.layers) + + @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..4e71e75 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -21,7 +21,8 @@ from operator import mul import math
import tempfile
-from .render import GerberContext
+from .render import GerberContext, RenderSettings
+from .theme import THEMES
from ..primitives import *
try:
@@ -39,16 +40,25 @@ 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
+ self._xform_matrix = None
- def set_bounds(self, bounds):
+ @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 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:
+ size_in_pixels = tuple(map(mul, size_in_inch, self.scale))
+ 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)
@@ -60,6 +70,58 @@ class GerberCairoContext(GerberContext): self.mask_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
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])
+ self._xform_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])
+
+ def render_layers(self, layers, filename, theme=THEMES['default']):
+ """ Render a set of layers
+ """
+ self.set_bounds(layers[0].bounds, True)
+ self._paint_background(True)
+ for layer in layers:
+ self._render_layer(layer, theme)
+ self.dump(filename)
+
+ def dump(self, filename):
+ """ Save image as `filename`
+ """
+ is_svg = filename.lower().endswith(".svg")
+ if is_svg:
+ self.surface.finish()
+ self.surface_buffer.flush()
+ with open(filename, "w") as f:
+ self.surface_buffer.seek(0)
+ f.write(self.surface_buffer.read())
+ f.flush()
+ else:
+ self.surface.write_to_png(filename)
+
+ def dump_str(self):
+ """ Return a string containing the rendered image.
+ """
+ fobj = StringIO()
+ self.surface.write_to_png(fobj)
+ return fobj.getvalue()
+
+ def dump_svg_str(self):
+ """ Return a string containg the rendered SVG.
+ """
+ self.surface.finish()
+ self.surface_buffer.flush()
+ return self.surface_buffer.read()
+
+ def _render_layer(self, layer, theme=THEMES['default']):
+ settings = theme.get(layer.layer_class, RenderSettings())
+ self.color = settings.color
+ self.alpha = settings.alpha
+ self.invert = settings.invert
+ if settings.mirror:
+ raise Warning('mirrored layers aren\'t supported yet...')
+ if self.invert:
+ self._clear_mask()
+ for prim in layer.primitives:
+ self.render(prim)
+ if self.invert:
+ self._render_mask()
def _render_line(self, line, color):
start = map(mul, line.start, self.scale)
@@ -178,12 +240,14 @@ 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)
- self.ctx.set_source_rgb(*color)
+ position = tuple(map(add, primitive.position, self.origin_in_inch))
+ self.ctx.set_operator(cairo.OPERATOR_OVER)
+ 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_rgba(*color, alpha=self.alpha)
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,38 +260,12 @@ 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(self._xform_matrix)
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):
- is_svg = filename.lower().endswith(".svg")
- if is_svg:
- self.surface.finish()
- self.surface_buffer.flush()
- with open(filename, "w") as f:
- self.surface_buffer.seek(0)
- f.write(self.surface_buffer.read())
- f.flush()
- else:
- self.surface.write_to_png(filename)
-
- def dump_str(self):
- """ Return a string containing the rendered image.
- """
- fobj = StringIO()
- self.surface.write_to_png(fobj)
- return fobj.getvalue()
-
- def dump_svg_str(self):
- """ Return a string containg the rendered SVG.
- """
- self.surface.finish()
- self.surface_buffer.flush()
- return self.surface_buffer.read()
diff --git a/gerber/render/render.py b/gerber/render/render.py index 737061e..6af8bf1 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: @@ -184,16 +183,10 @@ class GerberContext(object): pass -class Renderable(object): - def __init__(self, color=None, alpha=None, invert=False): +class RenderSettings(object): + def __init__(self, color=(0.0, 0.0, 0.0), alpha=1.0, invert=False, mirror=False): self.color = color self.alpha = alpha self.invert = invert + self.mirror = mirror - 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') diff --git a/gerber/render/theme.py b/gerber/render/theme.py index eae3735..e538df8 100644 --- a/gerber/render/theme.py +++ b/gerber/render/theme.py @@ -16,9 +16,14 @@ # limitations under the License. +from .render import RenderSettings + 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), @@ -30,30 +35,36 @@ COLORS = { } -class RenderSettings(object): - def __init__(self, color, alpha=1.0, invert=False): - self.color = color - self.alpha = alpha - self.invert = False - - 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) + + def get(self, key, noneval=None): + val = getattr(self, key) + return val if val is not None else noneval THEMES = { - 'Default': Theme(), - 'Osh Park': Theme(top=COLORS['enig copper'], - bottom=COLORS['enig copper'], - topmask=COLORS['purple soldermask'], - bottommask=COLORS['purple soldermask']), + 'default': Theme(), + '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 |