From b5f8451c8f37acf9148bbd09f3326eb5aba3e053 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 8 Oct 2014 18:34:16 -0400 Subject: cairo support --- gerber/render/cairo_backend.py | 156 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 gerber/render/cairo_backend.py (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py new file mode 100644 index 0000000..d5efc2d --- /dev/null +++ b/gerber/render/cairo_backend.py @@ -0,0 +1,156 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2014 Hamilton Kibbe + +# 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. + +from .render import GerberContext +from .apertures import Circle, Rect, Obround, Polygon +import cairo +import math + +SCALE = 200. + +class CairoCircle(Circle): + def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): + ctx.set_source_rgb (*color) + ctx.set_line_width(self.diameter * SCALE) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) + ctx.line_to(x * SCALE, y * SCALE) + ctx.stroke() + + def arc(self, ctx, x, y, i, j, direction, color=(184/255., 115/255., 51/255.)): + ctx_x, ctx_y = ctx.get_current_point() + + # Do the math + center = ((x + i) * SCALE, (y + j) * SCALE) + radius = math.sqrt(math.pow(ctx_x - center[0], 2) + math.pow(ctx_y - center[1], 2)) + delta_x0 = (ctx_x - center[0]) + delta_y0 = (ctx_y - center[1]) + delta_x1 = (x * SCALE - center[0]) + delta_y1 = (y * SCALE - center[1]) + theta0 = math.atan2(delta_y0, delta_x0) + theta1 = math.atan2(delta_y1, delta_x1) + # Draw the arc + ctx.set_source_rgb (*color) + ctx.set_line_width(self.diameter * SCALE) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) + if direction == 'clockwise': + ctx.arc_negative(center[0], center[1], radius, theta0, theta1) + else: + ctx.arc(center[0], center[1], radius, theta0, theta1) + ctx.stroke() + + def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): + ctx.set_source_rgb (*color) + ctx.set_line_width(0) + ctx.arc(x * SCALE, y * SCALE, (self.diameter/2.) * SCALE, 0, 2 * math.pi) + ctx.fill() + +class CairoRect(Rect): + def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): + ctx.set_source_rgb (*color) + ctx.set_line_width(self.diameter * SCALE) + ctx.set_line_cap(cairo.LINE_CAP_SQUARE) + ctx.line_to(x * SCALE, y * SCALE) + ctx.stroke() + + def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): + xsize, ysize = self.size + ctx.set_source_rgb (*color) + ctx.set_line_width(0) + x0 = SCALE * (x - (xsize / 2.)) + y0 = SCALE * (y - (ysize / 2.)) + + ctx.rectangle(x0,y0,SCALE * xsize, SCALE * ysize) + ctx.fill() + + + +class GerberCairoContext(GerberContext): + def __init__(self, surface=None, size=(1000, 1000), + color='rgb(184, 115, 51)', drill_color='gray'): + GerberContext.__init__(self) + if surface is None: + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + size[0], size[1]) + else: + self.surface = surface + self.ctx = cairo.Context(self.surface) + self.size = size + self.ctx.translate(0, self.size[1]) + self.ctx.scale(1,-1) + self.apertures = {} + self.color = color + self.drill_color = drill_color + 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() + + def define_aperture(self, d, shape, modifiers): + aperture = None + if shape == 'C': + aperture = CairoCircle(diameter=float(modifiers[0][0])) + elif shape == 'R': + aperture = CairoRect(size=modifiers[0][0:2]) + self.apertures[d] = aperture + + def stroke(self, x, y, i, j): + super(GerberCairoContext, self).stroke(x, y, i, j) + + if self.interpolation == 'linear': + self.line(x, y) + elif self.interpolation == 'arc': + self.arc(x, y, i, j) + self.move(x,y) + + def line(self, x, y): + x, y = self.resolve(x, y) + ap = self.apertures.get(self.aperture, None) + if ap is None: + return + ap.line(self.ctx, x, y) + + def arc(self, x, y, i, j): + super(GerberCairoContext, self).arc(x, y, i, j) + ap = self.apertures.get(self.aperture, None) + if ap is None: + return + ap.arc(self.ctx, x, y, i, j, self.direction) + + + def flash(self, x, y): + x, y = self.resolve(x, y) + ap = self.apertures.get(self.aperture, None) + if ap is None: + return + ap.flash(self.ctx, x, y) + self.move(x, y, resolve=False) + + def move(self, x, y, resolve=True): + super(GerberCairoContext, self).move(x, y, resolve) + if x is None: + x = self.x + if y is None: + y = self.y + if self.x is not None and self.y is not None: + self.ctx.move_to(x * SCALE, y * SCALE) + + + def dump(self, filename): + self.surface.write_to_png(filename) \ No newline at end of file -- cgit From 0437e4198a0ff5d909d4321768341a173930904c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 26 Oct 2014 22:35:56 -0400 Subject: cairo working --- gerber/render/cairo_backend.py | 213 ++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 111 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index d5efc2d..f5e5aca 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -16,71 +16,71 @@ # limitations under the License. from .render import GerberContext -from .apertures import Circle, Rect, Obround, Polygon -import cairo +from operator import mul +import cairocffi as cairo import math -SCALE = 200. - -class CairoCircle(Circle): - def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): - ctx.set_source_rgb (*color) - ctx.set_line_width(self.diameter * SCALE) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.line_to(x * SCALE, y * SCALE) - ctx.stroke() - - def arc(self, ctx, x, y, i, j, direction, color=(184/255., 115/255., 51/255.)): - ctx_x, ctx_y = ctx.get_current_point() - - # Do the math - center = ((x + i) * SCALE, (y + j) * SCALE) - radius = math.sqrt(math.pow(ctx_x - center[0], 2) + math.pow(ctx_y - center[1], 2)) - delta_x0 = (ctx_x - center[0]) - delta_y0 = (ctx_y - center[1]) - delta_x1 = (x * SCALE - center[0]) - delta_y1 = (y * SCALE - center[1]) - theta0 = math.atan2(delta_y0, delta_x0) - theta1 = math.atan2(delta_y1, delta_x1) - # Draw the arc - ctx.set_source_rgb (*color) - ctx.set_line_width(self.diameter * SCALE) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - if direction == 'clockwise': - ctx.arc_negative(center[0], center[1], radius, theta0, theta1) - else: - ctx.arc(center[0], center[1], radius, theta0, theta1) - ctx.stroke() - - def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): - ctx.set_source_rgb (*color) - ctx.set_line_width(0) - ctx.arc(x * SCALE, y * SCALE, (self.diameter/2.) * SCALE, 0, 2 * math.pi) - ctx.fill() - -class CairoRect(Rect): - def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): - ctx.set_source_rgb (*color) - ctx.set_line_width(self.diameter * SCALE) - ctx.set_line_cap(cairo.LINE_CAP_SQUARE) - ctx.line_to(x * SCALE, y * SCALE) - ctx.stroke() - - def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): - xsize, ysize = self.size - ctx.set_source_rgb (*color) - ctx.set_line_width(0) - x0 = SCALE * (x - (xsize / 2.)) - y0 = SCALE * (y - (ysize / 2.)) - - ctx.rectangle(x0,y0,SCALE * xsize, SCALE * ysize) - ctx.fill() - +SCALE = 300. + + +#class CairoCircle(Circle): +# def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): +# ctx.set_source_rgb (*color) +# ctx.set_line_width(self.diameter * SCALE) +# ctx.set_line_cap(cairo.LINE_CAP_ROUND) +# ctx.line_to(x * SCALE, y * SCALE) +# ctx.stroke() +# +# def arc(self, ctx, x, y, i, j, direction, color=(184/255., 115/255., 51/255.)): +# ctx_x, ctx_y = ctx.get_current_point() +# +# # Do the math +# center = ((x + i) * SCALE, (y + j) * SCALE) +# radius = math.sqrt(math.pow(ctx_x - center[0], 2) + math.pow(ctx_y - center[1], 2)) +# delta_x0 = (ctx_x - center[0]) +# delta_y0 = (ctx_y - center[1]) +# delta_x1 = (x * SCALE - center[0]) +# delta_y1 = (y * SCALE - center[1]) +# theta0 = math.atan2(delta_y0, delta_x0) +# theta1 = math.atan2(delta_y1, delta_x1) +# # Draw the arc +# ctx.set_source_rgb (*color) +# ctx.set_line_width(self.diameter * SCALE) +# ctx.set_line_cap(cairo.LINE_CAP_ROUND) +# if direction == 'clockwise': +# ctx.arc_negative(center[0], center[1], radius, theta0, theta1) +# else: +# ctx.arc(center[0], center[1], radius, theta0, theta1) +# ctx.stroke() +# +# def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): +# ctx.set_source_rgb (*color) +# ctx.set_line_width(0) +# ctx.arc(x * SCALE, y * SCALE, (self.diameter/2.) * SCALE, 0, 2 * math.pi) +# ctx.fill() +# +#class CairoRect(Rect): +# def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): +# ctx.set_source_rgb (*color) +# ctx.set_line_width(self.diameter * SCALE) +# ctx.set_line_cap(cairo.LINE_CAP_SQUARE) +# ctx.line_to(x * SCALE, y * SCALE) +# ctx.stroke() +# +# def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): +# xsize, ysize = self.size +# ctx.set_source_rgb (*color) +# ctx.set_line_width(0) +# x0 = SCALE * (x - (xsize / 2.)) +# y0 = SCALE * (y - (ysize / 2.)) +# +# ctx.rectangle(x0,y0,SCALE * xsize, SCALE * ysize) +# ctx.fill() +# class GerberCairoContext(GerberContext): - def __init__(self, surface=None, size=(1000, 1000), - color='rgb(184, 115, 51)', drill_color='gray'): + def __init__(self, surface=None, size=(1000, 1000)): GerberContext.__init__(self) if surface is None: self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, @@ -90,10 +90,9 @@ class GerberCairoContext(GerberContext): self.ctx = cairo.Context(self.surface) self.size = size self.ctx.translate(0, self.size[1]) - self.ctx.scale(1,-1) + self.scale = (SCALE,SCALE) + self.ctx.scale(1, -1) self.apertures = {} - self.color = color - self.drill_color = drill_color self.background = False def set_bounds(self, bounds): @@ -102,55 +101,47 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgb(0,0,0) self.ctx.fill() - def define_aperture(self, d, shape, modifiers): - aperture = None - if shape == 'C': - aperture = CairoCircle(diameter=float(modifiers[0][0])) - elif shape == 'R': - aperture = CairoRect(size=modifiers[0][0:2]) - self.apertures[d] = aperture - - def stroke(self, x, y, i, j): - super(GerberCairoContext, self).stroke(x, y, i, j) - - if self.interpolation == 'linear': - self.line(x, y) - elif self.interpolation == 'arc': - self.arc(x, y, i, j) - self.move(x,y) - - def line(self, x, y): - x, y = self.resolve(x, y) - ap = self.apertures.get(self.aperture, None) - if ap is None: - return - ap.line(self.ctx, x, y) - - def arc(self, x, y, i, j): - super(GerberCairoContext, self).arc(x, y, i, j) - ap = self.apertures.get(self.aperture, None) - if ap is None: - return - ap.arc(self.ctx, x, y, i, j, self.direction) - - - def flash(self, x, y): - x, y = self.resolve(x, y) - ap = self.apertures.get(self.aperture, None) - if ap is None: - return - ap.flash(self.ctx, x, y) - self.move(x, y, resolve=False) - - def move(self, x, y, resolve=True): - super(GerberCairoContext, self).move(x, y, resolve) - if x is None: - x = self.x - if y is None: - y = self.y - if self.x is not None and self.y is not None: - self.ctx.move_to(x * SCALE, y * SCALE) + def _render_line(self, line, color): + start = map(mul, line.start, self.scale) + end = map(mul, line.end, self.scale) + self.ctx.set_source_rgb (*color) + self.ctx.set_line_width(line.width * SCALE) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() + + def _render_region(self, region, color): + points = [tuple(map(mul, point, self.scale)) for point in region.points] + self.ctx.set_source_rgb (*color) + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) + for point in points[1:]: + self.ctx.move_to(*point) + self.ctx.fill() + + def _render_circle(self, circle, color): + center = map(mul, circle.position, self.scale) + self.ctx.set_source_rgb (*color) + self.ctx.set_line_width(0) + self.ctx.arc(*center, radius=circle.radius * SCALE, angle1=0, angle2=2 * math.pi) + self.ctx.fill() + + def _render_rectangle(self, rectangle, color): + ll = map(mul, rectangle.lower_left, self.scale) + width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) + self.ctx.set_source_rgb (*color) + self.ctx.set_line_width(0) + self.ctx.rectangle(*ll,width=width, height=height) + self.ctx.fill() + + def _render_obround(self, obround, color): + self._render_circle(obround.subshapes['circle1'], color) + self._render_circle(obround.subshapes['circle2'], color) + self._render_rectangle(obround.subshapes['rectangle'], color) + def _render_drill(self, circle, color): + self._render_circle(circle, color) def dump(self, filename): self.surface.write_to_png(filename) \ No newline at end of file -- cgit From c08cdf84bceb43ef452e48396bfbe508f0bdd338 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 26 Oct 2014 22:40:55 -0400 Subject: removed dead code --- gerber/render/cairo_backend.py | 64 +++--------------------------------------- 1 file changed, 4 insertions(+), 60 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index f5e5aca..df513bb 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -23,70 +23,14 @@ import math SCALE = 300. -#class CairoCircle(Circle): -# def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): -# ctx.set_source_rgb (*color) -# ctx.set_line_width(self.diameter * SCALE) -# ctx.set_line_cap(cairo.LINE_CAP_ROUND) -# ctx.line_to(x * SCALE, y * SCALE) -# ctx.stroke() -# -# def arc(self, ctx, x, y, i, j, direction, color=(184/255., 115/255., 51/255.)): -# ctx_x, ctx_y = ctx.get_current_point() -# -# # Do the math -# center = ((x + i) * SCALE, (y + j) * SCALE) -# radius = math.sqrt(math.pow(ctx_x - center[0], 2) + math.pow(ctx_y - center[1], 2)) -# delta_x0 = (ctx_x - center[0]) -# delta_y0 = (ctx_y - center[1]) -# delta_x1 = (x * SCALE - center[0]) -# delta_y1 = (y * SCALE - center[1]) -# theta0 = math.atan2(delta_y0, delta_x0) -# theta1 = math.atan2(delta_y1, delta_x1) -# # Draw the arc -# ctx.set_source_rgb (*color) -# ctx.set_line_width(self.diameter * SCALE) -# ctx.set_line_cap(cairo.LINE_CAP_ROUND) -# if direction == 'clockwise': -# ctx.arc_negative(center[0], center[1], radius, theta0, theta1) -# else: -# ctx.arc(center[0], center[1], radius, theta0, theta1) -# ctx.stroke() -# -# def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): -# ctx.set_source_rgb (*color) -# ctx.set_line_width(0) -# ctx.arc(x * SCALE, y * SCALE, (self.diameter/2.) * SCALE, 0, 2 * math.pi) -# ctx.fill() -# -#class CairoRect(Rect): -# def line(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): -# ctx.set_source_rgb (*color) -# ctx.set_line_width(self.diameter * SCALE) -# ctx.set_line_cap(cairo.LINE_CAP_SQUARE) -# ctx.line_to(x * SCALE, y * SCALE) -# ctx.stroke() -# -# def flash(self, ctx, x, y, color=(184/255., 115/255., 51/255.)): -# xsize, ysize = self.size -# ctx.set_source_rgb (*color) -# ctx.set_line_width(0) -# x0 = SCALE * (x - (xsize / 2.)) -# y0 = SCALE * (y - (ysize / 2.)) -# -# ctx.rectangle(x0,y0,SCALE * xsize, SCALE * ysize) -# ctx.fill() -# - - class GerberCairoContext(GerberContext): def __init__(self, surface=None, size=(1000, 1000)): GerberContext.__init__(self) if surface is None: - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size[0], size[1]) else: - self.surface = surface + self.surface = surface self.ctx = cairo.Context(self.surface) self.size = size self.ctx.translate(0, self.size[1]) @@ -94,7 +38,7 @@ class GerberCairoContext(GerberContext): self.ctx.scale(1, -1) self.apertures = {} 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])) @@ -144,4 +88,4 @@ class GerberCairoContext(GerberContext): self._render_circle(circle, color) def dump(self, filename): - self.surface.write_to_png(filename) \ No newline at end of file + self.surface.write_to_png(filename) -- cgit From 95de179bb08157c3f6716b0645ec00794acc83e6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 27 Oct 2014 08:29:43 -0400 Subject: Fix rendering of 0-width lines (e.g. board outlines) in SVG and Cairo renderer --- gerber/render/cairo_backend.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index df513bb..1c69725 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -20,7 +20,7 @@ from operator import mul import cairocffi as cairo import math -SCALE = 300. +SCALE = 400. class GerberCairoContext(GerberContext): @@ -48,8 +48,9 @@ class GerberCairoContext(GerberContext): def _render_line(self, line, color): start = map(mul, line.start, self.scale) end = map(mul, line.end, self.scale) - self.ctx.set_source_rgb (*color) - self.ctx.set_line_width(line.width * SCALE) + width = line.width if line.width != 0 else 0.001 + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_line_width(width * SCALE) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) @@ -57,7 +58,7 @@ class GerberCairoContext(GerberContext): def _render_region(self, region, color): points = [tuple(map(mul, point, self.scale)) for point in region.points] - self.ctx.set_source_rgb (*color) + self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) for point in points[1:]: @@ -66,7 +67,7 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = map(mul, circle.position, self.scale) - self.ctx.set_source_rgb (*color) + self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_line_width(0) self.ctx.arc(*center, radius=circle.radius * SCALE, angle1=0, angle2=2 * math.pi) self.ctx.fill() @@ -74,7 +75,7 @@ class GerberCairoContext(GerberContext): def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) - self.ctx.set_source_rgb (*color) + self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_line_width(0) self.ctx.rectangle(*ll,width=width, height=height) self.ctx.fill() -- cgit From f5abd5b0bdc0b9f524456dc9216bd0f3732e82a0 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 28 Oct 2014 22:11:43 -0400 Subject: Add arc rendering and tests --- gerber/render/cairo_backend.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 1c69725..125a125 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -56,13 +56,31 @@ class GerberCairoContext(GerberContext): self.ctx.line_to(*end) self.ctx.stroke() + def _render_arc(self, arc, color): + center = map(mul, arc.center, self.scale) + start = map(mul, arc.start, self.scale) + end = map(mul, arc.end, self.scale) + radius = SCALE * arc.radius + angle1 = arc.start_angle + angle2 = arc.end_angle + width = arc.width if arc.width != 0 else 0.001 + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_line_width(width * SCALE) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) # You actually have to do this... + if arc.direction == 'counterclockwise': + self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.move_to(*end) # ...lame + def _render_region(self, region, color): points = [tuple(map(mul, point, self.scale)) for point in region.points] self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) for point in points[1:]: - self.ctx.move_to(*point) + self.ctx.line_to(*point) self.ctx.fill() def _render_circle(self, circle, color): -- cgit From 8f69c1dfa281b6486c8fce16c1d58acef70c7ae7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 12 Feb 2015 11:28:50 -0500 Subject: Update line primitive to take aperture parameter This fixes the exception referenced in #12. Still need to add rendering code for rectangle aperture lines and arcs. Rectangle strokes will be drawn as polygons by the rendering backends. --- gerber/render/cairo_backend.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 125a125..c1df87a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -20,6 +20,8 @@ from operator import mul import cairocffi as cairo import math +from ..primitives import * + SCALE = 400. @@ -48,13 +50,17 @@ class GerberCairoContext(GerberContext): def _render_line(self, line, color): start = map(mul, line.start, self.scale) end = map(mul, line.end, self.scale) - width = line.width if line.width != 0 else 0.001 - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_line_width(width * SCALE) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) - self.ctx.line_to(*end) - self.ctx.stroke() + if isinstance(line.aperture, Circle): + width = line.aperture.diameter if line.aperture.diameter != 0 else 0.001 + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_line_width(width * SCALE) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() + elif isinstance(line.aperture, rectangle): + # TODO: Render rectangle strokes as a polygon... + pass def _render_arc(self, arc, color): center = map(mul, arc.center, self.scale) -- cgit From 5e23d07bcb5103b4607c6ad591a2a547c97ee1f6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 13 Feb 2015 09:37:27 -0500 Subject: Fix rendering for line with rectangular aperture per #12. Still need to do the same for arcs. --- gerber/render/cairo_backend.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index c1df87a..999269b 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -26,7 +26,7 @@ SCALE = 400. class GerberCairoContext(GerberContext): - def __init__(self, surface=None, size=(1000, 1000)): + def __init__(self, surface=None, size=(10000, 10000)): GerberContext.__init__(self) if surface is None: self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, @@ -58,9 +58,14 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*start) self.ctx.line_to(*end) self.ctx.stroke() - elif isinstance(line.aperture, rectangle): - # TODO: Render rectangle strokes as a polygon... - pass + elif isinstance(line.aperture, Rectangle): + points = [tuple(map(mul, x, self.scale)) for x in line.vertices] + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) + for point in points[1:]: + self.ctx.line_to(*point) + self.ctx.fill() def _render_arc(self, arc, color): center = map(mul, arc.center, self.scale) -- cgit From bfe14841604b6be403e7123e8b6667b1f0aff6f6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 15 Feb 2015 03:29:47 -0500 Subject: Add cairo example code, and use example-generated image in readme --- gerber/render/cairo_backend.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 999269b..18d1ceb 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -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) -- cgit From d63bf0d68ae100c0413c7619f96d5d1c65da6c4e Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 15 Feb 2015 13:29:50 -0500 Subject: Fix cairo image size --- gerber/render/cairo_backend.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 18d1ceb..f79dfbe 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -44,7 +44,14 @@ class GerberCairoContext(GerberContext): def set_bounds(self, bounds): 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])) + width = SCALE * (xbounds[1] - xbounds[0]) + height = SCALE * (ybounds[1] - ybounds[0]) + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) + self.ctx = cairo.Context(self.surface) + self.ctx.translate(0, height) + self.scale = (SCALE,SCALE) + self.ctx.scale(1, -1) + self.ctx.rectangle(SCALE * xbounds[0], SCALE * ybounds[0], width, height) self.ctx.set_source_rgb(0,0,0) self.ctx.fill() self.background = True -- cgit From d830375c4c33e6a863e02c9f767c127841a070f7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 20 Feb 2015 10:07:26 -0500 Subject: Fix arc width per comment in #12 --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index f79dfbe..326f44e 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -83,7 +83,7 @@ class GerberCairoContext(GerberContext): radius = SCALE * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle - width = arc.width if arc.width != 0 else 0.001 + width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_line_width(width * SCALE) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) -- cgit From 68619d4d5a7beb38dc81d953b43bf4196ca1d3a6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 5 Mar 2015 22:42:42 -0500 Subject: Fix parsing for multiline ipc-d-356 records --- gerber/render/cairo_backend.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 326f44e..fa1aecc 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -104,7 +104,7 @@ class GerberCairoContext(GerberContext): self.ctx.fill() def _render_circle(self, circle, color): - center = map(mul, circle.position, self.scale) + center = tuple(map(mul, circle.position, self.scale)) self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_line_width(0) self.ctx.arc(*center, radius=circle.radius * SCALE, angle1=0, angle2=2 * math.pi) @@ -126,5 +126,15 @@ class GerberCairoContext(GerberContext): def _render_drill(self, circle, color): 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) + self.ctx.move_to(*[SCALE * (coord + 0.01) for coord in primitive.position]) + self.ctx.scale(1, -1) + self.ctx.show_text(primitive.net_name) + self.ctx.scale(1, -1) + def dump(self, filename): self.surface.write_to_png(filename) -- cgit From 5aaf18889c3cdc31ae61b9593bf5848bc57ec09a Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 9 Jul 2015 03:54:47 -0300 Subject: Initial patch to unify our render towards cairo This branch allows a pure cairo based render for both PNG and SVG. Cairo backend is mostly the same but with improved support for configurable scale, orientation and inverted color drawing. API is not yet final. --- gerber/render/cairo_backend.py | 87 ++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 37 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index fa1aecc..939863b 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -16,53 +16,44 @@ # limitations under the License. from .render import GerberContext -from operator import mul + import cairocffi as cairo + +from operator import mul import math +import tempfile from ..primitives import * -SCALE = 4000. - - class GerberCairoContext(GerberContext): - def __init__(self, surface=None, size=(10000, 10000)): + def __init__(self, scale=300): GerberContext.__init__(self) - if surface is None: - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, - size[0], size[1]) - else: - self.surface = surface + self.scale = (scale, scale) + self.surface = None + self.ctx = None + + def set_bounds(self, bounds): + 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.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) - self.size = size - self.ctx.translate(0, self.size[1]) - self.scale = (SCALE,SCALE) + self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.ctx.scale(1, -1) - self.apertures = {} - self.background = False - - def set_bounds(self, bounds): - if not self.background: - xbounds, ybounds = bounds - width = SCALE * (xbounds[1] - xbounds[0]) - height = SCALE * (ybounds[1] - ybounds[0]) - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) - self.ctx = cairo.Context(self.surface) - self.ctx.translate(0, height) - self.scale = (SCALE,SCALE) - self.ctx.scale(1, -1) - self.ctx.rectangle(SCALE * xbounds[0], SCALE * ybounds[0], width, height) - self.ctx.set_source_rgb(0,0,0) - self.ctx.fill() - self.background = True + self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) + # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) def _render_line(self, line, color): start = map(mul, line.start, self.scale) end = map(mul, line.end, self.scale) if isinstance(line.aperture, Circle): - width = line.aperture.diameter if line.aperture.diameter != 0 else 0.001 + width = line.aperture.diameter self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_line_width(width * SCALE) + self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) @@ -70,6 +61,7 @@ class GerberCairoContext(GerberContext): elif isinstance(line.aperture, Rectangle): points = [tuple(map(mul, x, self.scale)) for x in line.vertices] self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) for point in points[1:]: @@ -80,12 +72,13 @@ class GerberCairoContext(GerberContext): center = map(mul, arc.center, self.scale) start = map(mul, arc.start, self.scale) end = map(mul, arc.end, self.scale) - radius = SCALE * arc.radius + radius = self.scale * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_line_width(width * SCALE) + self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': @@ -97,6 +90,7 @@ class GerberCairoContext(GerberContext): def _render_region(self, region, color): points = [tuple(map(mul, point, self.scale)) for point in region.points] self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) for point in points[1:]: @@ -106,14 +100,16 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = tuple(map(mul, circle.position, self.scale)) self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * SCALE, angle1=0, angle2=2 * math.pi) + self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(*ll,width=width, height=height) self.ctx.fill() @@ -131,10 +127,27 @@ class GerberCairoContext(GerberContext): self.ctx.set_font_size(200) self._render_circle(Circle(primitive.position, 0.01), color) self.ctx.set_source_rgb(*color) - self.ctx.move_to(*[SCALE * (coord + 0.01) for coord in primitive.position]) + self.ctx.set_operator(cairo.OPERATOR_OVER if (primitive.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.move_to(*[self.scale[0] * (coord + 0.01) for coord in primitive.position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) + def _paint_inverted_layer(self): + self.ctx.set_source_rgba(*self.background_color) + self.ctx.set_operator(cairo.OPERATOR_OVER) + self.ctx.paint() + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + def dump(self, filename): - self.surface.write_to_png(filename) + is_svg = filename.lower().endswith(".svg") + + if is_svg: + self.surface.finish() + self.surface_buffer.flush() + + with open(filename, "w") as f: + f.write(open(self.surface_buffer.name, "r").read()) + f.flush() + else: + self.surface.write_to_png(filename) -- cgit From b3f6ec558ca35a19bd60440f2a114eb98c0a4263 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 9 Jul 2015 04:05:15 -0300 Subject: Fix arcs and ackground painting --- gerber/render/cairo_backend.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 939863b..16638f5 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -72,7 +72,7 @@ class GerberCairoContext(GerberContext): center = map(mul, arc.center, self.scale) start = map(mul, arc.start, self.scale) end = map(mul, arc.end, self.scale) - radius = self.scale * arc.radius + radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 @@ -139,6 +139,10 @@ class GerberCairoContext(GerberContext): self.ctx.paint() self.ctx.set_operator(cairo.OPERATOR_CLEAR) + def _paint_background(self): + self.ctx.set_source_rgba(*self.background_color) + self.ctx.paint() + def dump(self, filename): is_svg = filename.lower().endswith(".svg") -- cgit From 39726e3936c5fa5c50158727e8eb7f5d01cb1b49 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 22 Jul 2015 22:13:09 -0400 Subject: Fix multiple layer issue in cairo-unification branch (see #33) --- gerber/render/cairo_backend.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 16638f5..2791d76 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -31,20 +31,21 @@ class GerberCairoContext(GerberContext): self.scale = (scale, scale) self.surface = None self.ctx = None + self.bg = False def set_bounds(self, bounds): 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.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) - self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - self.ctx.scale(1, -1) - self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) + if self.surface is None: + 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) + self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + self.ctx.scale(1, -1) + self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) + # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) def _render_line(self, line, color): start = map(mul, line.start, self.scale) @@ -140,8 +141,10 @@ class GerberCairoContext(GerberContext): self.ctx.set_operator(cairo.OPERATOR_CLEAR) def _paint_background(self): - self.ctx.set_source_rgba(*self.background_color) - self.ctx.paint() + if not self.bg: + self.bg = True + self.ctx.set_source_rgba(*self.background_color) + self.ctx.paint() def dump(self, filename): is_svg = filename.lower().endswith(".svg") -- cgit From d4a870570855265b9b37f1609dd2bc9f49699bb6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 25 Jul 2015 09:48:58 -0400 Subject: Fix windows permission error per #33 the issue was trying to re-open the temporary file. it works on everything but windows. I've changed it to seek to the beginning and read from the file without re-opening, which should fix the issue. --- gerber/render/cairo_backend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 2791d76..0ae5d40 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -154,7 +154,9 @@ class GerberCairoContext(GerberContext): self.surface_buffer.flush() with open(filename, "w") as f: - f.write(open(self.surface_buffer.name, "r").read()) + self.surface_buffer.seek(0) + f.write(self.surface_buffer.read()) f.flush() + else: self.surface.write_to_png(filename) -- cgit From cb2fa34e881a389cf8a4bc98fd12be662ff687f8 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 9 Aug 2015 15:11:13 -0400 Subject: Add support for arcs in regions. This fixes the circular cutout issue described in #32. Regions were previously stored as a collection of points, now they are stored as a collection of line and arc primitives. --- gerber/render/cairo_backend.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0ae5d40..a97e552 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -89,13 +89,25 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): - points = [tuple(map(mul, point, self.scale)) for point in region.points] self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.move_to(*points[0]) - for point in points[1:]: - self.ctx.line_to(*point) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) + for p in region.primitives: + if isinstance(p, Line): + self.ctx.line_to(*tuple(map(mul, p.end, self.scale))) + else: + center = map(mul, p.center, self.scale) + start = map(mul, p.start, self.scale) + end = map(mul, p.end, self.scale) + radius = self.scale[0] * p.radius + angle1 = p.start_angle + angle2 = p.end_angle + if p.direction == 'counterclockwise': + self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) self.ctx.fill() def _render_circle(self, circle, color): -- cgit From dd63b169f177389602e17bc6ced53bd0f1ba0de3 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 10 Oct 2015 16:51:21 -0400 Subject: Allow files to be read from strings per #37 Adds a loads() method to the top level module which generates a GerberFile or ExcellonFile from a string --- gerber/render/cairo_backend.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index a97e552..345f331 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -172,3 +172,9 @@ class GerberCairoContext(GerberContext): else: self.surface.write_to_png(filename) + + def dump_svg_str(self): + self.surface.finish() + self.surface_buffer.flush() + return self.surface_buffer.read() + \ No newline at end of file -- cgit From d5f382f4b413d73a96613dd86aa207bb9e665b0d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Mon, 23 Nov 2015 16:17:31 +0800 Subject: Render with cairo instead of cairocffi - I would like to make it use either, but for now, using the one that works with wxpython --- gerber/render/cairo_backend.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 345f331..e4a5eff 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -17,7 +17,7 @@ from .render import GerberContext -import cairocffi as cairo +import cairo from operator import mul import math @@ -52,7 +52,7 @@ class GerberCairoContext(GerberContext): end = map(mul, line.end, self.scale) if isinstance(line.aperture, Circle): width = line.aperture.diameter - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -61,7 +61,7 @@ class GerberCairoContext(GerberContext): self.ctx.stroke() elif isinstance(line.aperture, Rectangle): points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) @@ -77,7 +77,7 @@ class GerberCairoContext(GerberContext): angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -89,7 +89,8 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + #self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -112,7 +113,7 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = tuple(map(mul, circle.position, self.scale)) - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) @@ -121,7 +122,7 @@ class GerberCairoContext(GerberContext): def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) - self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(*ll,width=width, height=height) -- cgit From 8eede187f3f644c4f8a0de0dc5825dc4c00c7b8f Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Mon, 23 Nov 2015 22:22:30 +0800 Subject: More fixes to work with cairo --- gerber/render/cairo_backend.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index e4a5eff..81c5ce4 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -61,7 +61,7 @@ class GerberCairoContext(GerberContext): self.ctx.stroke() elif isinstance(line.aperture, Rectangle): points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) @@ -83,14 +83,13 @@ class GerberCairoContext(GerberContext): self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - #self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) @@ -106,9 +105,9 @@ class GerberCairoContext(GerberContext): angle1 = p.start_angle angle2 = p.end_angle if p.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) self.ctx.fill() def _render_circle(self, circle, color): @@ -116,7 +115,7 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.arc(center[0], center[1], circle.radius * self.scale[0], 0, 2 * math.pi) self.ctx.fill() def _render_rectangle(self, rectangle, color): @@ -148,7 +147,7 @@ class GerberCairoContext(GerberContext): self.ctx.scale(1, -1) def _paint_inverted_layer(self): - self.ctx.set_source_rgba(*self.background_color) + self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2]) self.ctx.set_operator(cairo.OPERATOR_OVER) self.ctx.paint() self.ctx.set_operator(cairo.OPERATOR_CLEAR) @@ -156,7 +155,7 @@ class GerberCairoContext(GerberContext): def _paint_background(self): if not self.bg: self.bg = True - self.ctx.set_source_rgba(*self.background_color) + self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2]) self.ctx.paint() def dump(self, filename): -- cgit From d69f50e0f62570a4c327cb8fe4f886f439196010 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 2 Dec 2015 12:44:30 +0800 Subject: Make the hit accessible from the drawable Hit, fix crash with cario drawing rect --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 81c5ce4..4a0724f 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -124,7 +124,7 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.rectangle(*ll,width=width, height=height) + self.ctx.rectangle(ll[0], ll[1], width, height) self.ctx.fill() def _render_obround(self, obround, color): -- cgit From 206f4c57ab66f8a6753015340315991b40178c9b Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 16 Dec 2015 18:59:25 +0800 Subject: Fix drawing arcs. Dont crash for arcs with rectangular apertures. Fix crash with board size of zero for only one drill --- gerber/render/cairo_backend.py | 1 + 1 file changed, 1 insertion(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 4a0724f..4d71199 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -87,6 +87,7 @@ class GerberCairoContext(GerberContext): else: self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) self.ctx.move_to(*end) # ...lame + self.ctx.stroke() def _render_region(self, region, color): self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) -- cgit From 1cb269131bc52f0b1a1e69cef0466f2d994d52a8 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 19 Dec 2015 21:54:29 -0500 Subject: Allow negative render of soldermask per #50 Update example code and rendering to show change --- gerber/render/cairo_backend.py | 121 ++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 37 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 345f331..6aaffd4 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -25,6 +25,7 @@ import tempfile from ..primitives import * + class GerberCairoContext(GerberContext): def __init__(self, scale=300): GerberContext.__init__(self) @@ -32,42 +33,72 @@ class GerberCairoContext(GerberContext): self.surface = None self.ctx = None self.bg = False - + self.mask = None + self.mask_ctx = None + self.origin_in_pixels = None + self.size_in_pixels = None + def set_bounds(self, bounds): 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.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) self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.ctx.scale(1, -1) self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) + + self.mask_buffer = tempfile.NamedTemporaryFile() + self.mask = cairo.SVGSurface(self.mask_buffer, size_in_pixels[0], size_in_pixels[1]) + self.mask_ctx = cairo.Context(self.mask) + 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]) def _render_line(self, line, color): start = map(mul, line.start, self.scale) end = map(mul, line.end, self.scale) - if isinstance(line.aperture, Circle): - width = line.aperture.diameter + if not self.invert: self.ctx.set_source_rgba(*color, alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(width * self.scale[0]) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) - self.ctx.line_to(*end) - self.ctx.stroke() - elif isinstance(line.aperture, Rectangle): - points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.move_to(*points[0]) - for point in points[1:]: - self.ctx.line_to(*point) - self.ctx.fill() + if isinstance(line.aperture, Circle): + width = line.aperture.diameter + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() + elif isinstance(line.aperture, Rectangle): + points = [tuple(map(mul, x, self.scale)) for x in line.vertices] + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) + for point in points[1:]: + self.ctx.line_to(*point) + self.ctx.fill() + else: + self.mask_ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.mask_ctx.set_operator(cairo.OPERATOR_CLEAR) + if isinstance(line.aperture, Circle): + width = line.aperture.diameter + self.mask_ctx.set_line_width(0) + self.mask_ctx.set_line_width(width * self.scale[0]) + self.mask_ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.mask_ctx.move_to(*start) + self.mask_ctx.line_to(*end) + self.mask_ctx.stroke() + elif isinstance(line.aperture, Rectangle): + points = [tuple(map(mul, x, self.scale)) for x in line.vertices] + self.mask_ctx.set_line_width(0) + self.mask_ctx.move_to(*points[0]) + for point in points[1:]: + self.mask_ctx.line_to(*point) + self.mask_ctx.fill() def _render_arc(self, arc, color): center = map(mul, arc.center, self.scale) @@ -78,7 +109,7 @@ class GerberCairoContext(GerberContext): angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... @@ -112,20 +143,34 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = tuple(map(mul, circle.position, self.scale)) - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - self.ctx.fill() + if not self.invert: + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() + else: + self.mask_ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.mask_ctx.set_operator(cairo.OPERATOR_CLEAR) + self.mask_ctx.set_line_width(0) + self.mask_ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.mask_ctx.fill() def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.rectangle(*ll,width=width, height=height) - self.ctx.fill() + if not self.invert: + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.rectangle(*ll, width=width, height=height) + self.ctx.fill() + else: + self.mask_ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.mask_ctx.set_operator(cairo.OPERATOR_CLEAR) + self.mask_ctx.set_line_width(0) + self.mask_ctx.rectangle(*ll, width=width, height=height) + self.mask_ctx.fill() def _render_obround(self, obround, color): self._render_circle(obround.subshapes['circle1'], color) @@ -140,17 +185,23 @@ class GerberCairoContext(GerberContext): self.ctx.set_font_size(200) self._render_circle(Circle(primitive.position, 0.01), color) self.ctx.set_source_rgb(*color) - self.ctx.set_operator(cairo.OPERATOR_OVER if (primitive.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_OVER if (primitive.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.move_to(*[self.scale[0] * (coord + 0.01) for coord in primitive.position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) def _paint_inverted_layer(self): - self.ctx.set_source_rgba(*self.background_color) + self.mask_ctx.set_operator(cairo.OPERATOR_OVER) + self.mask_ctx.set_source_rgba(*self.color, alpha=self.alpha) + self.mask_ctx.paint() + + 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])) + self.ctx.set_source(ptn) self.ctx.paint() - self.ctx.set_operator(cairo.OPERATOR_CLEAR) def _paint_background(self): if not self.bg: @@ -160,21 +211,17 @@ class GerberCairoContext(GerberContext): 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_svg_str(self): self.surface.finish() self.surface_buffer.flush() return self.surface_buffer.read() - \ No newline at end of file -- cgit From 60c5906b29b6427a5190d31c7bb5511e0bf78fd4 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 20 Dec 2015 15:24:20 -0500 Subject: Clean up negative render code --- gerber/render/cairo_backend.py | 145 ++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 76 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 6aaffd4..c8f94ff 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -44,18 +44,14 @@ class GerberCairoContext(GerberContext): 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.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) self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.ctx.scale(1, -1) self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - - self.mask_buffer = tempfile.NamedTemporaryFile() - self.mask = cairo.SVGSurface(self.mask_buffer, size_in_pixels[0], size_in_pixels[1]) + self.mask = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) self.mask_ctx = cairo.Context(self.mask) self.mask_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.mask_ctx.scale(1, -1) @@ -65,40 +61,27 @@ class GerberCairoContext(GerberContext): start = map(mul, line.start, self.scale) end = map(mul, line.end, self.scale) if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - if isinstance(line.aperture, Circle): - width = line.aperture.diameter - self.ctx.set_line_width(width * self.scale[0]) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) - self.ctx.line_to(*end) - self.ctx.stroke() - elif isinstance(line.aperture, Rectangle): - points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.ctx.set_line_width(0) - self.ctx.move_to(*points[0]) - for point in points[1:]: - self.ctx.line_to(*point) - self.ctx.fill() + ctx = self.ctx + ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - self.mask_ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.mask_ctx.set_operator(cairo.OPERATOR_CLEAR) - if isinstance(line.aperture, Circle): - width = line.aperture.diameter - self.mask_ctx.set_line_width(0) - self.mask_ctx.set_line_width(width * self.scale[0]) - self.mask_ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.mask_ctx.move_to(*start) - self.mask_ctx.line_to(*end) - self.mask_ctx.stroke() - elif isinstance(line.aperture, Rectangle): - points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - self.mask_ctx.set_line_width(0) - self.mask_ctx.move_to(*points[0]) - for point in points[1:]: - self.mask_ctx.line_to(*point) - self.mask_ctx.fill() + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + if isinstance(line.aperture, Circle): + width = line.aperture.diameter + ctx.set_line_width(width * self.scale[0]) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) + ctx.move_to(*start) + ctx.line_to(*end) + ctx.stroke() + elif isinstance(line.aperture, Rectangle): + points = [tuple(map(mul, x, self.scale)) for x in line.vertices] + ctx.set_line_width(0) + ctx.move_to(*points[0]) + for point in points[1:]: + ctx.line_to(*point) + ctx.fill() def _render_arc(self, arc, color): center = map(mul, arc.center, self.scale) @@ -108,26 +91,38 @@ class GerberCairoContext(GerberContext): angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(width * self.scale[0]) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) # You actually have to do this... + if not self.invert: + ctx = self.ctx + ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + else: + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.set_line_width(width * self.scale[0]) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) + ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) else: - self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) - self.ctx.move_to(*end) # ...lame + ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + ctx.move_to(*end) # ...lame def _render_region(self, region, color): - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) + if not self.invert: + ctx = self.ctx + ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + else: + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.set_line_width(0) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) + ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) for p in region.primitives: if isinstance(p, Line): - self.ctx.line_to(*tuple(map(mul, p.end, self.scale))) + ctx.line_to(*tuple(map(mul, p.end, self.scale))) else: center = map(mul, p.center, self.scale) start = map(mul, p.start, self.scale) @@ -136,41 +131,39 @@ class GerberCairoContext(GerberContext): angle1 = p.start_angle angle2 = p.end_angle if p.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) else: - self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) - self.ctx.fill() + ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) + ctx.fill() def _render_circle(self, circle, color): center = tuple(map(mul, circle.position, self.scale)) if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - self.ctx.fill() + ctx = self.ctx + ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - self.mask_ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.mask_ctx.set_operator(cairo.OPERATOR_CLEAR) - self.mask_ctx.set_line_width(0) - self.mask_ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - self.mask_ctx.fill() + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.set_line_width(0) + ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.rectangle(*ll, width=width, height=height) - self.ctx.fill() + ctx = self.ctx + ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if rectangle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - self.mask_ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.mask_ctx.set_operator(cairo.OPERATOR_CLEAR) - self.mask_ctx.set_line_width(0) - self.mask_ctx.rectangle(*ll, width=width, height=height) - self.mask_ctx.fill() + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.set_line_width(0) + ctx.rectangle(*ll, width=width, height=height) + ctx.fill() def _render_obround(self, obround, color): self._render_circle(obround.subshapes['circle1'], color) @@ -185,7 +178,7 @@ class GerberCairoContext(GerberContext): self.ctx.set_font_size(200) self._render_circle(Circle(primitive.position, 0.01), color) self.ctx.set_source_rgb(*color) - self.ctx.set_operator(cairo.OPERATOR_OVER if (primitive.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + 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.scale(1, -1) self.ctx.show_text(primitive.net_name) -- cgit From af5541ac93b222c05229ee05c9def8dbae5f6e25 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 20 Dec 2015 23:54:20 -0500 Subject: Allow renderer to write to memory per #38 Some updates to rendering colors/themes --- gerber/render/cairo_backend.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index c8f94ff..8283ae0 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -15,16 +15,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .render import GerberContext import cairocffi as cairo - from operator import mul import math import tempfile +from .render import GerberContext from ..primitives import * +try: + from cStringIO import StringIO +except(ImportError): + from io import StringIO + class GerberCairoContext(GerberContext): def __init__(self, scale=300): @@ -184,7 +188,7 @@ class GerberCairoContext(GerberContext): self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) - def _paint_inverted_layer(self): + def _clear_mask(self): self.mask_ctx.set_operator(cairo.OPERATOR_OVER) self.mask_ctx.set_source_rgba(*self.color, alpha=self.alpha) self.mask_ctx.paint() @@ -214,7 +218,16 @@ class GerberCairoContext(GerberContext): 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() -- cgit From 6f876edd09d9b81649691e529f85653f14b8fd1c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 22 Dec 2015 02:45:48 -0500 Subject: Add PCB interface this incorporates some of @chintal's layers.py changes PCB.from_directory() simplifies loading of multiple gerbers the PCB() class should be pretty helpful going forward... the context classes could use some cleaning up, although I'd like to wait until the freecad stuff gets merged, that way we can try to refactor the context base to support more use cases --- gerber/render/cairo_backend.py | 62 ++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 15 deletions(-) (limited to 'gerber/render/cairo_backend.py') 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): -- cgit From 5430fa6738b74f324c47c947477dd5b779db5d1c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 22 Dec 2015 10:18:51 -0500 Subject: Python3 fix --- gerber/render/cairo_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 7acf29a..c3e9ac2 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, div +from operator import mul import math import tempfile @@ -45,7 +45,7 @@ class GerberCairoContext(GerberContext): 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) + 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: -- cgit From 4a815bf25ddd1d378ec6ad5af008e5bbcd362b51 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 14:05:00 +0800 Subject: First time any macro renders --- gerber/render/cairo_backend.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 4d71199..3ee38ae 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -122,11 +122,27 @@ class GerberCairoContext(GerberContext): def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) + + if rectangle.rotation != 0: + self.ctx.save() + + center = map(mul, rectangle.position, self.scale) + matrix = cairo.Matrix() + matrix.translate(center[0], center[1]) + # For drawing, we already handles the translation + ll[0] = ll[0] - center[0] + ll[1] = ll[1] - center[1] + matrix.rotate(rectangle.rotation) + self.ctx.transform(matrix) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(ll[0], ll[1], width, height) self.ctx.fill() + + if rectangle.rotation != 0: + self.ctx.restore() def _render_obround(self, obround, color): self._render_circle(obround.subshapes['circle1'], color) @@ -135,6 +151,10 @@ class GerberCairoContext(GerberContext): def _render_drill(self, circle, color): self._render_circle(circle, color) + + def _render_amgroup(self, amgroup, color): + for primitive in amgroup.primitives: + self.render(primitive) def _render_test_record(self, primitive, color): self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) -- cgit From f61eee807f87c329f6f88645ecdb48f01b887c52 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 18:44:07 +0800 Subject: Render polygon flashes --- gerber/render/cairo_backend.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 3ee38ae..68e9e98 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -148,6 +148,22 @@ class GerberCairoContext(GerberContext): self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) + + def _render_polygon(self, polygon, color): + vertices = polygon.vertices + + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (polygon.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + + # Start from before the end so it is easy to iterate and make sure it is closed + self.ctx.move_to(*map(mul, vertices[-1], self.scale)) + for v in vertices: + self.ctx.line_to(*map(mul, v, self.scale)) + + self.ctx.fill() + def _render_drill(self, circle, color): self._render_circle(circle, color) -- cgit From 6a005436b475e3517fd6a583473b60e601bcc661 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 1 Jan 2016 12:25:38 -0500 Subject: Refactor a little pulled all rendering stuff out of the pcb/layer objects --- gerber/render/cairo_backend.py | 96 ++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 45 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index c3e9ac2..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: @@ -41,6 +42,15 @@ class GerberCairoContext(GerberContext): self.mask_ctx = None self.origin_in_inch = None self.size_in_inch = None + self._xform_matrix = None + + @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]) @@ -60,34 +70,56 @@ 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): + 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) + self._render_layer(layer, theme) 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) + 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) - @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 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): - self.color = layer.settings.color - self.alpha = layer.settings.alpha - self.invert = layer.settings.invert - if layer.settings.mirror: + 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 p in layer.primitives: - self.render(p) + for prim in layer.primitives: + self.render(prim) if self.invert: self._render_mask() @@ -209,10 +241,11 @@ class GerberCairoContext(GerberContext): def _render_test_record(self, primitive, 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_rgb(*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.015) for coord in position]) self.ctx.scale(1, -1) @@ -227,8 +260,7 @@ 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() @@ -237,29 +269,3 @@ class GerberCairoContext(GerberContext): self.bg = True 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() -- cgit From 83ae0670d11b5f5ef8ba3a6c362b7129a9e31ab3 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Fri, 8 Jan 2016 00:19:47 +0800 Subject: More stability fixes for poorly constructed files --- gerber/render/cairo_backend.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 68e9e98..fbc4271 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -76,7 +76,10 @@ class GerberCairoContext(GerberContext): radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle - width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 + if isinstance(arc.aperture, Circle): + width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 + else: + width = max(arc.aperture.width, arc.aperture.height, 0.001) self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) @@ -163,7 +166,6 @@ class GerberCairoContext(GerberContext): self.ctx.line_to(*map(mul, v, self.scale)) self.ctx.fill() - def _render_drill(self, circle, color): self._render_circle(circle, color) -- cgit From 5476da8aa3f4ee424f56f4f2491e7af1c4b7b758 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Fix a bunch of rendering bugs. - 'clear' polarity primitives no longer erase background - Added aperture macro support for polygons - Added aperture macro rendring support - Renderer now creates a new surface for each layer and merges them instead of working directly on a single surface - Updated examples accordingly --- gerber/render/cairo_backend.py | 255 +++++++++++++++++++++++------------------ 1 file changed, 141 insertions(+), 114 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 4e71e75..cc2722a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -17,8 +17,6 @@ import cairocffi as cairo -from operator import mul -import math import tempfile from .render import GerberContext, RenderSettings @@ -32,11 +30,14 @@ except(ImportError): class GerberCairoContext(GerberContext): + def __init__(self, scale=300): - GerberContext.__init__(self) + super(GerberCairoContext, self).__init__() self.scale = (scale, scale) self.surface = None self.ctx = None + self.active_layer = None + self.output_ctx = None self.bg = False self.mask = None self.mask_ctx = None @@ -46,37 +47,40 @@ class GerberCairoContext(GerberContext): @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) + return (self.scale_point(self.origin_in_inch) + 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) + return (self.scale_point(self.size_in_inch) + 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 = tuple(map(mul, size_in_inch, self.scale)) + size_in_inch = (abs(bounds[0][1] - bounds[0][0]), + abs(bounds[1][1] - bounds[1][0])) + size_in_pixels = self.scale_point(size_in_inch) 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) - self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - self.ctx.scale(1, -1) - self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - self.mask = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) - self.mask_ctx = cairo.Context(self.mask) - 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]) + self.surface = cairo.SVGSurface( + self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) + self.output_ctx = cairo.Context(self.surface) + self.output_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + self.output_ctx.scale(1, -1) + self.output_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) @@ -114,158 +118,181 @@ class GerberCairoContext(GerberContext): self.color = settings.color self.alpha = settings.alpha self.invert = settings.invert + + # Get a new clean layer to render on + self._new_render_layer() 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() + # Add layer to image + self._flatten() def _render_line(self, line, color): - start = map(mul, line.start, self.scale) - end = map(mul, line.end, self.scale) + start = [pos * scale for pos, scale in zip(line.start, self.scale)] + end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if line.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): width = line.aperture.diameter - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*start) - ctx.line_to(*end) - ctx.stroke() + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() elif isinstance(line.aperture, Rectangle): - points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - ctx.set_line_width(0) - ctx.move_to(*points[0]) + points = [self.scale_point(x) for x in line.vertices] + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) for point in points[1:]: - ctx.line_to(*point) - ctx.fill() + self.ctx.line_to(*point) + self.ctx.fill() def _render_arc(self, arc, color): - center = map(mul, arc.center, self.scale) - start = map(mul, arc.start, self.scale) - end = map(mul, arc.end, self.scale) + center = self.scale_point(arc.center) + start = self.scale_point(arc.start) + end = self.scale_point(arc.end) radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if arc.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*start) # You actually have to do this... + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) else: - ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) - ctx.move_to(*end) # ...lame + self.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if region.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(0) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) - for p in region.primitives: - if isinstance(p, Line): - ctx.line_to(*tuple(map(mul, p.end, self.scale))) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*self.scale_point(region.primitives[0].start)) + for prim in region.primitives: + if isinstance(prim, Line): + self.ctx.line_to(*self.scale_point(prim.end)) else: - center = map(mul, p.center, self.scale) - start = map(mul, p.start, self.scale) - end = map(mul, p.end, self.scale) - radius = self.scale[0] * p.radius - angle1 = p.start_angle - angle2 = p.end_angle - if p.direction == 'counterclockwise': - ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + center = self.scale_point(prim.center) + radius = self.scale[0] * prim.radius + angle1 = prim.start_angle + angle2 = prim.end_angle + if prim.direction == 'counterclockwise': + self.ctx.arc(*center, radius=radius, + angle1=angle1, angle2=angle2) else: - ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) - ctx.fill() + self.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + self.ctx.fill() def _render_circle(self, circle, color): - center = tuple(map(mul, circle.position, self.scale)) + center = self.scale_point(circle.position) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator( + cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(0) - ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(*center, radius=circle.radius * + self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() def _render_rectangle(self, rectangle, color): - ll = map(mul, rectangle.lower_left, self.scale) - width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) + lower_left = self.scale_point(rectangle.lower_left) + width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) + if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if rectangle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator( + cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(0) - ctx.rectangle(*ll, width=width, height=height) - ctx.fill() + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.rectangle(*lower_left, width=width, height=height) + self.ctx.fill() def _render_obround(self, obround, color): self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) - def _render_drill(self, circle, color): + def _render_drill(self, circle, color=None): + color = color if color is not None else self.drill_color self._render_circle(circle, color) def _render_test_record(self, primitive, color): - position = tuple(map(add, primitive.position, self.origin_in_inch)) + position = [pos + origin for pos, origin in zip(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.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.015) for coord in position]) + 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.015) + for coord in position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) - def _clear_mask(self): - self.mask_ctx.set_operator(cairo.OPERATOR_OVER) - self.mask_ctx.set_source_rgba(*self.color, alpha=self.alpha) - self.mask_ctx.paint() + def _new_render_layer(self, color=None): + size_in_pixels = self.scale_point(self.size_in_inch) + layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) + ctx = cairo.Context(layer) + ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + ctx.scale(1, -1) + ctx.translate(-(self.origin_in_inch[0] * self.scale[0]), + (-self.origin_in_inch[1] * self.scale[0]) + - size_in_pixels[1]) + if self.invert: + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgba(*self.color, alpha=self.alpha) + ctx.paint() + self.ctx = ctx + self.active_layer = layer - def _render_mask(self): - self.ctx.set_operator(cairo.OPERATOR_OVER) - ptn = cairo.SurfacePattern(self.mask) + def _flatten(self): + self.output_ctx.set_operator(cairo.OPERATOR_OVER) + ptn = cairo.SurfacePattern(self.active_layer) ptn.set_matrix(self._xform_matrix) - self.ctx.set_source(ptn) - self.ctx.paint() + self.output_ctx.set_source(ptn) + self.output_ctx.paint() + self.ctx = None + self.active_layer = None def _paint_background(self, force=False): if (not self.bg) or force: self.bg = True - self.ctx.set_source_rgba(*self.background_color, alpha=1.0) - self.ctx.paint() + self.output_ctx.set_operator(cairo.OPERATOR_OVER) + self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0) + self.output_ctx.paint() + + def scale_point(self, point): + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) -- cgit From 66a0d09e72b078da5820820aa5c6a2a7d7430507 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 04:39:55 -0500 Subject: Add support for mirrored rendering - The default theme now renders the bottom layers mirrored. - see https://github.com/curtacircuitos/pcb-tools/blob/master/examples/pcb_bottom.png for an example. --- gerber/render/cairo_backend.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index cc2722a..2370eb9 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -18,6 +18,7 @@ import cairocffi as cairo import tempfile +import copy from .render import GerberContext, RenderSettings from .theme import THEMES @@ -37,6 +38,7 @@ class GerberCairoContext(GerberContext): self.surface = None self.ctx = None self.active_layer = None + self.active_matrix = None self.output_ctx = None self.bg = False self.mask = None @@ -120,9 +122,7 @@ class GerberCairoContext(GerberContext): self.invert = settings.invert # Get a new clean layer to render on - self._new_render_layer() - if settings.mirror: - raise Warning('mirrored layers aren\'t supported yet...') + self._new_render_layer(mirror=settings.mirror) for prim in layer.primitives: self.render(prim) # Add layer to image @@ -262,30 +262,35 @@ class GerberCairoContext(GerberContext): self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) - def _new_render_layer(self, color=None): + def _new_render_layer(self, color=None, mirror=False): size_in_pixels = self.scale_point(self.size_in_inch) layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) ctx = cairo.Context(layer) ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) ctx.scale(1, -1) ctx.translate(-(self.origin_in_inch[0] * self.scale[0]), - (-self.origin_in_inch[1] * self.scale[0]) - - size_in_pixels[1]) + (-self.origin_in_inch[1] * self.scale[0]) - size_in_pixels[1]) if self.invert: ctx.set_operator(cairo.OPERATOR_OVER) ctx.set_source_rgba(*self.color, alpha=self.alpha) ctx.paint() + matrix = copy.copy(self._xform_matrix) + if mirror: + matrix.xx = -1.0 + matrix.x0 = self.origin_in_pixels[0] + self.size_in_pixels[0] self.ctx = ctx self.active_layer = layer + self.active_matrix = matrix def _flatten(self): self.output_ctx.set_operator(cairo.OPERATOR_OVER) ptn = cairo.SurfacePattern(self.active_layer) - ptn.set_matrix(self._xform_matrix) + ptn.set_matrix(self.active_matrix) self.output_ctx.set_source(ptn) self.output_ctx.paint() self.ctx = None self.active_layer = None + self.active_matrix = None def _paint_background(self, force=False): if (not self.bg) or force: -- cgit From 5df38c014fd09792995b2b12b1982c535c962c9a Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 28 Jan 2016 12:19:03 -0500 Subject: Cleanup, rendering fixes. fixed rendering of tented vias fixed rendering of semi-transparent layers fixed file type detection issues added some examples --- gerber/render/cairo_backend.py | 172 +++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 82 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 2370eb9..df4fcf1 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -17,6 +17,7 @@ import cairocffi as cairo +import os import tempfile import copy @@ -36,16 +37,16 @@ class GerberCairoContext(GerberContext): super(GerberCairoContext, self).__init__() self.scale = (scale, scale) self.surface = None + self.surface_buffer = None self.ctx = None self.active_layer = None self.active_matrix = None self.output_ctx = None - self.bg = False - self.mask = None - self.mask_ctx = None + self.has_bg = False self.origin_in_inch = None self.size_in_inch = None self._xform_matrix = None + self._render_count = 0 @property def origin_in_pixels(self): @@ -66,10 +67,8 @@ class GerberCairoContext(GerberContext): 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.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) self.output_ctx = cairo.Context(self.surface) - self.output_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.output_ctx.scale(1, -1) self.output_ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1] * self.scale[0]) - size_in_pixels[1]) @@ -77,20 +76,44 @@ class GerberCairoContext(GerberContext): 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']): + def render_layer(self, layer, filename=None, settings=None, bgsettings=None, + verbose=False): + if settings is None: + settings = THEMES['default'].get(layer.layer_class, RenderSettings()) + if bgsettings is None: + bgsettings = THEMES['default'].get('background', RenderSettings()) + + if self._render_count == 0: + if verbose: + print('[Render]: Rendering Background.') + self.clear() + self.set_bounds(layer.bounds) + self._paint_background(bgsettings) + if verbose: + print('[Render]: Rendering {} Layer.'.format(layer.layer_class)) + self._render_count += 1 + self._render_layer(layer, settings) + if filename is not None: + self.dump(filename, verbose) + + def render_layers(self, layers, filename, theme=THEMES['default'], + verbose=False): """ Render a set of layers """ - self.set_bounds(layers[0].bounds, True) - self._paint_background(True) - + self.clear() + bgsettings = theme['background'] for layer in layers: - self._render_layer(layer, theme) - self.dump(filename) + settings = theme.get(layer.layer_class, RenderSettings()) + self.render_layer(layer, settings=settings, bgsettings=bgsettings, + verbose=verbose) + self.dump(filename, verbose) - def dump(self, filename): + def dump(self, filename, verbose=False): """ Save image as `filename` """ - is_svg = filename.lower().endswith(".svg") + is_svg = os.path.splitext(filename.lower())[1] == '.svg' + if verbose: + print('[Render]: Writing image to {}'.format(filename)) if is_svg: self.surface.finish() self.surface_buffer.flush() @@ -115,30 +138,33 @@ class GerberCairoContext(GerberContext): 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 + def clear(self): + self.surface = None + self.output_ctx = None + self.has_bg = False + self.origin_in_inch = None + self.size_in_inch = None + self._xform_matrix = None + self._render_count = 0 + if hasattr(self.surface_buffer, 'close'): + self.surface_buffer.close() + self.surface_buffer = None + def _render_layer(self, layer, settings): + self.invert = settings.invert # Get a new clean layer to render on self._new_render_layer(mirror=settings.mirror) for prim in layer.primitives: self.render(prim) # Add layer to image - self._flatten() + self._paint(settings.color, settings.alpha) def _render_line(self, line, color): start = [pos * scale for pos, scale in zip(line.start, self.scale)] end = [pos * scale for pos, scale in zip(line.end, self.scale)] - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if line.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_SOURCE + if line.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): width = line.aperture.diameter self.ctx.set_line_width(width * self.scale[0]) @@ -162,14 +188,9 @@ class GerberCairoContext(GerberContext): angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if arc.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_SOURCE + if arc.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... @@ -181,14 +202,9 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if region.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_SOURCE + if region.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*self.scale_point(region.primitives[0].start)) @@ -210,29 +226,22 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = self.scale_point(circle.position) - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator( - cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_SOURCE + if circle.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * - self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.arc(*center, radius=(circle.radius * self.scale[0]), angle1=0, + angle2=(2 * math.pi)) self.ctx.fill() def _render_rectangle(self, rectangle, color): lower_left = self.scale_point(rectangle.lower_left) - width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) - - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator( - cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + width, height = tuple([abs(coord) for coord in + self.scale_point((rectangle.width, + rectangle.height))]) + self.ctx.set_operator(cairo.OPERATOR_SOURCE + if rectangle.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(*lower_left, width=width, height=height) self.ctx.fill() @@ -247,34 +256,31 @@ class GerberCairoContext(GerberContext): self._render_circle(circle, color) def _render_test_record(self, primitive, color): - position = [pos + origin for pos, origin in zip(primitive.position, self.origin_in_inch)] - self.ctx.set_operator(cairo.OPERATOR_OVER) + position = [pos + origin for pos, origin in + zip(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_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.015) - for coord in position]) + self.ctx.set_operator(cairo.OPERATOR_SOURCE + if primitive.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) + 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) def _new_render_layer(self, color=None, mirror=False): size_in_pixels = self.scale_point(self.size_in_inch) + matrix = copy.copy(self._xform_matrix) layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) ctx = cairo.Context(layer) - ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) ctx.scale(1, -1) ctx.translate(-(self.origin_in_inch[0] * self.scale[0]), - (-self.origin_in_inch[1] * self.scale[0]) - size_in_pixels[1]) + (-self.origin_in_inch[1] * self.scale[0]) - size_in_pixels[1]) if self.invert: ctx.set_operator(cairo.OPERATOR_OVER) - ctx.set_source_rgba(*self.color, alpha=self.alpha) ctx.paint() - matrix = copy.copy(self._xform_matrix) if mirror: matrix.xx = -1.0 matrix.x0 = self.origin_in_pixels[0] + self.size_in_pixels[0] @@ -282,21 +288,23 @@ class GerberCairoContext(GerberContext): self.active_layer = layer self.active_matrix = matrix - def _flatten(self): - self.output_ctx.set_operator(cairo.OPERATOR_OVER) + def _paint(self, color=None, alpha=None): + color = color if color is not None else self.color + alpha = alpha if alpha is not None else self.alpha ptn = cairo.SurfacePattern(self.active_layer) ptn.set_matrix(self.active_matrix) - self.output_ctx.set_source(ptn) - self.output_ctx.paint() + self.output_ctx.set_source_rgba(*color, alpha=alpha) + self.output_ctx.mask(ptn) self.ctx = None self.active_layer = None self.active_matrix = None - def _paint_background(self, force=False): - if (not self.bg) or force: - self.bg = True - self.output_ctx.set_operator(cairo.OPERATOR_OVER) - self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0) + def _paint_background(self, settings=None): + color = settings.color if settings is not None else self.background_color + alpha = settings.alpha if settings is not None else 1.0 + if not self.has_bg: + self.has_bg = True + self.output_ctx.set_source_rgba(*color, alpha=alpha) self.output_ctx.paint() def scale_point(self, point): -- cgit From 96bdd0f59dbda9b755b0eb28eb44cb9a6eae1410 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 31 Jan 2016 15:24:57 +0800 Subject: Keep track of quadrant mode so we can draw full circles --- gerber/render/cairo_backend.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index fbc4271..7be7e6a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -76,6 +76,9 @@ class GerberCairoContext(GerberContext): radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle + if angle1 == angle2 and arc.quadrant_mode != 'single-quadrant': + # Make the angles slightly different otherwise Cario will draw nothing + angle2 -= 0.000000001 if isinstance(arc.aperture, Circle): width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 else: -- cgit From acde19f205898188c03a46e5d8a7a6a4d4637a2d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 26 Mar 2016 15:59:42 +0800 Subject: Support for the G85 slot statement --- gerber/render/cairo_backend.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 7be7e6a..d895e5c 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -173,6 +173,20 @@ class GerberCairoContext(GerberContext): def _render_drill(self, circle, color): self._render_circle(circle, color) + def _render_slot(self, slot, color): + start = map(mul, slot.start, self.scale) + end = map(mul, slot.end, self.scale) + + width = slot.diameter + + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (slot.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() + def _render_amgroup(self, amgroup, color): for primitive in amgroup.primitives: self.render(primitive) -- cgit From 199a0f3d3c5d4dbbc4ac6e8d1b4548523e44f00f Mon Sep 17 00:00:00 2001 From: Qau Lau Date: Fri, 8 Apr 2016 20:02:04 +0800 Subject: Update cairo_backend.py If cairo module import error use cairocffi --- gerber/render/cairo_backend.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index d895e5c..1d168ca 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -17,7 +17,10 @@ from .render import GerberContext -import cairo +try: + import cairo +except ImportError: + import cairocffi as cairo from operator import mul import math @@ -233,4 +236,4 @@ class GerberCairoContext(GerberContext): self.surface.finish() self.surface_buffer.flush() return self.surface_buffer.read() - \ No newline at end of file + -- cgit From b01c4822b6da6b7be37becb73c58f60621f6366f Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 12:27:28 +0800 Subject: Render aperture macros with clear regions --- gerber/render/cairo_backend.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 1d168ca..c1bd60c 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -191,8 +191,11 @@ class GerberCairoContext(GerberContext): self.ctx.stroke() def _render_amgroup(self, amgroup, color): + self.ctx.push_group() for primitive in amgroup.primitives: self.render(primitive) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_test_record(self, primitive, color): self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) -- cgit From ccb6eb7a766bd6edf314978f3ec4fc0dcd61652d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 16:46:44 +0800 Subject: Add support for polygon apertures --- gerber/render/cairo_backend.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index c1bd60c..2b7c2ff 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -159,6 +159,9 @@ class GerberCairoContext(GerberContext): self._render_rectangle(obround.subshapes['rectangle'], color) def _render_polygon(self, polygon, color): + if polygon.hole_radius > 0: + self.ctx.push_group() + vertices = polygon.vertices self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) @@ -172,6 +175,18 @@ class GerberCairoContext(GerberContext): self.ctx.line_to(*map(mul, v, self.scale)) self.ctx.fill() + + if polygon.hole_radius > 0: + # Render the center clear + center = tuple(map(mul, polygon.position, self.scale)) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + self.ctx.fill() + + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_drill(self, circle, color): self._render_circle(circle, color) -- cgit From f0585baefa54c5cd891ba04c81053956b1a59977 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 17 Jul 2016 13:14:54 +0800 Subject: Create first test that renders and validates the the rendered PNG is correct. --- gerber/render/cairo_backend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 5a3c7a1..3c4a395 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -292,8 +292,7 @@ class GerberCairoContext(GerberContext): self.ctx.paint() def dump(self, filename): - is_svg = filename.lower().endswith(".svg") - if is_svg: + if filename and filename.lower().endswith(".svg"): self.surface.finish() self.surface_buffer.flush() with open(filename, "w") as f: @@ -301,7 +300,7 @@ class GerberCairoContext(GerberContext): f.write(self.surface_buffer.read()) f.flush() else: - self.surface.write_to_png(filename) + return self.surface.write_to_png(filename) def dump_svg_str(self): self.surface.finish() -- cgit From 7cd6acf12670f3773113f67ed2acb35cb21c32a0 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 24 Jul 2016 17:08:47 +0800 Subject: Add many render tests based on the Umaco gerger specification. Fix multiple rendering bugs, especially related to holes in flashed apertures --- gerber/render/cairo_backend.py | 105 ++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 21 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0cb230b..78ccf34 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -20,13 +20,14 @@ try: except ImportError: import cairocffi as cairo -from operator import mul, div import math +from operator import mul, div import tempfile +from ..primitives import * from .render import GerberContext, RenderSettings from .theme import THEMES -from ..primitives import * + try: from cStringIO import StringIO @@ -219,15 +220,30 @@ class GerberCairoContext(GerberContext): center = tuple(map(mul, circle.position, self.scale)) if not self.invert: ctx = self.ctx - ctx.set_source_rgba(*color, alpha=self.alpha) + ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: ctx = self.mask_ctx ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) ctx.set_operator(cairo.OPERATOR_CLEAR) + + if circle.hole_diameter > 0: + ctx.push_group() + ctx.set_line_width(0) ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + ctx.fill() + + if circle.hole_diameter > 0: + # Render the center clear + + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() + + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) @@ -253,48 +269,95 @@ class GerberCairoContext(GerberContext): ll[1] = ll[1] - center[1] matrix.rotate(rectangle.rotation) ctx.transform(matrix) - + + if rectangle.hole_diameter > 0: + ctx.push_group() + ctx.set_line_width(0) ctx.rectangle(ll[0], ll[1], width, height) ctx.fill() + if rectangle.hole_diameter > 0: + # Render the center clear + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + center = map(mul, rectangle.position, self.scale) + ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() + + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) + if rectangle.rotation != 0: ctx.restore() def _render_obround(self, obround, color): + + if not self.invert: + ctx = self.ctx + ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + else: + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + + if obround.hole_diameter > 0: + ctx.push_group() + self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) + if obround.hole_diameter > 0: + # Render the center clear + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + center = map(mul, obround.position, self.scale) + ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + ctx.fill() + + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) + def _render_polygon(self, polygon, color): + + # TODO Ths does not handle rotation of a polygon + if not self.invert: + ctx = self.ctx + ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + else: + ctx = self.mask_ctx + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_CLEAR) + if polygon.hole_radius > 0: - self.ctx.push_group() + ctx.push_group() vertices = polygon.vertices - - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (polygon.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + + ctx.set_line_width(0) + ctx.set_line_cap(cairo.LINE_CAP_ROUND) # Start from before the end so it is easy to iterate and make sure it is closed - self.ctx.move_to(*map(mul, vertices[-1], self.scale)) + ctx.move_to(*map(mul, vertices[-1], self.scale)) for v in vertices: - self.ctx.line_to(*map(mul, v, self.scale)) + ctx.line_to(*map(mul, v, self.scale)) - self.ctx.fill() + ctx.fill() if polygon.hole_radius > 0: # Render the center clear center = tuple(map(mul, polygon.position, self.scale)) - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) - self.ctx.fill() + ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + ctx.set_operator(cairo.OPERATOR_CLEAR) + ctx.set_line_width(0) + ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + ctx.fill() - self.ctx.pop_group_to_source() - self.ctx.paint_with_alpha(1) + ctx.pop_group_to_source() + ctx.paint_with_alpha(1) def _render_drill(self, circle, color): self._render_circle(circle, color) -- cgit From 8cd842a41a55ab3d8f558a2e3e198beba7da58a1 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Manually mere rendering changes --- gerber/render/cairo_backend.py | 395 +++++++++++++++++++++-------------------- 1 file changed, 207 insertions(+), 188 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 78ccf34..349640a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -12,7 +12,7 @@ # 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 +# See the License for the specific language governing permissions and # limitations under the License. try: @@ -21,7 +21,8 @@ except ImportError: import cairocffi as cairo import math -from operator import mul, div +from operator import mul, di + import tempfile from ..primitives import * @@ -36,11 +37,14 @@ except(ImportError): class GerberCairoContext(GerberContext): + def __init__(self, scale=300): - GerberContext.__init__(self) + super(GerberCairoContext, self).__init__() self.scale = (scale, scale) self.surface = None self.ctx = None + self.active_layer = None + self.output_ctx = None self.bg = False self.mask = None self.mask_ctx = None @@ -50,37 +54,40 @@ class GerberCairoContext(GerberContext): @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) + return (self.scale_point(self.origin_in_inch) + 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) + return (self.scale_point(self.size_in_inch) + 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 = tuple(map(mul, size_in_inch, self.scale)) + size_in_inch = (abs(bounds[0][1] - bounds[0][0]), + abs(bounds[1][1] - bounds[1][0])) + size_in_pixels = self.scale_point(size_in_inch) 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) - self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - self.ctx.scale(1, -1) - self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - self.mask = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) - self.mask_ctx = cairo.Context(self.mask) - 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]) + self.surface = cairo.SVGSurface( + self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) + self.output_ctx = cairo.Context(self.surface) + self.output_ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + self.output_ctx.scale(1, -1) + self.output_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) @@ -117,46 +124,46 @@ class GerberCairoContext(GerberContext): self.color = settings.color self.alpha = settings.alpha self.invert = settings.invert + + # Get a new clean layer to render on + self._new_render_layer() 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() + # Add layer to image + self._flatten() def _render_line(self, line, color): - start = map(mul, line.start, self.scale) - end = map(mul, line.end, self.scale) + start = [pos * scale for pos, scale in zip(line.start, self.scale)] + end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if line.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): - width = line.aperture.diameter - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - - ctx.move_to(*start) - ctx.line_to(*end) - ctx.stroke() + width = line.aperture.diameter + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() elif isinstance(line.aperture, Rectangle): - points = [tuple(map(mul, x, self.scale)) for x in line.vertices] - ctx.set_line_width(0) - ctx.move_to(*points[0]) + points = [self.scale_point(x) for x in line.vertices] + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) for point in points[1:]: - ctx.line_to(*point) - ctx.fill() + self.ctx.line_to(*point) + self.ctx.fill() def _render_arc(self, arc, color): - center = map(mul, arc.center, self.scale) - start = map(mul, arc.start, self.scale) - end = map(mul, arc.end, self.scale) + center = self.scale_point(arc.center) + start = self.scale_point(arc.start) + end = self.scale_point(arc.end) radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle @@ -169,141 +176,137 @@ class GerberCairoContext(GerberContext): width = max(arc.aperture.width, arc.aperture.height, 0.001) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if arc.level_polarity == "dark"\ + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*start) # You actually have to do this... + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - ctx.arc(center[0], center[1], radius, angle1, angle2) + self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - ctx.arc_negative(center[0], center[1], radius, angle1, angle2) - ctx.move_to(*end) # ...lame - ctx.stroke() + self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) + self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if region.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - - ctx.set_line_width(0) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) - for p in region.primitives: - if isinstance(p, Line): - ctx.line_to(*tuple(map(mul, p.end, self.scale))) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*self.scale_point(region.primitives[0].start)) + for prim in region.primitives: + if isinstance(prim, Line): + self.ctx.line_to(*self.scale_point(prim.end)) else: - center = map(mul, p.center, self.scale) - start = map(mul, p.start, self.scale) - end = map(mul, p.end, self.scale) - radius = self.scale[0] * p.radius - angle1 = p.start_angle - angle2 = p.end_angle - if p.direction == 'counterclockwise': - ctx.arc(center[0], center[1], radius, angle1, angle2) + center = self.scale_point(prim.center) + radius = self.scale[0] * prim.radius + angle1 = prim.start_angle + angle2 = prim.end_angle + if prim.direction == 'counterclockwise': + self.ctx.arc(*center, radius=radius, + angle1=angle1, angle2=angle2) else: - ctx.arc_negative(center[0], center[1], radius, angle1, angle2) - ctx.fill() - + self.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + self.ctx.fill() def _render_circle(self, circle, color): - center = tuple(map(mul, circle.position, self.scale)) + center = self.scale_point(circle.position) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if circle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if circle.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if circle.hole_diameter > 0: - ctx.push_group() + self.ctx.push_group() - ctx.set_line_width(0) - ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.set_line_width(0) + self.ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() if circle.hole_diameter > 0: # Render the center clear - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_rectangle(self, rectangle, color): - ll = map(mul, rectangle.lower_left, self.scale) - width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) + lower_left = self.scale_point(rectangle.lower_left) + width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if rectangle.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if rectangle.level_polarity == "dark" + else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if rectangle.rotation != 0: - ctx.save() + self.ctx.save() center = map(mul, rectangle.position, self.scale) matrix = cairo.Matrix() matrix.translate(center[0], center[1]) # For drawing, we already handles the translation - ll[0] = ll[0] - center[0] - ll[1] = ll[1] - center[1] + lower_left[0] = lower_left[0] - center[0] + lower_left[1] = lower_left[1] - center[1] matrix.rotate(rectangle.rotation) - ctx.transform(matrix) + self.ctx.transform(matrix) if rectangle.hole_diameter > 0: - ctx.push_group() + self.ctx.push_group() - ctx.set_line_width(0) - ctx.rectangle(ll[0], ll[1], width, height) - ctx.fill() + self.ctx.set_line_width(0) + self.ctx.rectangle(lower_left[0], lower_left[1], width, height) + self.ctx.fill() if rectangle.hole_diameter > 0: # Render the center clear - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) center = map(mul, rectangle.position, self.scale) - ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) if rectangle.rotation != 0: - ctx.restore() + self.ctx.restore() def _render_obround(self, obround, color): if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if obround.hole_diameter > 0: - ctx.push_group() + self.ctx.push_group() self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) @@ -311,55 +314,54 @@ class GerberCairoContext(GerberContext): if obround.hole_diameter > 0: # Render the center clear - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) center = map(mul, obround.position, self.scale) - ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) - ctx.fill() + self.ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_polygon(self, polygon, color): # TODO Ths does not handle rotation of a polygon if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if polygon.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) if polygon.hole_radius > 0: - ctx.push_group() + self.ctx.push_group() vertices = polygon.vertices - ctx.set_line_width(0) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.set_line_width(0) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) # Start from before the end so it is easy to iterate and make sure it is closed - ctx.move_to(*map(mul, vertices[-1], self.scale)) + self.ctx.move_to(*map(mul, vertices[-1], self.scale)) for v in vertices: - ctx.line_to(*map(mul, v, self.scale)) + self.ctx.line_to(*map(mul, v, self.scale)) - ctx.fill() + self.ctx.fill() if polygon.hole_radius > 0: # Render the center clear center = tuple(map(mul, polygon.position, self.scale)) - ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - ctx.set_operator(cairo.OPERATOR_CLEAR) - ctx.set_line_width(0) - ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) - ctx.fill() + self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + self.ctx.fill() - ctx.pop_group_to_source() - ctx.paint_with_alpha(1) + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) - def _render_drill(self, circle, color): + def _render_drill(self, circle, color=None): + color = color if color is not None else self.drill_color self._render_circle(circle, color) def _render_slot(self, slot, color): @@ -369,19 +371,17 @@ class GerberCairoContext(GerberContext): width = slot.diameter if not self.invert: - ctx = self.ctx - ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) - ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR) + self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: - ctx = self.mask_ctx - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_CLEAR) - - ctx.set_line_width(width * self.scale[0]) - ctx.set_line_cap(cairo.LINE_CAP_ROUND) - ctx.move_to(*start) - ctx.line_to(*end) - ctx.stroke() + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + + self.ctx.set_line_width(width * self.scale[0]) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*start) + self.ctx.line_to(*end) + self.ctx.stroke() def _render_amgroup(self, amgroup, color): self.ctx.push_group() @@ -391,33 +391,52 @@ class GerberCairoContext(GerberContext): self.ctx.paint_with_alpha(1) def _render_test_record(self, primitive, color): - position = tuple(map(add, primitive.position, self.origin_in_inch)) + position = [pos + origin for pos, origin in zip(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.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.015) for coord in position]) + 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.015) + for coord in position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) - self.ctx.scale(1, -1) - - def _clear_mask(self): - self.mask_ctx.set_operator(cairo.OPERATOR_OVER) - self.mask_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=self.alpha) - self.mask_ctx.paint() - - def _render_mask(self): - self.ctx.set_operator(cairo.OPERATOR_OVER) - ptn = cairo.SurfacePattern(self.mask) + self.ctx.scale(1, -1) + + def _new_render_layer(self, color=None): + size_in_pixels = self.scale_point(self.size_in_inch) + layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) + ctx = cairo.Context(layer) + ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + ctx.scale(1, -1) + ctx.translate(-(self.origin_in_inch[0] * self.scale[0]), + (-self.origin_in_inch[1] * self.scale[0]) + - size_in_pixels[1]) + if self.invert: + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgba(*self.color, alpha=self.alpha) + ctx.paint() + self.ctx = ctx + self.active_layer = layer + + def _flatten(self): + self.output_ctx.set_operator(cairo.OPERATOR_OVER) + ptn = cairo.SurfacePattern(self.active_layer) ptn.set_matrix(self._xform_matrix) - self.ctx.set_source(ptn) - self.ctx.paint() + self.output_ctx.set_source(ptn) + self.output_ctx.paint() + self.ctx = None + self.active_layer = None def _paint_background(self, force=False): - if (not self.bg) or force: - self.bg = True - self.ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) - self.ctx.paint() - + if (not self.bg) or force: + self.bg = True + self.output_ctx.set_operator(cairo.OPERATOR_OVER) + self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) + self.output_ctx.paint() + + def scale_point(self, point): + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) \ No newline at end of file -- cgit From 8d5e782ccf220d77f0aad5a4e5605dc5cbe0f410 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 6 Aug 2016 09:51:58 +0800 Subject: Fix multiple problems with the merge. There are still errors, but I will intentionally leave them because future merges might resolve them --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 349640a..dc39607 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -21,7 +21,7 @@ except ImportError: import cairocffi as cairo import math -from operator import mul, di +from operator import mul, div import tempfile -- cgit From 5af19af190c1fb0f0c5be029d46d63e657dde4d9 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Commit partial merge so I can work on the plane --- gerber/render/cairo_backend.py | 92 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index dc39607..8c7232f 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -12,6 +12,7 @@ # 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. @@ -22,14 +23,14 @@ except ImportError: import math from operator import mul, div - import tempfile +import cairocffi as cairo + from ..primitives import * from .render import GerberContext, RenderSettings from .theme import THEMES - try: from cStringIO import StringIO except(ImportError): @@ -138,20 +139,35 @@ class GerberCairoContext(GerberContext): start = [pos * scale for pos, scale in zip(line.start, self.scale)] end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: +<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" else cairo.OPERATOR_CLEAR) +======= + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if line.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): +<<<<<<< HEAD width = line.aperture.diameter +======= + width = line.aperture.diameter +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) +<<<<<<< HEAD self.ctx.stroke() +======= + self.ctx.stroke() +>>>>>>> 5476da8... Fix a bunch of rendering bugs. elif isinstance(line.aperture, Rectangle): points = [self.scale_point(x) for x in line.vertices] self.ctx.set_line_width(0) @@ -176,6 +192,7 @@ class GerberCairoContext(GerberContext): width = max(arc.aperture.width, arc.aperture.height, 0.001) if not self.invert: +<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark"\ @@ -184,25 +201,50 @@ class GerberCairoContext(GerberContext): self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) +======= + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if arc.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + else: + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': +<<<<<<< HEAD self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) +======= + self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + self.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: +<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" +======= + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER + if region.level_polarity == 'dark' +>>>>>>> 5476da8... Fix a bunch of rendering bugs. else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) +<<<<<<< HEAD +======= +>>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*self.scale_point(region.primitives[0].start)) @@ -220,6 +262,7 @@ class GerberCairoContext(GerberContext): else: self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) +<<<<<<< HEAD self.ctx.fill() def _render_circle(self, circle, color): center = self.scale_point(circle.position) @@ -249,10 +292,28 @@ class GerberCairoContext(GerberContext): self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) +======= + self.ctx.fill() + + def _render_circle(self, circle, color): + center = self.scale_point(circle.position) + if not self.invert: + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator( + cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) + else: + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.arc(*center, radius=circle.radius * + self.scale[0], angle1=0, angle2=2 * math.pi) + self.ctx.fill() +>>>>>>> 5476da8... Fix a bunch of rendering bugs. def _render_rectangle(self, rectangle, color): lower_left = self.scale_point(rectangle.lower_left) width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) +<<<<<<< HEAD if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) @@ -295,6 +356,19 @@ class GerberCairoContext(GerberContext): if rectangle.rotation != 0: self.ctx.restore() +======= + + if not self.invert: + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator( + cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) + else: + self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(0) + self.ctx.rectangle(*lower_left, width=width, height=height) + self.ctx.fill() +>>>>>>> 5476da8... Fix a bunch of rendering bugs. def _render_obround(self, obround, color): @@ -424,7 +498,11 @@ class GerberCairoContext(GerberContext): def _flatten(self): self.output_ctx.set_operator(cairo.OPERATOR_OVER) +<<<<<<< HEAD ptn = cairo.SurfacePattern(self.active_layer) +======= + ptn = cairo.SurfacePattern(self.active_layer) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. ptn.set_matrix(self._xform_matrix) self.output_ctx.set_source(ptn) self.output_ctx.paint() @@ -435,8 +513,16 @@ class GerberCairoContext(GerberContext): if (not self.bg) or force: self.bg = True self.output_ctx.set_operator(cairo.OPERATOR_OVER) +<<<<<<< HEAD self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) self.output_ctx.paint() def scale_point(self, point): - return tuple([coord * scale for coord, scale in zip(point, self.scale)]) \ No newline at end of file + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) +======= + self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0) + self.output_ctx.paint() + + def scale_point(self, point): + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) +>>>>>>> 5476da8... Fix a bunch of rendering bugs. -- cgit From 724c2b3bced319ed0b50c4302fed9b0e1aa9ce9c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 5 Nov 2016 20:56:47 -0400 Subject: Finish Merge, most tests passing --- gerber/render/cairo_backend.py | 167 +++++++++++------------------------------ 1 file changed, 43 insertions(+), 124 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 8c7232f..77d413e 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -12,15 +12,15 @@ # 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. - + +# See the License for the specific language governing permissions and +# limitations under the License. + try: import cairo except ImportError: import cairocffi as cairo - + import math from operator import mul, div import tempfile @@ -139,35 +139,20 @@ class GerberCairoContext(GerberContext): start = [pos * scale for pos, scale in zip(line.start, self.scale)] end = [pos * scale for pos, scale in zip(line.end, self.scale)] if not self.invert: -<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if line.level_polarity == "dark" - else cairo.OPERATOR_CLEAR) -======= - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if line.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. + else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) if isinstance(line.aperture, Circle): -<<<<<<< HEAD - width = line.aperture.diameter -======= width = line.aperture.diameter ->>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) -<<<<<<< HEAD - self.ctx.stroke() -======= self.ctx.stroke() ->>>>>>> 5476da8... Fix a bunch of rendering bugs. elif isinstance(line.aperture, Rectangle): points = [self.scale_point(x) for x in line.vertices] self.ctx.set_line_width(0) @@ -190,9 +175,8 @@ class GerberCairoContext(GerberContext): width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 else: width = max(arc.aperture.width, arc.aperture.height, 0.001) - + if not self.invert: -<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if arc.level_polarity == "dark"\ @@ -200,51 +184,26 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - -======= - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if arc.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. + self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': -<<<<<<< HEAD self.ctx.arc(center[0], center[1], radius, angle1, angle2) else: self.ctx.arc_negative(center[0], center[1], radius, angle1, angle2) -======= - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) - else: - self.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): if not self.invert: -<<<<<<< HEAD self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if region.level_polarity == "dark" -======= - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER - if region.level_polarity == 'dark' ->>>>>>> 5476da8... Fix a bunch of rendering bugs. else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) -<<<<<<< HEAD -======= ->>>>>>> 5476da8... Fix a bunch of rendering bugs. self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*self.scale_point(region.primitives[0].start)) @@ -262,8 +221,9 @@ class GerberCairoContext(GerberContext): else: self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) -<<<<<<< HEAD - self.ctx.fill() + + self.ctx.fill() + def _render_circle(self, circle, color): center = self.scale_point(circle.position) if not self.invert: @@ -274,47 +234,30 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if circle.hole_diameter > 0: self.ctx.push_group() self.ctx.set_line_width(0) self.ctx.arc(center[0], center[1], radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - + if circle.hole_diameter > 0: # Render the center clear self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) self.ctx.arc(center[0], center[1], radius=circle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - - self.ctx.pop_group_to_source() - self.ctx.paint_with_alpha(1) -======= - self.ctx.fill() - def _render_circle(self, circle, color): - center = self.scale_point(circle.position) - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator( - cairo.OPERATOR_OVER if circle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * - self.scale[0], angle1=0, angle2=2 * math.pi) - self.ctx.fill() ->>>>>>> 5476da8... Fix a bunch of rendering bugs. + self.ctx.pop_group_to_source() + self.ctx.paint_with_alpha(1) def _render_rectangle(self, rectangle, color): lower_left = self.scale_point(rectangle.lower_left) width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) -<<<<<<< HEAD - + + if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER @@ -323,10 +266,10 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if rectangle.rotation != 0: self.ctx.save() - + center = map(mul, rectangle.position, self.scale) matrix = cairo.Matrix() matrix.translate(center[0], center[1]) @@ -335,14 +278,14 @@ class GerberCairoContext(GerberContext): lower_left[1] = lower_left[1] - center[1] matrix.rotate(rectangle.rotation) self.ctx.transform(matrix) - + if rectangle.hole_diameter > 0: self.ctx.push_group() self.ctx.set_line_width(0) self.ctx.rectangle(lower_left[0], lower_left[1], width, height) self.ctx.fill() - + if rectangle.hole_diameter > 0: # Render the center clear self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) @@ -350,42 +293,30 @@ class GerberCairoContext(GerberContext): center = map(mul, rectangle.position, self.scale) self.ctx.arc(center[0], center[1], radius=rectangle.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - + self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) - + if rectangle.rotation != 0: - self.ctx.restore() -======= + self.ctx.restore() - if not self.invert: - self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator( - cairo.OPERATOR_OVER if rectangle.level_polarity == 'dark' else cairo.OPERATOR_CLEAR) - else: - self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.rectangle(*lower_left, width=width, height=height) - self.ctx.fill() ->>>>>>> 5476da8... Fix a bunch of rendering bugs. def _render_obround(self, obround, color): - + if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if obround.level_polarity == "dark" else cairo.OPERATOR_CLEAR) else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if obround.hole_diameter > 0: self.ctx.push_group() self._render_circle(obround.subshapes['circle1'], color) self._render_circle(obround.subshapes['circle2'], color) self._render_rectangle(obround.subshapes['rectangle'], color) - + if obround.hole_diameter > 0: # Render the center clear self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) @@ -393,12 +324,12 @@ class GerberCairoContext(GerberContext): center = map(mul, obround.position, self.scale) self.ctx.arc(center[0], center[1], radius=obround.hole_radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() - + self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) def _render_polygon(self, polygon, color): - + # TODO Ths does not handle rotation of a polygon if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) @@ -406,44 +337,44 @@ class GerberCairoContext(GerberContext): else: self.ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) self.ctx.set_operator(cairo.OPERATOR_CLEAR) - + if polygon.hole_radius > 0: self.ctx.push_group() - - vertices = polygon.vertices + + vertices = polygon.vertices self.ctx.set_line_width(0) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - + # Start from before the end so it is easy to iterate and make sure it is closed self.ctx.move_to(*map(mul, vertices[-1], self.scale)) for v in vertices: self.ctx.line_to(*map(mul, v, self.scale)) self.ctx.fill() - + if polygon.hole_radius > 0: # Render the center clear center = tuple(map(mul, polygon.position, self.scale)) self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi) self.ctx.fill() - + self.ctx.pop_group_to_source() self.ctx.paint_with_alpha(1) def _render_drill(self, circle, color=None): color = color if color is not None else self.drill_color self._render_circle(circle, color) - + def _render_slot(self, slot, color): start = map(mul, slot.start, self.scale) end = map(mul, slot.end, self.scale) - + width = slot.diameter - + if not self.invert: self.ctx.set_source_rgba(color[0], color[1], color[2], alpha=self.alpha) self.ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == "dark" else cairo.OPERATOR_CLEAR) @@ -456,7 +387,7 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*start) self.ctx.line_to(*end) self.ctx.stroke() - + def _render_amgroup(self, amgroup, color): self.ctx.push_group() for primitive in amgroup.primitives: @@ -478,7 +409,7 @@ class GerberCairoContext(GerberContext): for coord in position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) - self.ctx.scale(1, -1) + self.ctx.scale(1, -1) def _new_render_layer(self, color=None): size_in_pixels = self.scale_point(self.size_in_inch) @@ -498,11 +429,7 @@ class GerberCairoContext(GerberContext): def _flatten(self): self.output_ctx.set_operator(cairo.OPERATOR_OVER) -<<<<<<< HEAD - ptn = cairo.SurfacePattern(self.active_layer) -======= ptn = cairo.SurfacePattern(self.active_layer) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. ptn.set_matrix(self._xform_matrix) self.output_ctx.set_source(ptn) self.output_ctx.paint() @@ -510,19 +437,11 @@ class GerberCairoContext(GerberContext): self.active_layer = None def _paint_background(self, force=False): - if (not self.bg) or force: + if (not self.bg) or force: self.bg = True self.output_ctx.set_operator(cairo.OPERATOR_OVER) -<<<<<<< HEAD self.output_ctx.set_source_rgba(self.background_color[0], self.background_color[1], self.background_color[2], alpha=1.0) self.output_ctx.paint() def scale_point(self, point): return tuple([coord * scale for coord, scale in zip(point, self.scale)]) -======= - self.output_ctx.set_source_rgba(*self.background_color, alpha=1.0) - self.output_ctx.paint() - - def scale_point(self, point): - return tuple([coord * scale for coord, scale in zip(point, self.scale)]) ->>>>>>> 5476da8... Fix a bunch of rendering bugs. -- cgit From d7a0f3ad2b62402f4af9abe2729f9a7c14814fe6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 6 Nov 2016 14:58:32 -0500 Subject: Remove debug print" --- gerber/render/cairo_backend.py | 1 - 1 file changed, 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 2ba022f..810351b 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -131,7 +131,6 @@ class GerberCairoContext(GerberContext): f.write(self.surface_buffer.read()) f.flush() else: - print("Wriitng To Png: filename: {}".format(filename)) return self.surface.write_to_png(filename) def dump_str(self): -- cgit From 6db0658e2336d6405fe1a6acd10dfab39ba8e7ff Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 6 Nov 2016 15:08:00 -0500 Subject: Fix tests on python3 --- gerber/render/cairo_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 810351b..6e95446 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -21,8 +21,7 @@ try: except ImportError: import cairocffi as cairo -import math -from operator import mul, div +from operator import mul import tempfile import copy import os -- cgit From 369ac7b2a33b0de2e95eb2f0ec38d543ad7ca98d Mon Sep 17 00:00:00 2001 From: Girts Folkmanis Date: Mon, 7 Nov 2016 17:11:07 -0800 Subject: cairo_backend.py: use BytesIO instead of StringIO This fixes a crash in cairocffi on Python3, and should be compatible with both python2 and python3. In python2, byte strings are just strings. In python3, when getting binary data, the user probably wants a byte string instead of a regular string. --- gerber/render/cairo_backend.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index df4fcf1..d7026b8 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -25,10 +25,7 @@ from .render import GerberContext, RenderSettings from .theme import THEMES from ..primitives import * -try: - from cStringIO import StringIO -except(ImportError): - from io import StringIO +from io import BytesIO class GerberCairoContext(GerberContext): @@ -125,9 +122,9 @@ class GerberCairoContext(GerberContext): self.surface.write_to_png(filename) def dump_str(self): - """ Return a string containing the rendered image. + """ Return a byte-string containing the rendered image. """ - fobj = StringIO() + fobj = BytesIO() self.surface.write_to_png(fobj) return fobj.getvalue() -- cgit From 5696fc7064af674d02cf84cf7934c1ac7446259e Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 18 Nov 2016 08:09:03 -0500 Subject: Fix a bunch of bugs in rendering that showed up when rendering the gerbv test suite --- gerber/render/cairo_backend.py | 488 +++++++++++++++++++++++++---------------- 1 file changed, 305 insertions(+), 183 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 31a1e77..a2baa47 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2014 Hamilton Kibbe @@ -29,6 +29,7 @@ import os from .render import GerberContext, RenderSettings from .theme import THEMES from ..primitives import * +from ..utils import rotate_point from io import BytesIO @@ -67,16 +68,13 @@ class GerberCairoContext(GerberContext): size_in_pixels = self.scale_point(size_in_inch) 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 + self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0, + x0=-self.origin_in_pixels[0], + y0=self.size_in_pixels[1]) 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.output_ctx = cairo.Context(self.surface) - self.output_ctx.scale(1, -1) - self.output_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_layer(self, layer, filename=None, settings=None, bgsettings=None, verbose=False): @@ -155,6 +153,23 @@ class GerberCairoContext(GerberContext): self.surface_buffer.close() self.surface_buffer = None + def _new_mask(self): + class Mask: + def __enter__(msk): + size_in_pixels = self.size_in_pixels + msk.surface = cairo.SVGSurface(None, size_in_pixels[0], + size_in_pixels[1]) + msk.ctx = cairo.Context(msk.surface) + msk.ctx.translate(-self.origin_in_pixels[0], -self.origin_in_pixels[1]) + return msk + + + def __exit__(msk, exc_type, exc_val, traceback): + if hasattr(msk.surface, 'finish'): + msk.surface.finish() + + return Mask() + def _render_layer(self, layer, settings): self.invert = settings.invert # Get a new clean layer to render on @@ -167,31 +182,36 @@ class GerberCairoContext(GerberContext): def _render_line(self, line, color): start = [pos * scale for pos, scale in zip(line.start, self.scale)] end = [pos * scale for pos, scale in zip(line.end, self.scale)] - self.ctx.set_operator(cairo.OPERATOR_SOURCE - if line.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - if isinstance(line.aperture, Circle): - width = line.aperture.diameter - self.ctx.set_line_width(width * self.scale[0]) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) - self.ctx.line_to(*end) - self.ctx.stroke() - elif isinstance(line.aperture, Rectangle): - points = [self.scale_point(x) for x in line.vertices] - self.ctx.set_line_width(0) - self.ctx.move_to(*points[0]) - for point in points[1:]: - self.ctx.line_to(*point) - self.ctx.fill() + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and line.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._new_mask() as mask: + if isinstance(line.aperture, Circle): + width = line.aperture.diameter + mask.ctx.set_line_width(width * self.scale[0]) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + mask.ctx.move_to(*start) + mask.ctx.line_to(*end) + mask.ctx.stroke() + + elif hasattr(line, 'vertices') and line.vertices is not None: + points = [self.scale_point(x) for x in line.vertices] + mask.ctx.set_line_width(0) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_arc(self, arc, color): center = self.scale_point(arc.center) start = self.scale_point(arc.start) end = self.scale_point(arc.end) radius = self.scale[0] * arc.radius - angle1 = arc.start_angle - angle2 = arc.end_angle + two_pi = 2 * math.pi + angle1 = (arc.start_angle + two_pi) % two_pi + angle2 = (arc.end_angle + two_pi) % two_pi if angle1 == angle2 and arc.quadrant_mode != 'single-quadrant': # Make the angles slightly different otherwise Cario will draw nothing angle2 -= 0.000000001 @@ -200,61 +220,111 @@ class GerberCairoContext(GerberContext): else: width = max(arc.aperture.width, arc.aperture.height, 0.001) - self.ctx.set_operator(cairo.OPERATOR_SOURCE - if arc.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(width * self.scale[0]) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) # You actually have to do this... - if arc.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) - else: - self.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) - self.ctx.move_to(*end) # ...lame + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and arc.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + + with self._new_mask() as mask: + mask.ctx.set_line_width(width * self.scale[0]) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND if isinstance(arc.aperture, Circle) else cairo.LINE_CAP_SQUARE) + mask.ctx.move_to(*start) # You actually have to do this... + if arc.direction == 'counterclockwise': + mask.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + mask.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + mask.ctx.move_to(*end) # ...lame + mask.ctx.stroke() + + #if isinstance(arc.aperture, Rectangle): + # print("Flash Rectangle Ends") + # print(arc.aperture.rotation * 180/math.pi) + # rect = arc.aperture + # width = self.scale[0] * rect.width + # height = self.scale[1] * rect.height + # for point, angle in zip((start, end), (angle1, angle2)): + # print("{} w {} h{}".format(point, rect.width, rect.height)) + # mask.ctx.rectangle(point[0] - width/2.0, + # point[1] - height/2.0, width, height) + # mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + def _render_region(self, region, color): - self.ctx.set_operator(cairo.OPERATOR_SOURCE - if region.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*self.scale_point(region.primitives[0].start)) - for prim in region.primitives: - if isinstance(prim, Line): - self.ctx.line_to(*self.scale_point(prim.end)) - else: - center = self.scale_point(prim.center) - radius = self.scale[0] * prim.radius - angle1 = prim.start_angle - angle2 = prim.end_angle - if prim.direction == 'counterclockwise': - self.ctx.arc(*center, radius=radius, - angle1=angle1, angle2=angle2) + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) and region.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + mask.ctx.move_to(*self.scale_point(region.primitives[0].start)) + for prim in region.primitives: + if isinstance(prim, Line): + mask.ctx.line_to(*self.scale_point(prim.end)) else: - self.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) - self.ctx.fill() + center = self.scale_point(prim.center) + radius = self.scale[0] * prim.radius + angle1 = prim.start_angle + angle2 = prim.end_angle + if prim.direction == 'counterclockwise': + mask.ctx.arc(*center, radius=radius, + angle1=angle1, angle2=angle2) + else: + mask.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + mask.ctx.fill() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_circle(self, circle, color): center = self.scale_point(circle.position) - self.ctx.set_operator(cairo.OPERATOR_SOURCE - if circle.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=(circle.radius * self.scale[0]), angle1=0, - angle2=(2 * math.pi)) - self.ctx.fill() - - if circle.hole_diameter > 0: - # Render the center clear - - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - self.ctx.arc(center[0], center[1], - radius=circle.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) - self.ctx.fill() + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and circle.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + mask.ctx.arc(center[0], + center[1], + radius=(circle.radius * self.scale[0]), + angle1=0, + angle2=(2 * math.pi)) + mask.ctx.fill() + + if hasattr(circle, 'hole_diameter') and circle.hole_diameter > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR) + mask.ctx.arc(center[0], + center[1], + radius=circle.hole_radius * self.scale[0], + angle1=0, + angle2=2 * math.pi) + mask.ctx.fill() + + if (hasattr(circle, 'hole_width') and hasattr(circle, 'hole_height') + and circle.hole_width > 0 and circle.hole_height > 0): + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if circle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height = self.scale_point((circle.hole_width, circle.hole_height)) + lower_left = rotate_point( + (center[0] - width / 2.0, center[1] - height / 2.0), + circle.rotation, center) + lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), + circle.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), + circle.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), + circle.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_rectangle(self, rectangle, color): @@ -262,101 +332,156 @@ class GerberCairoContext(GerberContext): width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) - self.ctx.set_operator(cairo.OPERATOR_SOURCE - if rectangle.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - - if rectangle.rotation != 0: - self.ctx.save() - - center = map(mul, rectangle.position, self.scale) - matrix = cairo.Matrix() - matrix.translate(center[0], center[1]) - # For drawing, we already handles the translation - lower_left[0] = lower_left[0] - center[0] - lower_left[1] = lower_left[1] - center[1] - matrix.rotate(rectangle.rotation) - self.ctx.transform(matrix) - - if rectangle.hole_diameter > 0: - self.ctx.push_group() - - self.ctx.set_line_width(0) - self.ctx.rectangle(*lower_left, width=width, height=height) - self.ctx.fill() - - if rectangle.hole_diameter > 0: - # Render the center clear - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR - if rectangle.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_SOURCE) - center = map(mul, rectangle.position, self.scale) - self.ctx.arc(center[0], center[1], - radius=rectangle.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) - self.ctx.fill() - - if rectangle.rotation != 0: - self.ctx.restore() + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and rectangle.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._new_mask() as mask: + + mask.ctx.set_line_width(0) + mask.ctx.rectangle(*lower_left, width=width, height=height) + mask.ctx.fill() + + center = self.scale_point(rectangle.position) + if rectangle.hole_diameter > 0: + # Render the center clear + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if rectangle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + + mask.ctx.arc(center[0], center[1], + radius=rectangle.hole_radius * self.scale[0], angle1=0, + angle2=2 * math.pi) + mask.ctx.fill() + + if rectangle.hole_width > 0 and rectangle.hole_height > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if rectangle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height = self.scale_point((rectangle.hole_width, rectangle.hole_height)) + lower_left = rotate_point((center[0] - width/2.0, center[1] - height/2.0), rectangle.rotation, center) + lower_right = rotate_point((center[0] + width/2.0, center[1] - height/2.0), rectangle.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), rectangle.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), rectangle.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_obround(self, obround, color): - if obround.hole_diameter > 0: - self.ctx.push_group() - - self._render_circle(obround.subshapes['circle1'], color) - self._render_circle(obround.subshapes['circle2'], color) - self._render_rectangle(obround.subshapes['rectangle'], color) - - if obround.hole_diameter > 0: - # Render the center clear - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR) - center = map(mul, obround.position, self.scale) - self.ctx.arc(center[0], center[1], - radius=obround.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) - self.ctx.fill() - - self.ctx.pop_group_to_source() - self.ctx.paint_with_alpha(1) - + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and obround.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + + # Render circles + for circle in (obround.subshapes['circle1'], obround.subshapes['circle2']): + center = self.scale_point(circle.position) + mask.ctx.arc(center[0], + center[1], + radius=(circle.radius * self.scale[0]), + angle1=0, + angle2=(2 * math.pi)) + mask.ctx.fill() + + # Render Rectangle + rectangle = obround.subshapes['rectangle'] + lower_left = self.scale_point(rectangle.lower_left) + width, height = tuple([abs(coord) for coord in + self.scale_point((rectangle.width, + rectangle.height))]) + mask.ctx.rectangle(*lower_left, width=width, height=height) + mask.ctx.fill() + + center = self.scale_point(obround.position) + if obround.hole_diameter > 0: + # Render the center clear + mask.ctx.set_operator(cairo.OPERATOR_CLEAR) + mask.ctx.arc(center[0], center[1], + radius=obround.hole_radius * self.scale[0], angle1=0, + angle2=2 * math.pi) + mask.ctx.fill() + + if obround.hole_width > 0 and obround.hole_height > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if rectangle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height =self.scale_point((obround.hole_width, obround.hole_height)) + lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0), + obround.rotation, center) + lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), + obround.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), + obround.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), + obround.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_polygon(self, polygon, color): - - # TODO Ths does not handle rotation of a polygon - self.ctx.set_operator(cairo.OPERATOR_SOURCE - if polygon.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - if polygon.hole_radius > 0: - self.ctx.push_group() - - vertices = polygon.vertices - - self.ctx.set_line_width(0) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - - # Start from before the end so it is easy to iterate and make sure it is closed - self.ctx.move_to(*map(mul, vertices[-1], self.scale)) - for v in vertices: - self.ctx.line_to(*map(mul, v, self.scale)) - - self.ctx.fill() - - if polygon.hole_radius > 0: - # Render the center clear - center = tuple(map(mul, polygon.position, self.scale)) - self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha) - self.ctx.set_operator(cairo.OPERATOR_CLEAR - if polygon.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_SOURCE) - self.ctx.set_line_width(0) - self.ctx.arc(center[0], - center[1], - polygon.hole_radius * self.scale[0], 0, 2 * math.pi) - self.ctx.fill() + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and polygon.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._new_mask() as mask: + + vertices = polygon.vertices + mask.ctx.set_line_width(0) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + # Start from before the end so it is easy to iterate and make sure + # it is closed + mask.ctx.move_to(*self.scale_point(vertices[-1])) + for v in vertices: + mask.ctx.line_to(*self.scale_point(v)) + mask.ctx.fill() + + center = self.scale_point(polygon.position) + if polygon.hole_radius > 0: + # Render the center clear + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if polygon.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + mask.ctx.set_line_width(0) + mask.ctx.arc(center[0], + center[1], + polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + mask.ctx.fill() + + if polygon.hole_width > 0 and polygon.hole_height > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if polygon.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height = self.scale_point((polygon.hole_width, polygon.hole_height)) + lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0), + polygon.rotation, center) + lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), + polygon.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), + polygon.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), + polygon.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_drill(self, circle, color=None): color = color if color is not None else self.drill_color @@ -368,22 +493,20 @@ class GerberCairoContext(GerberContext): width = slot.diameter - self.ctx.set_operator(cairo.OPERATOR_SOURCE + self.ctx.set_operator(cairo.OPERATOR_OVER if slot.level_polarity == 'dark' and (not self.invert) else cairo.OPERATOR_CLEAR) - - self.ctx.set_line_width(width * self.scale[0]) - self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - self.ctx.move_to(*start) - self.ctx.line_to(*end) - self.ctx.stroke() + with self._new_mask() as mask: + mask.ctx.set_line_width(width * self.scale[0]) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + mask.ctx.move_to(*start) + mask.ctx.line_to(*end) + mask.ctx.stroke() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) def _render_amgroup(self, amgroup, color): - self.ctx.push_group() for primitive in amgroup.primitives: self.render(primitive) - self.ctx.pop_group_to_source() - self.ctx.paint_with_alpha(1) def _render_test_record(self, primitive, color): position = [pos + origin for pos, origin in @@ -392,7 +515,7 @@ class GerberCairoContext(GerberContext): '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_operator(cairo.OPERATOR_SOURCE + self.ctx.set_operator(cairo.OPERATOR_OVER if primitive.level_polarity == 'dark' and (not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.move_to(*[self.scale[0] * (coord + 0.015) for coord in position]) @@ -405,26 +528,25 @@ class GerberCairoContext(GerberContext): matrix = copy.copy(self._xform_matrix) layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) ctx = cairo.Context(layer) - ctx.scale(1, -1) - ctx.translate(-(self.origin_in_inch[0] * self.scale[0]), - (-self.origin_in_inch[1] * self.scale[0]) - size_in_pixels[1]) + if self.invert: + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) ctx.set_operator(cairo.OPERATOR_OVER) ctx.paint() if mirror: matrix.xx = -1.0 matrix.x0 = self.origin_in_pixels[0] + self.size_in_pixels[0] self.ctx = ctx + self.ctx.set_matrix(matrix) self.active_layer = layer self.active_matrix = matrix + def _flatten(self, color=None, alpha=None): color = color if color is not None else self.color alpha = alpha if alpha is not None else self.alpha - ptn = cairo.SurfacePattern(self.active_layer) - ptn.set_matrix(self.active_matrix) self.output_ctx.set_source_rgba(*color, alpha=alpha) - self.output_ctx.mask(ptn) + self.output_ctx.mask_surface(self.active_layer) self.ctx = None self.active_layer = None self.active_matrix = None -- cgit From 7c4ec8a768c7dfd3124c6b68805031367a80a890 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 13 Dec 2016 00:01:05 -0500 Subject: Clip context to axis- and pixel- aligned bounds before rendering primitives. Significantly speeds up render --- gerber/render/cairo_backend.py | 1173 +++++++++++++++++++++------------------- 1 file changed, 610 insertions(+), 563 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index a2baa47..e6af67f 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -1,563 +1,610 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2014 Hamilton Kibbe - -# 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. - -try: - import cairo -except ImportError: - import cairocffi as cairo - -from operator import mul -import tempfile -import copy -import os - -from .render import GerberContext, RenderSettings -from .theme import THEMES -from ..primitives import * -from ..utils import rotate_point - -from io import BytesIO - - -class GerberCairoContext(GerberContext): - - def __init__(self, scale=300): - super(GerberCairoContext, self).__init__() - self.scale = (scale, scale) - self.surface = None - self.surface_buffer = None - self.ctx = None - self.active_layer = None - self.active_matrix = None - self.output_ctx = None - self.has_bg = False - self.origin_in_inch = None - self.size_in_inch = None - self._xform_matrix = None - self._render_count = 0 - - @property - def origin_in_pixels(self): - return (self.scale_point(self.origin_in_inch) - if self.origin_in_inch is not None else (0.0, 0.0)) - - @property - def size_in_pixels(self): - return (self.scale_point(self.size_in_inch) - 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 = self.scale_point(size_in_inch) - 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 - self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0, - x0=-self.origin_in_pixels[0], - y0=self.size_in_pixels[1]) - 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.output_ctx = cairo.Context(self.surface) - - def render_layer(self, layer, filename=None, settings=None, bgsettings=None, - verbose=False): - if settings is None: - settings = THEMES['default'].get(layer.layer_class, RenderSettings()) - if bgsettings is None: - bgsettings = THEMES['default'].get('background', RenderSettings()) - - if self._render_count == 0: - if verbose: - print('[Render]: Rendering Background.') - self.clear() - self.set_bounds(layer.bounds) - self._paint_background(bgsettings) - if verbose: - print('[Render]: Rendering {} Layer.'.format(layer.layer_class)) - self._render_count += 1 - self._render_layer(layer, settings) - if filename is not None: - self.dump(filename, verbose) - - def render_layers(self, layers, filename, theme=THEMES['default'], - verbose=False): - """ Render a set of layers - """ - self.clear() - bgsettings = theme['background'] - for layer in layers: - settings = theme.get(layer.layer_class, RenderSettings()) - self.render_layer(layer, settings=settings, bgsettings=bgsettings, - verbose=verbose) - self.dump(filename, verbose) - - def dump(self, filename=None, verbose=False): - """ Save image as `filename` - """ - try: - is_svg = os.path.splitext(filename.lower())[1] == '.svg' - except: - is_svg = False - if verbose: - print('[Render]: Writing image to {}'.format(filename)) - 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: - return self.surface.write_to_png(filename) - - def dump_str(self): - """ Return a byte-string containing the rendered image. - """ - fobj = BytesIO() - 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 clear(self): - self.surface = None - self.output_ctx = None - self.has_bg = False - self.origin_in_inch = None - self.size_in_inch = None - self._xform_matrix = None - self._render_count = 0 - if hasattr(self.surface_buffer, 'close'): - self.surface_buffer.close() - self.surface_buffer = None - - def _new_mask(self): - class Mask: - def __enter__(msk): - size_in_pixels = self.size_in_pixels - msk.surface = cairo.SVGSurface(None, size_in_pixels[0], - size_in_pixels[1]) - msk.ctx = cairo.Context(msk.surface) - msk.ctx.translate(-self.origin_in_pixels[0], -self.origin_in_pixels[1]) - return msk - - - def __exit__(msk, exc_type, exc_val, traceback): - if hasattr(msk.surface, 'finish'): - msk.surface.finish() - - return Mask() - - def _render_layer(self, layer, settings): - self.invert = settings.invert - # Get a new clean layer to render on - self._new_render_layer(mirror=settings.mirror) - for prim in layer.primitives: - self.render(prim) - # Add layer to image - self._flatten(settings.color, settings.alpha) - - def _render_line(self, line, color): - start = [pos * scale for pos, scale in zip(line.start, self.scale)] - end = [pos * scale for pos, scale in zip(line.end, self.scale)] - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) - and line.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - with self._new_mask() as mask: - if isinstance(line.aperture, Circle): - width = line.aperture.diameter - mask.ctx.set_line_width(width * self.scale[0]) - mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - mask.ctx.move_to(*start) - mask.ctx.line_to(*end) - mask.ctx.stroke() - - elif hasattr(line, 'vertices') and line.vertices is not None: - points = [self.scale_point(x) for x in line.vertices] - mask.ctx.set_line_width(0) - mask.ctx.move_to(*points[-1]) - for point in points: - mask.ctx.line_to(*point) - mask.ctx.fill() - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - def _render_arc(self, arc, color): - center = self.scale_point(arc.center) - start = self.scale_point(arc.start) - end = self.scale_point(arc.end) - radius = self.scale[0] * arc.radius - two_pi = 2 * math.pi - angle1 = (arc.start_angle + two_pi) % two_pi - angle2 = (arc.end_angle + two_pi) % two_pi - if angle1 == angle2 and arc.quadrant_mode != 'single-quadrant': - # Make the angles slightly different otherwise Cario will draw nothing - angle2 -= 0.000000001 - if isinstance(arc.aperture, Circle): - width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 - else: - width = max(arc.aperture.width, arc.aperture.height, 0.001) - - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) - and arc.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - - with self._new_mask() as mask: - mask.ctx.set_line_width(width * self.scale[0]) - mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND if isinstance(arc.aperture, Circle) else cairo.LINE_CAP_SQUARE) - mask.ctx.move_to(*start) # You actually have to do this... - if arc.direction == 'counterclockwise': - mask.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) - else: - mask.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) - mask.ctx.move_to(*end) # ...lame - mask.ctx.stroke() - - #if isinstance(arc.aperture, Rectangle): - # print("Flash Rectangle Ends") - # print(arc.aperture.rotation * 180/math.pi) - # rect = arc.aperture - # width = self.scale[0] * rect.width - # height = self.scale[1] * rect.height - # for point, angle in zip((start, end), (angle1, angle2)): - # print("{} w {} h{}".format(point, rect.width, rect.height)) - # mask.ctx.rectangle(point[0] - width/2.0, - # point[1] - height/2.0, width, height) - # mask.ctx.fill() - - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - - def _render_region(self, region, color): - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) and region.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - with self._new_mask() as mask: - mask.ctx.set_line_width(0) - mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - mask.ctx.move_to(*self.scale_point(region.primitives[0].start)) - for prim in region.primitives: - if isinstance(prim, Line): - mask.ctx.line_to(*self.scale_point(prim.end)) - else: - center = self.scale_point(prim.center) - radius = self.scale[0] * prim.radius - angle1 = prim.start_angle - angle2 = prim.end_angle - if prim.direction == 'counterclockwise': - mask.ctx.arc(*center, radius=radius, - angle1=angle1, angle2=angle2) - else: - mask.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) - mask.ctx.fill() - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - def _render_circle(self, circle, color): - center = self.scale_point(circle.position) - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) - and circle.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - - with self._new_mask() as mask: - mask.ctx.set_line_width(0) - mask.ctx.arc(center[0], - center[1], - radius=(circle.radius * self.scale[0]), - angle1=0, - angle2=(2 * math.pi)) - mask.ctx.fill() - - if hasattr(circle, 'hole_diameter') and circle.hole_diameter > 0: - mask.ctx.set_operator(cairo.OPERATOR_CLEAR) - mask.ctx.arc(center[0], - center[1], - radius=circle.hole_radius * self.scale[0], - angle1=0, - angle2=2 * math.pi) - mask.ctx.fill() - - if (hasattr(circle, 'hole_width') and hasattr(circle, 'hole_height') - and circle.hole_width > 0 and circle.hole_height > 0): - mask.ctx.set_operator(cairo.OPERATOR_CLEAR - if circle.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_OVER) - width, height = self.scale_point((circle.hole_width, circle.hole_height)) - lower_left = rotate_point( - (center[0] - width / 2.0, center[1] - height / 2.0), - circle.rotation, center) - lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), - circle.rotation, center) - upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), - circle.rotation, center) - upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), - circle.rotation, center) - points = (lower_left, lower_right, upper_right, upper_left) - mask.ctx.move_to(*points[-1]) - for point in points: - mask.ctx.line_to(*point) - mask.ctx.fill() - - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - - def _render_rectangle(self, rectangle, color): - lower_left = self.scale_point(rectangle.lower_left) - width, height = tuple([abs(coord) for coord in - self.scale_point((rectangle.width, - rectangle.height))]) - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) - and rectangle.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - with self._new_mask() as mask: - - mask.ctx.set_line_width(0) - mask.ctx.rectangle(*lower_left, width=width, height=height) - mask.ctx.fill() - - center = self.scale_point(rectangle.position) - if rectangle.hole_diameter > 0: - # Render the center clear - mask.ctx.set_operator(cairo.OPERATOR_CLEAR - if rectangle.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_OVER) - - mask.ctx.arc(center[0], center[1], - radius=rectangle.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) - mask.ctx.fill() - - if rectangle.hole_width > 0 and rectangle.hole_height > 0: - mask.ctx.set_operator(cairo.OPERATOR_CLEAR - if rectangle.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_OVER) - width, height = self.scale_point((rectangle.hole_width, rectangle.hole_height)) - lower_left = rotate_point((center[0] - width/2.0, center[1] - height/2.0), rectangle.rotation, center) - lower_right = rotate_point((center[0] + width/2.0, center[1] - height/2.0), rectangle.rotation, center) - upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), rectangle.rotation, center) - upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), rectangle.rotation, center) - points = (lower_left, lower_right, upper_right, upper_left) - mask.ctx.move_to(*points[-1]) - for point in points: - mask.ctx.line_to(*point) - mask.ctx.fill() - - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - def _render_obround(self, obround, color): - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) - and obround.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - with self._new_mask() as mask: - mask.ctx.set_line_width(0) - - # Render circles - for circle in (obround.subshapes['circle1'], obround.subshapes['circle2']): - center = self.scale_point(circle.position) - mask.ctx.arc(center[0], - center[1], - radius=(circle.radius * self.scale[0]), - angle1=0, - angle2=(2 * math.pi)) - mask.ctx.fill() - - # Render Rectangle - rectangle = obround.subshapes['rectangle'] - lower_left = self.scale_point(rectangle.lower_left) - width, height = tuple([abs(coord) for coord in - self.scale_point((rectangle.width, - rectangle.height))]) - mask.ctx.rectangle(*lower_left, width=width, height=height) - mask.ctx.fill() - - center = self.scale_point(obround.position) - if obround.hole_diameter > 0: - # Render the center clear - mask.ctx.set_operator(cairo.OPERATOR_CLEAR) - mask.ctx.arc(center[0], center[1], - radius=obround.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) - mask.ctx.fill() - - if obround.hole_width > 0 and obround.hole_height > 0: - mask.ctx.set_operator(cairo.OPERATOR_CLEAR - if rectangle.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_OVER) - width, height =self.scale_point((obround.hole_width, obround.hole_height)) - lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0), - obround.rotation, center) - lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), - obround.rotation, center) - upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), - obround.rotation, center) - upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), - obround.rotation, center) - points = (lower_left, lower_right, upper_right, upper_left) - mask.ctx.move_to(*points[-1]) - for point in points: - mask.ctx.line_to(*point) - mask.ctx.fill() - - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - def _render_polygon(self, polygon, color): - self.ctx.set_operator(cairo.OPERATOR_OVER - if (not self.invert) - and polygon.level_polarity == 'dark' - else cairo.OPERATOR_CLEAR) - with self._new_mask() as mask: - - vertices = polygon.vertices - mask.ctx.set_line_width(0) - mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - # Start from before the end so it is easy to iterate and make sure - # it is closed - mask.ctx.move_to(*self.scale_point(vertices[-1])) - for v in vertices: - mask.ctx.line_to(*self.scale_point(v)) - mask.ctx.fill() - - center = self.scale_point(polygon.position) - if polygon.hole_radius > 0: - # Render the center clear - mask.ctx.set_operator(cairo.OPERATOR_CLEAR - if polygon.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_OVER) - mask.ctx.set_line_width(0) - mask.ctx.arc(center[0], - center[1], - polygon.hole_radius * self.scale[0], 0, 2 * math.pi) - mask.ctx.fill() - - if polygon.hole_width > 0 and polygon.hole_height > 0: - mask.ctx.set_operator(cairo.OPERATOR_CLEAR - if polygon.level_polarity == 'dark' - and (not self.invert) - else cairo.OPERATOR_OVER) - width, height = self.scale_point((polygon.hole_width, polygon.hole_height)) - lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0), - polygon.rotation, center) - lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), - polygon.rotation, center) - upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), - polygon.rotation, center) - upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), - polygon.rotation, center) - points = (lower_left, lower_right, upper_right, upper_left) - mask.ctx.move_to(*points[-1]) - for point in points: - mask.ctx.line_to(*point) - mask.ctx.fill() - - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - def _render_drill(self, circle, color=None): - color = color if color is not None else self.drill_color - self._render_circle(circle, color) - - def _render_slot(self, slot, color): - start = map(mul, slot.start, self.scale) - end = map(mul, slot.end, self.scale) - - width = slot.diameter - - self.ctx.set_operator(cairo.OPERATOR_OVER - if slot.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - with self._new_mask() as mask: - mask.ctx.set_line_width(width * self.scale[0]) - mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) - mask.ctx.move_to(*start) - mask.ctx.line_to(*end) - mask.ctx.stroke() - self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) - - def _render_amgroup(self, amgroup, color): - for primitive in amgroup.primitives: - self.render(primitive) - - def _render_test_record(self, primitive, color): - position = [pos + origin for pos, origin in - zip(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_operator(cairo.OPERATOR_OVER - if primitive.level_polarity == 'dark' and - (not self.invert) else cairo.OPERATOR_CLEAR) - 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) - - def _new_render_layer(self, color=None, mirror=False): - size_in_pixels = self.scale_point(self.size_in_inch) - matrix = copy.copy(self._xform_matrix) - layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) - ctx = cairo.Context(layer) - - if self.invert: - ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) - ctx.set_operator(cairo.OPERATOR_OVER) - ctx.paint() - if mirror: - matrix.xx = -1.0 - matrix.x0 = self.origin_in_pixels[0] + self.size_in_pixels[0] - self.ctx = ctx - self.ctx.set_matrix(matrix) - self.active_layer = layer - self.active_matrix = matrix - - - def _flatten(self, color=None, alpha=None): - color = color if color is not None else self.color - alpha = alpha if alpha is not None else self.alpha - self.output_ctx.set_source_rgba(*color, alpha=alpha) - self.output_ctx.mask_surface(self.active_layer) - self.ctx = None - self.active_layer = None - self.active_matrix = None - - def _paint_background(self, settings=None): - color = settings.color if settings is not None else self.background_color - alpha = settings.alpha if settings is not None else 1.0 - if not self.has_bg: - self.has_bg = True - self.output_ctx.set_source_rgba(*color, alpha=alpha) - self.output_ctx.paint() - - def scale_point(self, point): - return tuple([coord * scale for coord, scale in zip(point, self.scale)]) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2014 Hamilton Kibbe + +# 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. + +try: + import cairo +except ImportError: + import cairocffi as cairo + +from operator import mul +import tempfile +import copy +import os + +from .render import GerberContext, RenderSettings +from .theme import THEMES +from ..primitives import * +from ..utils import rotate_point + +from io import BytesIO + + +class GerberCairoContext(GerberContext): + + def __init__(self, scale=300): + super(GerberCairoContext, self).__init__() + self.scale = (scale, scale) + self.surface = None + self.surface_buffer = None + self.ctx = None + self.active_layer = None + self.active_matrix = None + self.output_ctx = None + self.has_bg = False + self.origin_in_inch = None + self.size_in_inch = None + self._xform_matrix = None + self._render_count = 0 + + @property + def origin_in_pixels(self): + return (self.scale_point(self.origin_in_inch) + if self.origin_in_inch is not None else (0.0, 0.0)) + + @property + def size_in_pixels(self): + return (self.scale_point(self.size_in_inch) + 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 = self.scale_point(size_in_inch) + 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 + self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0, + x0=-self.origin_in_pixels[0], + y0=self.size_in_pixels[1]) + 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.output_ctx = cairo.Context(self.surface) + + def render_layer(self, layer, filename=None, settings=None, bgsettings=None, + verbose=False): + if settings is None: + settings = THEMES['default'].get(layer.layer_class, RenderSettings()) + if bgsettings is None: + bgsettings = THEMES['default'].get('background', RenderSettings()) + + if self._render_count == 0: + if verbose: + print('[Render]: Rendering Background.') + self.clear() + self.set_bounds(layer.bounds) + self._paint_background(bgsettings) + if verbose: + print('[Render]: Rendering {} Layer.'.format(layer.layer_class)) + self._render_count += 1 + self._render_layer(layer, settings) + if filename is not None: + self.dump(filename, verbose) + + def render_layers(self, layers, filename, theme=THEMES['default'], + verbose=False): + """ Render a set of layers + """ + self.clear() + bgsettings = theme['background'] + for layer in layers: + settings = theme.get(layer.layer_class, RenderSettings()) + self.render_layer(layer, settings=settings, bgsettings=bgsettings, + verbose=verbose) + self.dump(filename, verbose) + + def dump(self, filename=None, verbose=False): + """ Save image as `filename` + """ + try: + is_svg = os.path.splitext(filename.lower())[1] == '.svg' + except: + is_svg = False + if verbose: + print('[Render]: Writing image to {}'.format(filename)) + 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: + return self.surface.write_to_png(filename) + + def dump_str(self): + """ Return a byte-string containing the rendered image. + """ + fobj = BytesIO() + 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 clear(self): + self.surface = None + self.output_ctx = None + self.has_bg = False + self.origin_in_inch = None + self.size_in_inch = None + self._xform_matrix = None + self._render_count = 0 + self.surface_buffer = None + + def _new_mask(self): + class Mask: + def __enter__(msk): + size_in_pixels = self.size_in_pixels + msk.surface = cairo.SVGSurface(None, size_in_pixels[0], + size_in_pixels[1]) + msk.ctx = cairo.Context(msk.surface) + msk.ctx.translate(-self.origin_in_pixels[0], -self.origin_in_pixels[1]) + return msk + + + def __exit__(msk, exc_type, exc_val, traceback): + if hasattr(msk.surface, 'finish'): + msk.surface.finish() + + return Mask() + + def _render_layer(self, layer, settings): + self.invert = settings.invert + # Get a new clean layer to render on + self._new_render_layer(mirror=settings.mirror) + for prim in layer.primitives: + self.render(prim) + # Add layer to image + self._flatten(settings.color, settings.alpha) + + def _render_line(self, line, color): + start = self.scale_point(line.start) + end = self.scale_point(line.end) + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and line.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + + with self._clip_primitive(line): + with self._new_mask() as mask: + if isinstance(line.aperture, Circle): + width = line.aperture.diameter + mask.ctx.set_line_width(width * self.scale[0]) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + mask.ctx.move_to(*start) + mask.ctx.line_to(*end) + mask.ctx.stroke() + + elif hasattr(line, 'vertices') and line.vertices is not None: + points = [self.scale_point(x) for x in line.vertices] + mask.ctx.set_line_width(0) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_arc(self, arc, color): + center = self.scale_point(arc.center) + start = self.scale_point(arc.start) + end = self.scale_point(arc.end) + radius = self.scale[0] * arc.radius + two_pi = 2 * math.pi + angle1 = (arc.start_angle + two_pi) % two_pi + angle2 = (arc.end_angle + two_pi) % two_pi + if angle1 == angle2 and arc.quadrant_mode != 'single-quadrant': + # Make the angles slightly different otherwise Cario will draw nothing + angle2 -= 0.000000001 + if isinstance(arc.aperture, Circle): + width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 + else: + width = max(arc.aperture.width, arc.aperture.height, 0.001) + + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and arc.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._clip_primitive(arc): + with self._new_mask() as mask: + mask.ctx.set_line_width(width * self.scale[0]) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND if isinstance(arc.aperture, Circle) else cairo.LINE_CAP_SQUARE) + mask.ctx.move_to(*start) # You actually have to do this... + if arc.direction == 'counterclockwise': + mask.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + mask.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + mask.ctx.move_to(*end) # ...lame + mask.ctx.stroke() + + #if isinstance(arc.aperture, Rectangle): + # print("Flash Rectangle Ends") + # print(arc.aperture.rotation * 180/math.pi) + # rect = arc.aperture + # width = self.scale[0] * rect.width + # height = self.scale[1] * rect.height + # for point, angle in zip((start, end), (angle1, angle2)): + # print("{} w {} h{}".format(point, rect.width, rect.height)) + # mask.ctx.rectangle(point[0] - width/2.0, + # point[1] - height/2.0, width, height) + # mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_region(self, region, color): + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) and region.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._clip_primitive(region): + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + mask.ctx.move_to(*self.scale_point(region.primitives[0].start)) + for prim in region.primitives: + if isinstance(prim, Line): + mask.ctx.line_to(*self.scale_point(prim.end)) + else: + center = self.scale_point(prim.center) + radius = self.scale[0] * prim.radius + angle1 = prim.start_angle + angle2 = prim.end_angle + if prim.direction == 'counterclockwise': + mask.ctx.arc(*center, radius=radius, + angle1=angle1, angle2=angle2) + else: + mask.ctx.arc_negative(*center, radius=radius, + angle1=angle1, angle2=angle2) + mask.ctx.fill() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_circle(self, circle, color): + center = self.scale_point(circle.position) + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and circle.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._clip_primitive(circle): + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + mask.ctx.arc(center[0], + center[1], + radius=(circle.radius * self.scale[0]), + angle1=0, + angle2=(2 * math.pi)) + mask.ctx.fill() + + if hasattr(circle, 'hole_diameter') and circle.hole_diameter > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR) + mask.ctx.arc(center[0], + center[1], + radius=circle.hole_radius * self.scale[0], + angle1=0, + angle2=2 * math.pi) + mask.ctx.fill() + + if (hasattr(circle, 'hole_width') and hasattr(circle, 'hole_height') + and circle.hole_width > 0 and circle.hole_height > 0): + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if circle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height = self.scale_point((circle.hole_width, circle.hole_height)) + lower_left = rotate_point( + (center[0] - width / 2.0, center[1] - height / 2.0), + circle.rotation, center) + lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), + circle.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), + circle.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), + circle.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_rectangle(self, rectangle, color): + lower_left = self.scale_point(rectangle.lower_left) + width, height = tuple([abs(coord) for coord in + self.scale_point((rectangle.width, + rectangle.height))]) + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and rectangle.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._clip_primitive(rectangle): + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + mask.ctx.rectangle(*lower_left, width=width, height=height) + mask.ctx.fill() + + center = self.scale_point(rectangle.position) + if rectangle.hole_diameter > 0: + # Render the center clear + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if rectangle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + + mask.ctx.arc(center[0], center[1], + radius=rectangle.hole_radius * self.scale[0], angle1=0, + angle2=2 * math.pi) + mask.ctx.fill() + + if rectangle.hole_width > 0 and rectangle.hole_height > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if rectangle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height = self.scale_point((rectangle.hole_width, rectangle.hole_height)) + lower_left = rotate_point((center[0] - width/2.0, center[1] - height/2.0), rectangle.rotation, center) + lower_right = rotate_point((center[0] + width/2.0, center[1] - height/2.0), rectangle.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), rectangle.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), rectangle.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_obround(self, obround, color): + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and obround.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._clip_primitive(obround): + with self._new_mask() as mask: + mask.ctx.set_line_width(0) + + # Render circles + for circle in (obround.subshapes['circle1'], obround.subshapes['circle2']): + center = self.scale_point(circle.position) + mask.ctx.arc(center[0], + center[1], + radius=(circle.radius * self.scale[0]), + angle1=0, + angle2=(2 * math.pi)) + mask.ctx.fill() + + # Render Rectangle + rectangle = obround.subshapes['rectangle'] + lower_left = self.scale_point(rectangle.lower_left) + width, height = tuple([abs(coord) for coord in + self.scale_point((rectangle.width, + rectangle.height))]) + mask.ctx.rectangle(*lower_left, width=width, height=height) + mask.ctx.fill() + + center = self.scale_point(obround.position) + if obround.hole_diameter > 0: + # Render the center clear + mask.ctx.set_operator(cairo.OPERATOR_CLEAR) + mask.ctx.arc(center[0], center[1], + radius=obround.hole_radius * self.scale[0], angle1=0, + angle2=2 * math.pi) + mask.ctx.fill() + + if obround.hole_width > 0 and obround.hole_height > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if rectangle.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height =self.scale_point((obround.hole_width, obround.hole_height)) + lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0), + obround.rotation, center) + lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), + obround.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), + obround.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), + obround.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_polygon(self, polygon, color): + self.ctx.set_operator(cairo.OPERATOR_OVER + if (not self.invert) + and polygon.level_polarity == 'dark' + else cairo.OPERATOR_CLEAR) + with self._clip_primitive(polygon): + with self._new_mask() as mask: + + vertices = polygon.vertices + mask.ctx.set_line_width(0) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + # Start from before the end so it is easy to iterate and make sure + # it is closed + mask.ctx.move_to(*self.scale_point(vertices[-1])) + for v in vertices: + mask.ctx.line_to(*self.scale_point(v)) + mask.ctx.fill() + + center = self.scale_point(polygon.position) + if polygon.hole_radius > 0: + # Render the center clear + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if polygon.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + mask.ctx.set_line_width(0) + mask.ctx.arc(center[0], + center[1], + polygon.hole_radius * self.scale[0], 0, 2 * math.pi) + mask.ctx.fill() + + if polygon.hole_width > 0 and polygon.hole_height > 0: + mask.ctx.set_operator(cairo.OPERATOR_CLEAR + if polygon.level_polarity == 'dark' + and (not self.invert) + else cairo.OPERATOR_OVER) + width, height = self.scale_point((polygon.hole_width, polygon.hole_height)) + lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0), + polygon.rotation, center) + lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0), + polygon.rotation, center) + upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), + polygon.rotation, center) + upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), + polygon.rotation, center) + points = (lower_left, lower_right, upper_right, upper_left) + mask.ctx.move_to(*points[-1]) + for point in points: + mask.ctx.line_to(*point) + mask.ctx.fill() + + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_drill(self, circle, color=None): + color = color if color is not None else self.drill_color + self._render_circle(circle, color) + + def _render_slot(self, slot, color): + start = map(mul, slot.start, self.scale) + end = map(mul, slot.end, self.scale) + + width = slot.diameter + + self.ctx.set_operator(cairo.OPERATOR_OVER + if slot.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) + with self._clip_primitive(slot): + with self._new_mask() as mask: + mask.ctx.set_line_width(width * self.scale[0]) + mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + mask.ctx.move_to(*start) + mask.ctx.line_to(*end) + mask.ctx.stroke() + self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) + + def _render_amgroup(self, amgroup, color): + for primitive in amgroup.primitives: + self.render(primitive) + + def _render_test_record(self, primitive, color): + position = [pos + origin for pos, origin in + zip(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_operator(cairo.OPERATOR_OVER + if primitive.level_polarity == 'dark' and + (not self.invert) else cairo.OPERATOR_CLEAR) + 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) + + def _new_render_layer(self, color=None, mirror=False): + size_in_pixels = self.scale_point(self.size_in_inch) + matrix = copy.copy(self._xform_matrix) + layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) + ctx = cairo.Context(layer) + + if self.invert: + ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0) + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.paint() + if mirror: + matrix.xx = -1.0 + matrix.x0 = self.origin_in_pixels[0] + self.size_in_pixels[0] + self.ctx = ctx + self.ctx.set_matrix(matrix) + self.active_layer = layer + self.active_matrix = matrix + + + def _flatten(self, color=None, alpha=None): + color = color if color is not None else self.color + alpha = alpha if alpha is not None else self.alpha + self.output_ctx.set_source_rgba(*color, alpha=alpha) + self.output_ctx.mask_surface(self.active_layer) + self.ctx = None + self.active_layer = None + self.active_matrix = None + + def _paint_background(self, settings=None): + color = settings.color if settings is not None else self.background_color + alpha = settings.alpha if settings is not None else 1.0 + if not self.has_bg: + self.has_bg = True + self.output_ctx.set_source_rgba(*color, alpha=alpha) + self.output_ctx.paint() + + def _clip_primitive(self, primitive): + """ Clip rendering context to pixel-aligned bounding box + + Calculates pixel- and axis- aligned bounding box, and clips current + context to that region. Improves rendering speed significantly. This + returns a context manager, use as follows: + + with self._clip_primitive(some_primitive): + do_rendering_stuff() + do_more_rendering stuff(with, arguments) + + The context manager will reset the context's clipping region when it + goes out of scope. + + """ + class Clip: + def __init__(clp, primitive): + x_range, y_range = primitive.bounding_box + xmin, xmax = x_range + ymin, ymax = y_range + + # Round bounds to the nearest pixel outside of the primitive + clp.xmin = math.floor(self.scale[0] * xmin) + clp.xmax = math.ceil(self.scale[0] * xmax) + + # We need to offset Y to take care of the difference in y-pos + # caused by flipping the axis. + clp.ymin = math.floor( + (self.scale[1] * ymin) - math.ceil(self.origin_in_pixels[1])) + clp.ymax = math.floor( + (self.scale[1] * ymax) - math.floor(self.origin_in_pixels[1])) + + # Calculate width and height, rounded to the nearest pixel + clp.width = abs(clp.xmax - clp.xmin) + clp.height = abs(clp.ymax - clp.ymin) + + def __enter__(clp): + # Clip current context to primitive's bounding box + self.ctx.rectangle(clp.xmin, clp.ymin, clp.width, clp.height) + self.ctx.clip() + + def __exit__(clp, exc_type, exc_val, traceback): + # Reset context clip region + self.ctx.reset_clip() + + return Clip(primitive) + + def scale_point(self, point): + return tuple([coord * scale for coord, scale in zip(point, self.scale)]) -- cgit From 19a8fb00487ca182bd3127b4def52719d8be3e30 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 13 Dec 2016 20:22:54 -0500 Subject: Add max_width and max_height arguments to --- gerber/render/cairo_backend.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index e6af67f..76be60a 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -97,10 +97,29 @@ class GerberCairoContext(GerberContext): self.dump(filename, verbose) def render_layers(self, layers, filename, theme=THEMES['default'], - verbose=False): + verbose=False, max_width=800, max_height=600): """ Render a set of layers """ + # Calculate scale parameter + x_range = [10000, -10000] + y_range = [10000, -10000] + for layer in layers: + bounds = layer.bounds + if bounds is not None: + layer_x, layer_y = bounds + x_range[0] = min(x_range[0], layer_x[0]) + x_range[1] = max(x_range[1], layer_x[1]) + y_range[0] = min(y_range[0], layer_y[0]) + y_range[1] = max(y_range[1], layer_y[1]) + width = x_range[1] - x_range[0] + height = y_range[1] - y_range[0] + + scale = math.floor(min(float(max_width)/width, float(max_height)/height)) + self.scale = (scale, scale) + self.clear() + + # Render layers bgsettings = theme['background'] for layer in layers: settings = theme.get(layer.layer_class, RenderSettings()) @@ -293,7 +312,7 @@ class GerberCairoContext(GerberContext): angle2=(2 * math.pi)) mask.ctx.fill() - if hasattr(circle, 'hole_diameter') and circle.hole_diameter > 0: + if hasattr(circle, 'hole_diameter') and circle.hole_diameter is not None and circle.hole_diameter > 0: mask.ctx.set_operator(cairo.OPERATOR_CLEAR) mask.ctx.arc(center[0], center[1], @@ -303,6 +322,7 @@ class GerberCairoContext(GerberContext): mask.ctx.fill() if (hasattr(circle, 'hole_width') and hasattr(circle, 'hole_height') + and circle.hole_width is not None and circle.hole_height is not None and circle.hole_width > 0 and circle.hole_height > 0): mask.ctx.set_operator(cairo.OPERATOR_CLEAR if circle.level_polarity == 'dark' -- cgit From 787399992662f1963cfbd2dd9e9b9002b6b131e4 Mon Sep 17 00:00:00 2001 From: Jan Margeta Date: Sat, 15 Apr 2017 14:53:08 +0200 Subject: Fix Cairo backend for svg saving and Python 3 --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 76be60a..8b6c81e 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -139,7 +139,7 @@ class GerberCairoContext(GerberContext): if is_svg: self.surface.finish() self.surface_buffer.flush() - with open(filename, "w") as f: + with open(filename, "wb") as f: self.surface_buffer.seek(0) f.write(self.surface_buffer.read()) f.flush() -- cgit From e0b45108d2fb96ffcf4e6af02dd55fc6aca3e4b2 Mon Sep 17 00:00:00 2001 From: Tom Anderson Date: Wed, 24 May 2017 09:42:23 -0700 Subject: Added bounds argument to render_layer() --- gerber/render/cairo_backend.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 76be60a..3d87f5c 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -77,7 +77,7 @@ class GerberCairoContext(GerberContext): self.output_ctx = cairo.Context(self.surface) def render_layer(self, layer, filename=None, settings=None, bgsettings=None, - verbose=False): + verbose=False, bounds=None): if settings is None: settings = THEMES['default'].get(layer.layer_class, RenderSettings()) if bgsettings is None: @@ -87,7 +87,10 @@ class GerberCairoContext(GerberContext): if verbose: print('[Render]: Rendering Background.') self.clear() - self.set_bounds(layer.bounds) + if bounds is not None: + self.set_bounds(bounds) + else: + self.set_bounds(layer.bounds) self._paint_background(bgsettings) if verbose: print('[Render]: Rendering {} Layer.'.format(layer.layer_class)) -- cgit From f7a719e6f72dc80c34bd6a1015e81f0d2b370689 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 12 Jun 2017 07:58:06 -0400 Subject: Fix error when unpacking colors in cairo backend --- gerber/render/cairo_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 76be60a..7c97cc9 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -565,7 +565,7 @@ class GerberCairoContext(GerberContext): def _flatten(self, color=None, alpha=None): color = color if color is not None else self.color alpha = alpha if alpha is not None else self.alpha - self.output_ctx.set_source_rgba(*color, alpha=alpha) + self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha) self.output_ctx.mask_surface(self.active_layer) self.ctx = None self.active_layer = None @@ -576,7 +576,7 @@ class GerberCairoContext(GerberContext): alpha = settings.alpha if settings is not None else 1.0 if not self.has_bg: self.has_bg = True - self.output_ctx.set_source_rgba(*color, alpha=alpha) + self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha) self.output_ctx.paint() def _clip_primitive(self, primitive): -- cgit From e754f5946885a5c5a9cfa49202ccef4c447d8116 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 4 Jul 2017 01:22:47 -0400 Subject: Remove rest of mixed unpack/kwarg syntax to fix #72 --- gerber/render/cairo_backend.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0a5e550..2e9b143 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -252,10 +252,10 @@ class GerberCairoContext(GerberContext): mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND if isinstance(arc.aperture, Circle) else cairo.LINE_CAP_SQUARE) mask.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': - mask.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + mask.ctx.arc(center[0], center[1], radius, angle1, angle2) else: - mask.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) + mask.ctx.arc_negative(center[0], center[1], radius, + angle1, angle2) mask.ctx.move_to(*end) # ...lame mask.ctx.stroke() @@ -291,11 +291,11 @@ class GerberCairoContext(GerberContext): angle1 = prim.start_angle angle2 = prim.end_angle if prim.direction == 'counterclockwise': - mask.ctx.arc(*center, radius=radius, - angle1=angle1, angle2=angle2) + mask.ctx.arc(center[0], center[1], radius, + angle1, angle2) else: - mask.ctx.arc_negative(*center, radius=radius, - angle1=angle1, angle2=angle2) + mask.ctx.arc_negative(center[0], center[1], radius, + angle1, angle2) mask.ctx.fill() self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0]) @@ -360,7 +360,7 @@ class GerberCairoContext(GerberContext): with self._clip_primitive(rectangle): with self._new_mask() as mask: mask.ctx.set_line_width(0) - mask.ctx.rectangle(*lower_left, width=width, height=height) + mask.ctx.rectangle(lower_left[0], lower_left[1], width, height) mask.ctx.fill() center = self.scale_point(rectangle.position) @@ -418,7 +418,7 @@ class GerberCairoContext(GerberContext): width, height = tuple([abs(coord) for coord in self.scale_point((rectangle.width, rectangle.height))]) - mask.ctx.rectangle(*lower_left, width=width, height=height) + mask.ctx.rectangle(lower_left[0], lower_left[1], width, height) mask.ctx.fill() center = self.scale_point(obround.position) -- cgit From e5597e84a81dbe2f031d1e8bba58a9e1d384f798 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sat, 25 Nov 2017 16:15:00 +0100 Subject: Use positional arguments for cairo.Context.arc cairocffi 0.6 does not support keyword args. --- gerber/render/cairo_backend.py | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 2e9b143..0e3a721 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -308,20 +308,12 @@ class GerberCairoContext(GerberContext): with self._clip_primitive(circle): with self._new_mask() as mask: mask.ctx.set_line_width(0) - mask.ctx.arc(center[0], - center[1], - radius=(circle.radius * self.scale[0]), - angle1=0, - angle2=(2 * math.pi)) + mask.ctx.arc(center[0], center[1], (circle.radius * self.scale[0]), 0, (2 * math.pi)) mask.ctx.fill() if hasattr(circle, 'hole_diameter') and circle.hole_diameter is not None and circle.hole_diameter > 0: mask.ctx.set_operator(cairo.OPERATOR_CLEAR) - mask.ctx.arc(center[0], - center[1], - radius=circle.hole_radius * self.scale[0], - angle1=0, - angle2=2 * math.pi) + mask.ctx.arc(center[0], center[1], circle.hole_radius * self.scale[0], 0, 2 * math.pi) mask.ctx.fill() if (hasattr(circle, 'hole_width') and hasattr(circle, 'hole_height') @@ -371,9 +363,7 @@ class GerberCairoContext(GerberContext): and (not self.invert) else cairo.OPERATOR_OVER) - mask.ctx.arc(center[0], center[1], - radius=rectangle.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) + mask.ctx.arc(center[0], center[1], rectangle.hole_radius * self.scale[0], 0, 2 * math.pi) mask.ctx.fill() if rectangle.hole_width > 0 and rectangle.hole_height > 0: @@ -405,11 +395,7 @@ class GerberCairoContext(GerberContext): # Render circles for circle in (obround.subshapes['circle1'], obround.subshapes['circle2']): center = self.scale_point(circle.position) - mask.ctx.arc(center[0], - center[1], - radius=(circle.radius * self.scale[0]), - angle1=0, - angle2=(2 * math.pi)) + mask.ctx.arc(center[0], center[1], (circle.radius * self.scale[0]), 0, (2 * math.pi)) mask.ctx.fill() # Render Rectangle @@ -425,9 +411,7 @@ class GerberCairoContext(GerberContext): if obround.hole_diameter > 0: # Render the center clear mask.ctx.set_operator(cairo.OPERATOR_CLEAR) - mask.ctx.arc(center[0], center[1], - radius=obround.hole_radius * self.scale[0], angle1=0, - angle2=2 * math.pi) + mask.ctx.arc(center[0], center[1], obround.hole_radius * self.scale[0], 0, 2 * math.pi) mask.ctx.fill() if obround.hole_width > 0 and obround.hole_height > 0: -- cgit From 5245fb925684b4ebe056e6509bfeca6b167903b5 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 5 Jun 2018 08:57:37 -0400 Subject: Fix hard requirement of cairo per #83, and add stubs for required subclass methods to GerberContext per #84 --- gerber/render/cairo_backend.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0e3a721..e1d1408 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -91,7 +91,7 @@ class GerberCairoContext(GerberContext): self.set_bounds(bounds) else: self.set_bounds(layer.bounds) - self._paint_background(bgsettings) + self.paint_background(bgsettings) if verbose: print('[Render]: Rendering {} Layer.'.format(layer.layer_class)) self._render_count += 1 @@ -193,11 +193,11 @@ class GerberCairoContext(GerberContext): def _render_layer(self, layer, settings): self.invert = settings.invert # Get a new clean layer to render on - self._new_render_layer(mirror=settings.mirror) + self.new_render_layer(mirror=settings.mirror) for prim in layer.primitives: self.render(prim) # Add layer to image - self._flatten(settings.color, settings.alpha) + self.flatten(settings.color, settings.alpha) def _render_line(self, line, color): start = self.scale_point(line.start) @@ -530,7 +530,7 @@ class GerberCairoContext(GerberContext): self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) - def _new_render_layer(self, color=None, mirror=False): + def new_render_layer(self, color=None, mirror=False): size_in_pixels = self.scale_point(self.size_in_inch) matrix = copy.copy(self._xform_matrix) layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) @@ -548,8 +548,7 @@ class GerberCairoContext(GerberContext): self.active_layer = layer self.active_matrix = matrix - - def _flatten(self, color=None, alpha=None): + def flatten(self, color=None, alpha=None): color = color if color is not None else self.color alpha = alpha if alpha is not None else self.alpha self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha) @@ -558,7 +557,7 @@ class GerberCairoContext(GerberContext): self.active_layer = None self.active_matrix = None - def _paint_background(self, settings=None): + def paint_background(self, settings=None): color = settings.color if settings is not None else self.background_color alpha = settings.alpha if settings is not None else 1.0 if not self.has_bg: -- cgit From 17924398fa2fe55c933ce004c59c70c2a663f28a Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 6 Jul 2018 19:57:01 +0200 Subject: Fix cairo matrix clone op to not use copy.copy For some reason, copy.copy would barf saying it can't deepcopy cairo matrices. --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0e3a721..b450be0 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -532,7 +532,7 @@ class GerberCairoContext(GerberContext): def _new_render_layer(self, color=None, mirror=False): size_in_pixels = self.scale_point(self.size_in_inch) - matrix = copy.copy(self._xform_matrix) + matrix = cairo.Matrix() * self._xform_matrix layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) ctx = cairo.Context(layer) -- cgit From 2601ae8eab8d7be807bdbed264cd943e441a8da0 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 2 Mar 2019 10:41:37 -0500 Subject: fix reversed layer bug --- gerber/render/cairo_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 7c01319..e1d1408 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -532,7 +532,7 @@ class GerberCairoContext(GerberContext): def new_render_layer(self, color=None, mirror=False): size_in_pixels = self.scale_point(self.size_in_inch) - matrix = cairo.Matrix() * self._xform_matrix + matrix = copy.copy(self._xform_matrix) layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1]) ctx = cairo.Context(layer) -- cgit