Add cairo example code, and use example-generated image in readme

This commit is contained in:
Hamilton Kibbe 2015-02-15 03:29:47 -05:00
parent 5cf1fa74b4
commit bfe1484160
16 changed files with 5869 additions and 22 deletions

View file

@ -3,25 +3,32 @@ PYTHON ?= python
NOSETESTS ?= nosetests
DOC_ROOT = doc
EXAMPLES = examples
.PHONY: clean
clean: doc-clean
#$(PYTHON) setup.py clean
find . -name '*.pyc' -delete
rm -rf coverage .coverage
rm -rf *.egg-info
.PHONY: test
test:
$(NOSETESTS) -s -v gerber
.PHONY: test-coverage
test-coverage:
rm -rf coverage .coverage
$(NOSETESTS) -s -v --with-coverage --cover-package=gerber
.PHONY: doc-html
doc-html:
(cd $(DOC_ROOT); make html)
.PHONY: doc-clean
doc-clean:
(cd $(DOC_ROOT); make clean)
.PHONY: examples
examples:
PYTHONPATH=. $(PYTHON) examples/cairo_example.py

View file

@ -1,6 +1,6 @@
pcb-tools
============
![Travis CI Build Status](https://travis-ci.org/curtacircuitos/pcb-tools.svg?branch=master)
[![Travis CI Build Status](https://travis-ci.org/curtacircuitos/pcb-tools.svg?branch=master)](https://travis-ci.org/curtacircuitos/pcb-tools)
[![Coverage Status](https://coveralls.io/repos/curtacircuitos/pcb-tools/badge.png?branch=master)](https://coveralls.io/r/curtacircuitos/pcb-tools?branch=master)
Tools to handle Gerber and Excellon files in Python.
@ -25,7 +25,5 @@ Useage Example:
Rendering Examples:
-------------------
###Top Composite rendering
![Composite Top Image](examples/composite_top.png)
###Bottom Composite rendering
![Composite Bottom Image](examples/composite_bottom.png)
![Composite Top Image](examples/cairo_example.png)
Source code for this example can be found [here](examples/cairo_example.py).

BIN
examples/cairo_example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

68
examples/cairo_example.py Normal file
View file

@ -0,0 +1,68 @@
#! /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 a composite
image from a set of gerber files. Each layer is loaded and drawn using a
GerberCairoContext. The color and opacity of each layer can be set individually.
Once all thedesired layers are drawn on the context, the context is written to
a .png file.
"""
import os
from gerber import read
from gerber.render import GerberCairoContext
GERBER_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gerbers'))
# Open the gerber files
copper = read(os.path.join(GERBER_FOLDER, 'copper.GTL'))
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()
# Draw the copper layer
copper.render(ctx)
# Set opacity and color for soldermask layer
ctx.alpha = 0.65
ctx.color = (0.2, 0.2, 0.75)
# Draw the soldermask layer
mask.render(ctx)
# Set opacity and color for silkscreen layer
ctx.alpha = 0.9
ctx.color = (1, 1, 1)
# Draw the silkscreen layer
silk.render(ctx)
# Set opacity for drill layer
ctx.alpha = 1.
drill.render(ctx)
# Write output to png file
ctx.dump(os.path.join(os.path.dirname(__file__), 'cairo_example.png'))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 569 KiB

3457
examples/gerbers/copper.GTL Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
%
M48
M72
T01C0.0236
T02C0.0354
T03C0.0400
T04C0.1260
T05C0.1280
%
T01
X9250Y4064
X12100Y5314
X13500Y6864
X15650Y6264
X15200Y4514
X13550Y8764
X13350Y10114
X13300Y11464
X11650Y13164
X10000Y15114
X6500Y13714
X4150Y11564
X14250Y14964
X15850Y9914
T02
X17200Y9464
X18200Y9964
X18200Y10964
X17200Y10464
X17200Y11464
X18200Y11964
T03
X18350Y16814
X17350Y16814
X7350Y16964
X6350Y16964
X5350Y16964
X1500Y12564
X1500Y11564
X1500Y10564
X1500Y9564
X1500Y8564
T04
X2350Y5114
X2300Y16064
X20800Y16064
X20800Y5064
T05
X20700Y8714
X20700Y12714
M30

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,162 @@
G75*
%MOIN*%
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239,22.5*
%
%ADD10R,0.0340X0.0880*%
%ADD11R,0.0671X0.0237*%
%ADD12R,0.4178X0.4332*%
%ADD13R,0.0930X0.0500*%
%ADD14R,0.0710X0.1655*%
%ADD15R,0.0671X0.0592*%
%ADD16R,0.0592X0.0671*%
%ADD17R,0.0710X0.1615*%
%ADD18R,0.1419X0.0828*%
%ADD19C,0.0634*%
%ADD20C,0.1360*%
%ADD21R,0.0474X0.0580*%
%ADD22C,0.0680*%
%ADD23R,0.0552X0.0552*%
%ADD24C,0.1340*%
%ADD25C,0.0476*%
D10*
X005000Y010604D03*
X005500Y010604D03*
X006000Y010604D03*
X006500Y010604D03*
X006500Y013024D03*
X006000Y013024D03*
X005500Y013024D03*
X005000Y013024D03*
D11*
X011423Y007128D03*
X011423Y006872D03*
X011423Y006616D03*
X011423Y006360D03*
X011423Y006104D03*
X011423Y005848D03*
X011423Y005592D03*
X011423Y005336D03*
X011423Y005080D03*
X011423Y004825D03*
X011423Y004569D03*
X011423Y004313D03*
X011423Y004057D03*
X011423Y003801D03*
X014277Y003801D03*
X014277Y004057D03*
X014277Y004313D03*
X014277Y004569D03*
X014277Y004825D03*
X014277Y005080D03*
X014277Y005336D03*
X014277Y005592D03*
X014277Y005848D03*
X014277Y006104D03*
X014277Y006360D03*
X014277Y006616D03*
X014277Y006872D03*
X014277Y007128D03*
D12*
X009350Y010114D03*
D13*
X012630Y010114D03*
X012630Y010784D03*
X012630Y011454D03*
X012630Y009444D03*
X012630Y008774D03*
D14*
X010000Y013467D03*
X010000Y016262D03*
D15*
X004150Y012988D03*
X004150Y012240D03*
X009900Y005688D03*
X009900Y004940D03*
X015000Y006240D03*
X015000Y006988D03*
D16*
X014676Y008364D03*
X015424Y008364D03*
X017526Y004514D03*
X018274Y004514D03*
X010674Y004064D03*
X009926Y004064D03*
X004174Y009564D03*
X003426Y009564D03*
X005376Y014564D03*
X006124Y014564D03*
D17*
X014250Y016088D03*
X014250Y012741D03*
D18*
X014250Y010982D03*
X014250Y009447D03*
D19*
X017200Y009464D03*
X018200Y009964D03*
X018200Y010964D03*
X017200Y010464D03*
X017200Y011464D03*
X018200Y011964D03*
D20*
X020700Y012714D03*
X020700Y008714D03*
D21*
X005004Y003814D03*
X005004Y004864D03*
X005004Y005864D03*
X005004Y006914D03*
X008696Y006914D03*
X008696Y005864D03*
X008696Y004864D03*
X008696Y003814D03*
D22*
X001800Y008564D02*
X001200Y008564D01*
X001200Y009564D02*
X001800Y009564D01*
X001800Y010564D02*
X001200Y010564D01*
X001200Y011564D02*
X001800Y011564D01*
X001800Y012564D02*
X001200Y012564D01*
X005350Y016664D02*
X005350Y017264D01*
X006350Y017264D02*
X006350Y016664D01*
X007350Y016664D02*
X007350Y017264D01*
X017350Y017114D02*
X017350Y016514D01*
X018350Y016514D02*
X018350Y017114D01*
D23*
X016613Y004514D03*
X015787Y004514D03*
D24*
X020800Y005064D03*
X020800Y016064D03*
X002300Y016064D03*
X002350Y005114D03*
D25*
X009250Y004064D03*
X012100Y005314D03*
X013500Y006864D03*
X015650Y006264D03*
X015200Y004514D03*
X013550Y008764D03*
X013350Y010114D03*
X013300Y011464D03*
X011650Y013164D03*
X010000Y015114D03*
X006500Y013714D03*
X004150Y011564D03*
X014250Y014964D03*
X015850Y009914D03*
M02*

View file

@ -265,6 +265,11 @@ class ExcellonParser(object):
elif line[0] == 'R' and self.state != 'HEADER':
stmt = RepeatHoleStmt.from_excellon(line, self._settings())
self.statements.append(stmt)
for i in xrange(stmt.count):
self.pos[0] += stmt.xdelta
self.pos[1] += stmt.ydelta
self.hits.append((self.active_tool, tuple(self.pos)))
self.active_tool._hit()
elif line[0] in ['X', 'Y']:
stmt = CoordinateStmt.from_excellon(line, self._settings())

View file

@ -296,16 +296,16 @@ class RepeatHoleStmt(ExcellonStatement):
if stmt['ydelta'] is not '' else None)
return cls(count, xdelta, ydelta)
def __init__(self, count, xdelta=None, ydelta=None):
def __init__(self, count, xdelta=0.0, ydelta=0.0):
self.count = count
self.xdelta = xdelta
self.ydelta = ydelta
def to_excellon(self, settings):
stmt = 'R%d' % self.count
if self.xdelta is not None:
if self.xdelta != 0.0:
stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, settings.zero_suppression)
if self.ydelta is not None:
if self.ydelta != 0.0:
stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, settings.zero_suppression)
return stmt

View file

@ -22,7 +22,7 @@ import math
from ..primitives import *
SCALE = 400.
SCALE = 4000.
class GerberCairoContext(GerberContext):
@ -42,10 +42,12 @@ class GerberCairoContext(GerberContext):
self.background = False
def set_bounds(self, bounds):
xbounds, ybounds = bounds
self.ctx.rectangle(SCALE * xbounds[0], SCALE * ybounds[0], SCALE * (xbounds[1]- xbounds[0]), SCALE * (ybounds[1] - ybounds[0]))
self.ctx.set_source_rgb(0,0,0)
self.ctx.fill()
if not self.background:
xbounds, ybounds = bounds
self.ctx.rectangle(SCALE * xbounds[0], SCALE * ybounds[0], SCALE * (xbounds[1]- xbounds[0]), SCALE * (ybounds[1] - ybounds[0]))
self.ctx.set_source_rgb(0,0,0)
self.ctx.fill()
self.background = True
def _render_line(self, line, color):
start = map(mul, line.start, self.scale)

View file

@ -4,6 +4,7 @@
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
from ..cam import FileSettings
from ..excellon import read, detect_excellon_format, ExcellonFile, ExcellonParser
from ..excellon_statements import ExcellonTool
from tests import *
import os
@ -120,6 +121,7 @@ def test_parse_absolute_mode():
def test_parse_repeat_hole():
p = ExcellonParser(FileSettings())
p.active_tool = ExcellonTool(FileSettings(), number=8)
p._parse('R03X1.5Y1.5')
assert_equal(p.statements[0].count, 3)