/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 2 -*- */ /* * gerber-output-dev.cc * Copyright (C) 2018 David Barksdale * * pdf2gerber is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * pdf2gerber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include "gerber-output-dev.h" static double midpoint(double a, double b, double c, double d) { return (a + 3 * (b + c) + d) / 8; } static void center(double x0, double y0, double x1, double y1, double x2, double y2, double *x, double *y) { double idet = 1 / ((x0 - x1) * (y1 - y2) - (x1 - x2) * (y0 - y1)); double offset = x1 * x1 + y1 * y1; double m01 = (x0 * x0 + y0 * y0 - offset) / 2; double m12 = (offset - x2 * x2 - y2 * y2) / 2; *x = (m01 * (y1 - y2) - m12 * (y0 - y1)) * idet; *y = (m12 * (x0 - x1) - m01 * (x1 - x2)) * idet; } static bool clockwise(double x0, double y0, double x1, double y1, double x2, double y2) { return (x1 - x0) * (y2 - y0) < (x2 - x0) * (y1 - y0); } // Constructor. GerberOutputDev::GerberOutputDev() { // Set format to 6.6 std::cout << "%FSLAX66Y66*%" << std::endl // Set units to mm << "%MOMM*%" << std::endl // Set multi-quadrant, why would you ever not want this? << "G75*" << std::endl; } // Does this device use upside-down coordinates? // (Upside-down means (0,0) is the top left corner of the page.) GBool GerberOutputDev::upsideDown() { return gFalse; } // Does this device use drawChar() or drawString()? GBool GerberOutputDev::useDrawChar() { return gTrue; } // Does this device use beginType3Char/endType3Char? Otherwise, // text in Type 3 fonts will be drawn with drawChar/drawString. GBool GerberOutputDev::interpretType3Chars() { return gFalse; } // Start a page. void GerberOutputDev::startPage(int pageNum, GfxState *state, XRef *xref) { std::cout << "G04 startPage " << pageNum << "*" << std::endl; } // End a page. void GerberOutputDev::endPage() { std::cout << "G04 endPage*" << std::endl; std::cout << "M02*" << std::endl; } // Dump page contents to display. void GerberOutputDev::dump() { //std::cerr << "dump" << std::endl; } //----- path painting void GerberOutputDev::stroke(GfxState *state) { GfxPath *path = state->getPath(); double width = state->getTransformedLineWidth(); GfxGray gray; state->getStrokeGray(&gray); std::cout << "G04 stroke width " << width << " gray " << colToDbl(gray) << "*" << std::endl; // McCAD seems to always generate vertical or horizontal strokes. // Since stoking an aperature will add stuff to the ends of the stroke // we're just going to make a region. if (1 != path->getNumSubpaths()) { std::cerr << "Stroke with more than 1 sub-path" << std::endl; return; } GfxSubpath *subpath = path->getSubpath(0); if (0 != state->getLineCap()) { std::cerr << "Stoke with non-but line cap" << std::endl; return; } if (colToDbl(gray) < 0.5) { // Set polarity to dark std::cout << "%LPD*%" << std::endl; } else { // Set polarity to clear std::cout << "%LPC*%" << std::endl; } std::cout << "G36*" << std::endl; if (2 == subpath->getNumPoints()) { double x0, y0, x1, y1; state->transform(subpath->getX(0), subpath->getY(0), &x0, &y0); state->transform(subpath->getX(1), subpath->getY(1), &x1, &y1); double dx = x1 - x0; double dy = y1 - y0; double l = sqrt(dx * dx + dy * dy); // half-width perpendicular vector double hwx = dy / l * width / 2; double hwy = -dx / l * width / 2; std::cout << "X" << coord(x0 + hwx) << "Y" << coord(y0 + hwy) << "D02*" << std::endl; std::cout << "G01*" << std::endl; std::cout << "X" << coord(x0 - hwx) << "Y" << coord(y0 - hwy) << "D01*" << std::endl; std::cout << "X" << coord(x1 - hwx) << "Y" << coord(y1 - hwy) << "D01*" << std::endl; std::cout << "X" << coord(x1 + hwx) << "Y" << coord(y1 + hwy) << "D01*" << std::endl; } else if (13 == subpath->getNumPoints()) { // I lied, sometimes there are circles double x0, y0, x1, y1; state->transform(subpath->getX(0), subpath->getY(0), &x0, &y0); state->transform(subpath->getX(6), subpath->getY(6), &x1, &y1); double cx = (x0 + x1) / 2; double cy = (y0 + y1) / 2; double hw = width / 2; std::cout << "G04 x0 " << x0 << " y0 " << y0 << " x1 " << x1 << " y1 " << y1 << "*" << std::endl; // Make outer circle std::cout << "X" << coord(x0 - hw) << "Y" << coord(y0) << "D02*" << std::endl; std::cout << "G02*" << std::endl; std::cout << "X" << coord(x1 + hw) << "Y" << coord(y1) << "I" << coord(cx - (x0 - hw)) << "J" << coord(cy - y0) << "D01*" << std::endl; std::cout << "X" << coord(x0 - hw) << "Y" << coord(y0) << "I" << coord(cx - (x1 + hw)) << "J" << coord(cy - y1) << "D01*" << std::endl; // Make inner circle std::cout << "G01*" << std::endl; std::cout << "X" << coord(x0 + hw) << "Y" << coord(y0) << "D01*" << std::endl; std::cout << "G03*" << std::endl; std::cout << "X" << coord(x1 - hw) << "Y" << coord(y1) << "I" << coord(cx - (x0 + hw)) << "J" << coord(cy - y0) << "D01*" << std::endl; std::cout << "X" << coord(x0 + hw) << "Y" << coord(y0) << "I" << coord(cx - (x1 - hw)) << "J" << coord(cy - y1) << "D01*" << std::endl; } else { std::cerr << "Stroke with points I can't handle" << std::endl; } std::cout << "G37*" << std::endl; } void GerberOutputDev::fill(GfxState *state) { // McCAD seems to generate fills for any shape that's not a rectangle, // rectangles are made with either vertical or horizontal strokes. GfxPath *path = state->getPath(); int nsubpaths = path->getNumSubpaths(); GfxGray gray; state->getFillGray(&gray); std::cout << "G04 fill " << nsubpaths << " subpath(s) gray " << colToDbl(gray) << "*" << std::endl; if (colToDbl(gray) < 0.5) { // Set polarity to dark std::cout << "%LPD*%" << std::endl; } else { // Set polarity to clear std::cout << "%LPC*%" << std::endl; } // Use a filled region std::cout << "G36*" << std::endl; for (int i = 0; i < nsubpaths; ++i) { GfxSubpath *subpath = path->getSubpath(i); int npoints = subpath->getNumPoints(); int j = 1; double x, y; state->transform(subpath->getX(0), subpath->getY(0), &x, &y); std::cout << "X" << coord(x) << "Y" << coord(y) << "D02*" << std::endl; while (j < npoints) { if (subpath->getCurve(j)) { // This is a bezier curve, approximate it with a single arc! double x0, y0, x1, y1, x2, y2; state->transform(subpath->getX(j), subpath->getY(j), &x0, &y0); state->transform(subpath->getX(j + 1), subpath->getY(j + 1), &x1, &y1); state->transform(subpath->getX(j + 2), subpath->getY(j + 2), &x2, &y2); j += 3; double mx = midpoint(x, x0, x1, x2); double my = midpoint(y, y0, y1, y2); double cx, cy; center(x, y, mx, my, x2, y2, &cx, &cy); if (clockwise(x0, y0, mx, my, x2, y2)) { // Set clockwise circular interpolation std::cout << "G02*" << std::endl; } else { // Set counterclockwise circular interpolation std::cout << "G03*" << std::endl; } // Arc to x,y with center offset i,j std::cout << "X" << coord(x2) << "Y" << coord(y2) << "I" << coord(cx - x) << "J" << coord(cy - y) << "D01*" << std::endl; x = x2; y = y2; } else { state->transform(subpath->getX(j), subpath->getY(j), &x, &y); // Set linear interpolation std::cout << "G01*" << std::endl // Line to x,y << "X" << coord(x) << "Y" << coord(y) << "D01*" << std::endl; ++j; } } } std::cout << "G37*" << std::endl; } //----- text drawing void GerberOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, Unicode *u, int uLen) { std::cout << "G04 drawChar(" << x << ", " << y << ", " << dx << ", " << dy << ", " << originX << ", " << originY << ", ...)*" << std::endl; } int GerberOutputDev::coord(double decimal) { return round(decimal * 1000000); }