/* KLayout Layout Viewer Copyright (C) 2006-2025 Matthias Koefferlein This program 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 2 of the License, or (at your option) any later version. This program 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, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "dbGerberDrillFileReader.h" namespace db { // --------------------------------------------------------------------------------- // GerberDrillFileReader implementation GerberDrillFileReader::GerberDrillFileReader (int warn_level) : GerberFileReader (warn_level) { init (); } GerberDrillFileReader::~GerberDrillFileReader () { // .. nothing yet .. } bool GerberDrillFileReader::does_accept () { for (int i = 0; i < 100; ++i) { tl::Extractor ex (get_block ().c_str ()); if (ex.test ("M71")) { return true; } else if (ex.test ("M48")) { return true; } else if (ex.test (";")) { if (ex.test ("Holesize")) { return true; } else if (ex.test ("T")) { return true; } } } return false; } void GerberDrillFileReader::init () { // Initialize reader: m_relative = false; m_x = 0.0; m_y = 0.0; m_xoff = 0.0; m_yoff = 0.0; m_current_diameter = 0.0; m_current_qty = 0; m_qty.clear (); m_current_tool = -1; m_tools.clear (); m_recording = false; m_record_pattern = false; m_holes.clear (); m_pattern.clear (); m_in_header = false; m_end_block = 0; m_m02_xoffset = m_m02_yoffset = 0.0; m_routing = false; m_plunged = false; m_linear_interpolation = false; m_format_set = false; } GerberMetaData GerberDrillFileReader::do_scan () { GerberMetaData data; data.function = GerberMetaData::Hole; return data; } void GerberDrillFileReader::do_read () { m_buffer.clear (); init (); stream ().skip (); // Actually read: while (! stream ().at_end ()) { try { process_line (get_block ()); } catch (tl::Exception &ex) { error (ex.msg ()); } } } void GerberDrillFileReader::process_line (const std::string &s) { tl::Extractor ex (s.c_str ()); char c = *ex.skip (); double xi = m_x, yi = m_y; if (ex.at_end ()) { // ignore empty line } else if (ex.test ("%")) { // blocks starting with a '%' are ignored and end the header m_in_header = false; } else if (ex.test (";")) { int t = -1; double d = 0.0; long q = 0; double u = 0; while (! ex.at_end ()) { if (ex.test ("Holesize")) { double x = 0.0; ex.read (x); ex.test ("="); ex.read (d); } else if (ex.test ("Quantity")) { ex.test ("="); ex.read (q); } else if (ex.test ("Tolerance")) { ex.test ("="); double x = 0.0; ex.test ("+"); if (ex.try_read (x)) { ex.test ("/"); ex.try_read (x); } } else if (ex.test ("size")) { ex.test (":"); ex.read (d); } else if (ex.test ("T")) { int x = 0; if (ex.try_read (x)) { t = x; } } else if (ex.test ("MM")) { u = 1000.0; } else if (ex.test ("INCH")) { u = 25400.0; } else if (ex.test ("MILS")) { u = 25.4; } else if (ex.test ("FILE_FORMAT")) { ex.test ("="); int l = -1, t = -1; ex.read (l); ex.expect (":"); ex.read (t); set_format (l, t); m_format_set = true; } else { std::string s; ex.read (s, ""); } } if (u == 0.0) { u = unit (); } if (d > 0) { d *= u; if (t >= 0) { m_tools.insert (std::make_pair (t, d)); } else if (q > 0) { m_qty.push_back (std::make_pair (q, d)); } else { m_current_diameter = d; } } } else if (ex.test ("ICI")) { if (ex.test (",ON")) { m_relative = true; } else if (ex.test (",OFF")) { m_relative = false; } } else if (ex.test ("FMAT")) { // TODO: what to do here? } else if (ex.test ("INCH")) { set_unit (25400.0); if (ex.test (",")) { if (ex.test ("LZ")) { if (m_format_set) { set_format (false); } else { set_format (2, -1, false); // omit trailing zeroes, fixed format for inch system } } else if (ex.test ("TZ")) { if (m_format_set) { set_format (true); } else { set_format (-1, 4, true); // omit leading zeroes, fixed format for inch system } } } ex.expect_end (); } else if (ex.test ("METRIC")) { set_unit (1000.0); if (ex.test (",")) { if (ex.test ("LZ")) { if (m_format_set) { set_format (false); } else { // TODO: what number of digits to use? set_format (3, -1, false); // omit trailing zeroes } } else if (ex.test ("TZ")) { if (m_format_set) { set_format (true); } else { // TODO: what number of digits to use? set_format (-1, 3, true); // omit leading zeroes } } } ex.expect_end (); } else if (ex.test ("M")) { int mcode = 0; ex.read (mcode); if (mcode == 48) { m_in_header = true; } else if (mcode == 95) { m_in_header = false; } else if (mcode == 14 || mcode == 15) { m_plunged = true; } else if (mcode == 16 || mcode == 17) { m_plunged = false; } else if (mcode == 25) { start_step_and_repeat (); } else if (mcode == 8) { stop_step_and_repeat (); } else if (mcode == 97 || mcode == 98) { warn ("Canned text not supported"); while (! stream ().at_end ()) { std::string l; read_line (l); tl::Extractor lex (l.c_str ()); if (lex.test (";")) { // comment -> drop } else { break; } } } else if (mcode == 2) { if (! ex.at_end ()) { while (true) { if (ex.test ("X")) { m_m02_xoffset += read_coord (ex); } else if (ex.test ("Y")) { m_m02_yoffset += read_coord (ex); } else { break; } } bool swapxy = false; double fx = 1.0; double fy = 1.0; while (ex.test ("M")) { int mcode2 = 0; ex.read (mcode2); if (mcode2 == 70) { swapxy = true; } else if (mcode2 == 80) { if (swapxy) { fy = -fy; } else { fx = -fx; } } else if (mcode2 == 90) { if (swapxy) { fx = -fx; } else { fy = -fy; } } } repeat_block (m_m02_xoffset, m_m02_yoffset, fx, fy, swapxy); } } else if (mcode == 1) { end_block (); m_m02_xoffset = 0.0; m_m02_yoffset = 0.0; } } else if (ex.test ("T")) { int tcode = 0; if (ex.try_read (tcode)) { m_current_tool = tcode; c = *ex.skip (); if (c == 'F' || c == 'S' || c == 'C') { while (true) { double d = 0.0; if (ex.test ("F") || ex.test ("S")) { ex.read (d); } else if (ex.test ("C")) { ex.read (d); d *= unit (); m_tools.insert (std::make_pair (tcode, d)); m_current_diameter = d; } else { break; } } } else if (ex.test ("size")) { double d = 0.0; ex.test (":"); ex.read (d); d *= unit (); m_tools.insert (std::make_pair (tcode, d)); m_current_diameter = d; } else { // following specs if (m_tools.find (tcode) == m_tools.end ()) { if (tcode == 0) { // some file formats indicate "no tool" with this code .. m_current_diameter = 0.0; } else { throw tl::Exception (tl::to_string (tr ("Undefined tool code %d")), tcode); } } else { m_current_diameter = m_tools [tcode]; } } } } else if (! m_in_header && ex.test ("P")) { int n = 0; ex.read (n); double dx = 0.0, dy = 0.0; if (ex.test ("X")) { dx = read_coord (ex); } if (ex.test ("Y")) { dy = read_coord (ex); } for (int i = 1; i <= n; ++i) { repeat_pattern (i * dx, i * dy); } } else if (! m_in_header && ex.test ("R")) { int n = 0; ex.read (n); double dx = 0.0, dy = 0.0; bool use_block = false; if (ex.test ("M")) { int mcode = 0; ex.read (mcode); if (mcode == 2) { use_block = true; } } if (ex.test ("X")) { dx = read_coord (ex); } if (ex.test ("Y")) { dy = read_coord (ex); } if (use_block) { for (int i = 1; i < n; ++i) { repeat_block (i * dx, i * dy); } } else { for (int i = 1; i <= n; ++i) { next_hole (); produce_circle (m_x + m_xoff + i * dx, m_y + m_yoff + i * dy, m_current_diameter * 0.5); } m_x += n * dx; m_y += n * dy; } } else if (! m_in_header && (c == 'X' || c == 'Y' || c == 'G')) { double xb = 0.0, yb = 0.0; bool has_xb = false, has_yb = false; while (true) { if (ex.test ("X")) { xb = read_coord (ex); has_xb = true; } else if (ex.test ("Y")) { yb = read_coord (ex); has_yb = true; } else { break; } } int gcode = 0; if (has_xb) { if (m_relative) { m_x += xb; } else { m_x = xb; } } if (has_yb) { if (m_relative) { m_y += yb; } else { m_y = yb; } } if (ex.test ("G")) { ex.read (gcode); if (gcode == 90) { m_relative = false; } else if (gcode == 91) { m_relative = true; } else if (gcode == 93 || gcode == 92) { double xa = 0.0, ya = 0.0; bool has_xa = false, has_ya = false; while (true) { if (ex.test ("X")) { xa = read_coord (ex); has_xa = true; } else if (ex.test ("Y")) { ya = read_coord (ex); has_ya = true; } else { break; } } if (has_xa) { m_xoff += xa; m_x = 0; } if (has_ya) { m_yoff += ya; m_y = 0; } } else if (gcode == 2 || gcode == 3) { // TODO: implement warn ("Circular interpolation not supported currently."); } else if (gcode == 32 || gcode == 33) { // TODO: implement warn ("Routed canned circles not supported currently."); } else if (gcode == 0) { m_routing = true; m_linear_interpolation = false; // process rest of line process_line (std::string (ex.skip ())); return; } else if (gcode == 5) { m_routing = false; m_linear_interpolation = false; } else if (gcode == 1) { m_linear_interpolation = true; // process rest of line process_line (std::string (ex.skip ())); return; } else if (gcode == 83 || gcode == 81 || gcode == 82) { std::vector > coords; while (! stream ().at_end () && coords.size () < 2) { std::string l; read_line (l); tl::Extractor lex (l.c_str ()); if (lex.test (";")) { // comment -> drop } else { coords.push_back (std::make_pair (m_x, m_y)); while (true) { if (lex.test ("X")) { double d = read_coord (lex); if (m_relative) { coords.back ().first += d; } else { coords.back ().first = d; } } else if (lex.test ("Y")) { double d = read_coord (lex); if (m_relative) { coords.back ().second += d; } else { coords.back ().second = d; } } else { break; } } } } if (coords.size () == 2) { begin_pattern (); if (gcode == 83) { double xc = (coords[0].first + coords[1].first) * 0.5; double yc = (coords[0].second + coords[1].second) * 0.5; double xr = xc - coords[0].first; double yr = yc - coords[0].second; for (int i = 0; i < 8; ++i) { double a = i * M_PI / 4; next_hole (); produce_circle (xc + m_xoff + cos (a) * xr - sin (a) * yr, yc + m_yoff + cos (a) * yr + sin (a) * xr, m_current_diameter * 0.5); } } else { double xa = 0.0, ya = 0.0; bool has_xa = false, has_ya = false; while (true) { if (ex.test ("X")) { xa = read_coord (ex); has_xa = true; } else if (ex.test ("Y")) { ya = read_coord (ex); has_ya = true; } else { break; } } if (! has_xa || xa < 1e-6) { xa = 0.1 * 25400.0; } if (! has_ya || ya < 1e-6) { ya = 0.3 * 25400.0; } int n = 0; double dx = 0.0, dy = 0.0; if (fabs (fabs (coords[0].first - coords[1].first) - fabs (ya)) < fabs (fabs (coords[0].second - coords[1].second) - fabs (ya))) { // vertical dx = 0.0; dy = xa * (coords[1].second < coords[0].second ? -1.0 : 1.0); n = std::max (0, int (floor (0.5 + (coords[1].second - coords[0].second) / xa))); } else { // horizontal dy = 0.0; dx = xa * (coords[1].first < coords[0].first ? -1.0 : 1.0); n = std::max (0, int (floor (0.5 + (coords[1].first - coords[0].first) / xa))); } for (int i = 0; i <= n; ++i) { next_hole (); produce_circle (coords [0].first + m_xoff, coords [0].second + m_yoff, m_current_diameter * 0.5); coords[0].first += dx; coords[0].second += dy; next_hole (); produce_circle (coords [1].first + m_xoff, coords [1].second + m_yoff, m_current_diameter * 0.5); coords[1].first -= dx; coords[1].second -= dy; } } end_pattern (); } } else if (gcode == 85) { double xa = 0.0, ya = 0.0; bool has_xa = false, has_ya = false; while (true) { if (ex.test ("X")) { xa = read_coord (ex); has_xa = true; } else if (ex.test ("Y")) { ya = read_coord (ex); has_ya = true; } else { break; } } double x0 = m_x, y0 = m_y; if (has_xa) { if (m_relative) { m_x += xa; } else { m_x = xa; } } if (has_ya) { if (m_relative) { m_y += ya; } else { m_y = ya; } } next_hole (); // produce the slot produce_circle (x0 + m_xoff, y0 + m_yoff, m_current_diameter * 0.5, m_x + m_xoff, m_y + m_yoff); } } else if (has_xb || has_yb) { next_hole (); if (!m_routing) { produce_circle (m_x + m_xoff, m_y + m_yoff, m_current_diameter * 0.5); } else if (m_plunged) { if (m_linear_interpolation) { produce_circle (xi + m_xoff, yi + m_yoff, m_current_diameter * 0.5, m_x + m_xoff, m_y + m_yoff); } } } if (! ex.at_end ()) { warn (tl::sprintf (tl::to_string (tr ("Part of line ignored: %s")), ex.skip ())); } } else if (!m_in_header && c != 0) { warn (tl::to_string (tr ("Statement ignored"))); } } void GerberDrillFileReader::next_hole () { if (m_current_tool < 0 && !m_qty.empty ()) { if (m_current_qty == 0) { m_current_qty = m_qty.front ().first; m_current_diameter = m_qty.front ().second; m_qty.pop_front (); } if (m_current_qty > 0) { --m_current_qty; } } } void GerberDrillFileReader::read_line (std::string &b) { progress_checkpoint (); b.clear (); char c; while (! stream ().at_end ()) { c = stream ().get_char (); if (c == '\n' || c == '\r') { break; } b += c; } c = stream ().peek_char (); if (c == '\n' || c == '\r') { stream ().get_char (); } } const std::string & GerberDrillFileReader::get_block () { read_line (m_buffer); return m_buffer; } void GerberDrillFileReader::begin_pattern () { m_record_pattern = true; m_pattern.clear (); } void GerberDrillFileReader::end_pattern () { m_record_pattern = false; } void GerberDrillFileReader::repeat_pattern (double dx, double dy) { if (m_record_pattern) { return; } for (size_t i = 0; i < m_pattern.size (); ++i) { produce_circle (m_pattern [i].x + dx, m_pattern [i].y + dy, m_pattern [i].r, m_pattern [i].ex + dx, m_pattern [i].ey + dy); } } void GerberDrillFileReader::start_step_and_repeat () { m_holes.clear (); m_recording = true; m_end_block = 0; } void GerberDrillFileReader::stop_step_and_repeat () { m_recording = false; for (size_t i = 0; i < m_holes.size (); ++i) { produce_circle (m_holes [i].x, m_holes [i].y, m_holes [i].r, m_holes [i].ex, m_holes [i].ey); } } void GerberDrillFileReader::repeat_block (double dx, double dy, double fx, double fy, bool swapxy) { for (size_t i = 0; i < m_end_block; ++i) { m_holes.push_back (m_holes [i]); m_holes.back ().x -= m_xoff; m_holes.back ().y -= m_yoff; m_holes.back ().ex -= m_xoff; m_holes.back ().ey -= m_yoff; m_holes.back ().x *= fx; m_holes.back ().y *= fy; m_holes.back ().ex *= fx; m_holes.back ().ey *= fy; if (swapxy) { std::swap (m_holes.back ().x, m_holes.back ().y); std::swap (m_holes.back ().ex, m_holes.back ().ey); } m_holes.back ().x += m_xoff; m_holes.back ().y += m_yoff; m_holes.back ().ex += m_xoff; m_holes.back ().ey += m_yoff; m_holes.back ().x += dx; m_holes.back ().y += dy; m_holes.back ().ex += dx; m_holes.back ().ey += dy; } } void GerberDrillFileReader::end_block () { m_end_block = m_holes.size (); } void GerberDrillFileReader::produce_circle (double cx, double cy, double r, double ex, double ey) { if (m_record_pattern) { m_pattern.push_back (DrillHoleDescriptor (cx, cy, r, ex, ey)); } if (m_recording) { m_holes.push_back (DrillHoleDescriptor (cx, cy, r, ex, ey)); } else { produce_circle_raw (cx, cy, r, ex, ey); } } void GerberDrillFileReader::produce_circle_raw (double cx, double cy, double r, double ex, double ey) { double mx = cx - ex; double my = cy - ey; double m = sqrt (mx * mx + my * my); if (m < 1e-6) { mx = r; my = 0.0; } else { mx *= r / m; my *= r / m; } double nx = -my; double ny = mx; std::vector points; int n_circle = get_circle_points (); // adjust the radius so we get a outer approximation of the circle: // r *= 1.0 / cos (M_PI / double (n_circle)); int i = 0; for (; i < n_circle / 2; ++i) { double a = M_PI * 2.0 * (double (i) / double (n_circle)); points.push_back (db::DPoint (cx + nx * cos (a) + mx * sin (a), cy + ny * cos (a) + my * sin (a))); } for (; i < n_circle; ++i) { double a = M_PI * 2.0 * (double (i) / double (n_circle)); points.push_back (db::DPoint (ex + nx * cos (a) + mx * sin (a), ey + ny * cos (a) + my * sin (a))); } db::DPolygon p; p.assign_hull (points.begin (), points.end ()); produce_polygon (p, false); } }