/* KLayout Layout Viewer Copyright (C) 2006-2018 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 "dbRS274XReader.h" #include "dbRS274XApertures.h" namespace db { // --------------------------------------------------------------------------------- // RS274XReader implementation RS274XReader::RS274XReader () : GerberFileReader () { init (); } RS274XReader::~RS274XReader () { for (std::vector::const_iterator a = m_apertures.begin (); a != m_apertures.end (); ++a) { if (*a) { delete *a; } } m_apertures.clear (); } bool RS274XReader::does_accept () { return true; } bool RS274XReader::is_clear_polarity () { // Now that we have used the polarity, we not longer guess it. m_guess_polarity = false; return m_neg_polarity ? !m_clear : m_clear; } void RS274XReader::init () { // Initialize reader: m_clear = false; m_guess_polarity = true; m_neg_polarity = false; m_relative = false; m_x = m_y = 0.0; m_current_gcode = -1; m_current_dcode = -1; m_polygon_mode = false; m_axis_mapping = ab_xy; m_current_aperture = 0; m_360deg_circular = false; m_buffer.clear (); m_polygon_points.clear (); for (std::vector::const_iterator a = m_apertures.begin (); a != m_apertures.end (); ++a) { if (*a) { delete *a; } } m_apertures.clear (); m_aperture_macros.clear (); m_current_aperture = 0; } static GerberMetaData::Position parse_position (tl::Extractor &ex) { if (ex.test ("Bot")) { return GerberMetaData::Bottom; } else if (ex.test ("Top")) { return GerberMetaData::Top; } else if (ex.test ("Inr")) { return GerberMetaData::Inner; } else { return GerberMetaData::NoPosition; } } GerberMetaData RS274XReader::do_scan () { GerberMetaData data; char c; // Actually read: while ((c = stream ().skip ()) != 0 && !stream ().at_end ()) { if (c == '%') { stream ().get_char (); while (! stream ().at_end () && (c = stream ().skip ()) != '%') { std::string param; param += stream ().get_char (); if (! stream ().at_end ()) { param += stream ().get_char (); } std::string bl = get_block (); if (param == "TF") { // Extract information tl::Extractor ex (bl); if (ex.test (".ProjectId")) { ex.test (","); data.project_id = ex.get (); } else if (ex.test (".CreationDate")) { ex.test (","); data.creation_date = ex.get (); } else if (ex.test (".GenerationSoftware")) { ex.test (","); data.generation_software = ex.get (); } else if (ex.test (".FileFunction")) { ex.test (","); bool plated = false; if (ex.test ("Copper")) { data.function = GerberMetaData::Copper; ex.test (","); ex.test ("L"); ex.read (data.cu_layer_number); ex.test (","); data.position = parse_position (ex); } else if (ex.test ("Profile")) { data.function = GerberMetaData::Profile; } else if (ex.test ("Soldermask")) { data.function = GerberMetaData::SolderMask; ex.test (","); data.position = parse_position (ex); } else if (ex.test ("Legend")) { data.function = GerberMetaData::Legend; ex.test (","); data.position = parse_position (ex); } else if ((plated = ex.test ("Plated")) || ex.test ("NonPlated")) { data.function = plated ? GerberMetaData::PlatedHole : GerberMetaData::NonPlatedHole; ex.test (","); ex.read (data.from_cu); ex.test (","); ex.read (data.to_cu); } else { data.function = GerberMetaData::NoFunction; } } } } // eat trailing '%' if (! stream ().at_end ()) { stream ().get_char (); } } else { get_block (); } } return data; } void RS274XReader::do_read () { init (); char c; // Actually read: while ((c = stream ().skip ()) != 0 && !stream ().at_end ()) { if (c == '%') { stream ().get_char (); while (! stream ().at_end () && (c = stream ().skip ()) != '%') { std::string param; param += stream ().get_char (); if (stream ().at_end ()) { throw tl::Exception (tl::to_string (tr ("Unexpected EOF"))); } param += stream ().get_char (); if (param == "AS") { read_as_parameter (get_block ()); } else if (param == "FS") { read_fs_parameter (get_block ()); } else if (param == "MI") { read_mi_parameter (get_block ()); } else if (param == "MO") { read_mo_parameter (get_block ()); } else if (param == "OF") { read_of_parameter (get_block ()); } else if (param == "SF") { read_sf_parameter (get_block ()); } else if (param == "IJ") { read_ij_parameter (get_block ()); } else if (param == "IN") { read_in_parameter (get_block ()); } else if (param == "IO") { read_io_parameter (get_block ()); } else if (param == "IP") { read_ip_parameter (get_block ()); } else if (param == "IR") { read_ir_parameter (get_block ()); } else if (param == "PF") { read_pf_parameter (get_block ()); } else if (param == "AD") { read_ad_parameter (get_block ()); } else if (param == "TA" || param == "TD" || param == "TF") { // TA, TD and TF paramters are skipped for layout get_block (); } else if (param == "AB") { std::string dcode = get_block (); if (dcode.empty ()) { if (graphics_stack_empty ()) { throw tl::Exception (tl::to_string (tr ("AB closed without initial opening AB command"))); } else { db::Region region; collect (region); std::string ap = pop_state (); install_block_aperture (ap, region); } } else if (m_polygon_mode) { warn (tl::to_string (tr ("AB command inside polygon sequence (G36/G37) - polygon ignored"))); } else { push_state (dcode); } } else if (param == "AM") { // AM parameters can span multiple data blocks, so collect them std::string am_string; while (! stream ().at_end () && stream ().skip () != '%') { am_string += get_block (); am_string += "*"; } read_am_parameter (am_string); } else if (param == "KO") { read_ko_parameter (get_block ()); } else if (param == "LN") { read_ln_parameter (get_block ()); } else if (param == "LP") { read_lp_parameter (get_block ()); } else if (param == "LM") { read_lm_parameter (get_block ()); } else if (param == "LR") { read_lr_parameter (get_block ()); } else if (param == "LS") { read_ls_parameter (get_block ()); } else if (param == "SR") { read_sr_parameter (get_block ()); } else if (param == "IF") { read_if_parameter (get_block ()); } else { get_block (); warn (tl::to_string (tr ("Parameter ignored: ")) + param); } } // eat trailing '%' stream ().get_char (); } else { // process block bool has_coord = false; double x = m_x, y = m_y; double i = 0.0, j = 0.0; tl::Extractor ex (get_block ().c_str ()); while (! ex.at_end ()) { c = *ex.skip (); ++ex; if (c == 'M') { int mcode = 0; ex.read (mcode); process_mcode (mcode); } else if (c == 'N') { int ncode = 0; ex.read (ncode); // currently N codes are ignored. } else if (c == 'G') { int gcode = -1; ex.read (gcode); if (gcode == 4) { // .. G04 - ignore rest of block break; } else if (gcode == 36) { // .. G36 - enter polygon mode m_polygon_mode = true; m_polygon_points.clear (); m_current_gcode = 1; m_current_dcode = -1; } else if (gcode == 37) { // .. G37 - leave polygon mode m_polygon_mode = false; if (m_polygon_points.size () >= 3) { db::DPolygon poly; poly.assign_hull (m_polygon_points.begin (), m_polygon_points.end ()); produce_polygon (poly, is_clear_polarity ()); } m_current_gcode = -1; m_current_dcode = -1; } else if (gcode == 54) { // .. G54 - tool prepare m_current_gcode = -1; m_current_dcode = -1; } else if (gcode == 70) { // .. G70 - specify inches set_unit (25400); } else if (gcode == 71) { // .. G71 - specify millimeters set_unit (1000); } else if (gcode == 74) { // .. G74 - disable 360 degree circular interpolation m_360deg_circular = false; } else if (gcode == 75) { // .. G74 - enable 360 degree circular interpolation m_360deg_circular = true; } else if (gcode == 90) { // .. G90 - absolute mode m_relative = false; } else if (gcode == 91) { // .. G91 - relative mode m_relative = false; } else if (gcode == 0) { // .. G0 - move m_current_gcode = gcode; } else if (gcode == 2 || gcode == 3) { // .. G2, G3 - circular interpolation m_current_gcode = gcode; } else if (gcode == 1 || gcode == 10 || gcode == 11 || gcode == 12) { // TODO: Handle G10, G11, G12 correctly? // .. G1 - linear interpolation m_current_gcode = 1; } else if (gcode >= 0) { warn (tl::sprintf (tl::to_string (tr ("Invalid 'G' code %d - ignored")), gcode)); } } else if (c == 'X') { double d = read_coord (ex); if (m_relative) { x += d; } else { x = d; } has_coord = true; } else if (c == 'Y') { double d = read_coord (ex); if (m_relative) { y += d; } else { y = d; } has_coord = true; } else if (c == 'I') { i = read_coord (ex); } else if (c == 'J') { j = read_coord (ex); } else if (c == 'D') { int dcode = -1; ex.read (dcode); if (dcode >= 10) { // set current aperture if (dcode >= int (m_apertures.size ()) || m_apertures[dcode] == 0) { throw tl::Exception (tl::to_string (tr ("Aperture code D%d is invalid or undefined")), dcode); } m_current_aperture = m_apertures[dcode]; } else if (dcode <= 3) { m_current_dcode = dcode; if (dcode == 3) { // force a flash here even if there is no explicit coordinate has_coord = true; } } else { warn (tl::sprintf (tl::to_string (tr ("Invalid D code %d ignored")), dcode)); } } else { throw tl::Exception (tl::to_string (tr ("Invalid function code '%c'")), c); } } if (has_coord) { if (m_current_dcode == 2) { // move if (m_polygon_mode) { // D02 strokes close the polygon (and restart a new one) if (m_polygon_points.size () >= 3) { db::DPolygon poly; poly.assign_hull (m_polygon_points.begin (), m_polygon_points.end ()); produce_polygon (poly, is_clear_polarity ()); } m_polygon_points.clear (); } } else if (m_current_dcode == 3) { // flash if (! m_current_aperture) { throw tl::Exception (tl::to_string (tr ("No aperture defined (missing G54 block)"))); } if (m_polygon_mode) { warn (tl::to_string (tr ("D03 blocks are ignored in polygon mode"))); } else { m_current_aperture->produce_flash (db::DCplxTrans (db::DVector (x, y)) * object_trans (), *this, ep (), is_clear_polarity ()); } } else { // only if m_current_dcode == 1? // move with "light" on if (m_current_gcode == 2 || m_current_gcode == 3) { // .. circular interpolation db::DPoint to = db::DPoint (x, y); db::DPoint from = db::DPoint (m_x, m_y); double rx = sqrt (i * i + j * j); double ry = (m_current_gcode == 3) ? rx : -rx; if (rx > 1e-12) { double a0 = 0.0, a1 = 0.0; db::DPoint center; bool has_center = false; double dmin = 0.0; if (! m_360deg_circular) { // look for a good center point for (int v = 0; v < 4; ++v) { db::DPoint c = from + db::DVector ((((v & 1) != 0) ? -i : i), (((v & 2) != 0) ? -j : j)); double aa0 = atan2 ((from.y () - c.y ()) / ry, (from.x () - c.x ()) / rx); double aa1 = atan2 ((to.y () - c.y ()) / ry, (to.x () - c.x ()) / rx); while (aa1 < aa0 - 1e-12) { aa1 += M_PI * 2.0; } // this is the single quadrant interpolation, so we can choose the one which is // properly located for one quadrant. if (aa1 - aa0 - 1e-6 < 0.5 * M_PI) { double d = fabs (c.distance (to) - rx); if (d < dmin || ! has_center) { dmin = d; center = c; a0 = aa0; a1 = aa1; has_center = true; } } } if (! has_center) { warn (tl::sprintf (tl::to_string (tr ("No suitable center point found for G%d code: P1=%s P2=%s I=%g J=%g")), m_current_gcode, from.to_string (), to.to_string (), i, j)); } } else { center = from + db::DVector (i, j); a0 = atan2 ((from.y () - center.y ()) / ry, (from.x () - center.x ()) / rx); a1 = atan2 ((to.y () - center.y ()) / ry, (to.x () - center.x ()) / rx); // multi quadrant interpolation while (a1 < a0 + 1e-12) { a1 += M_PI * 2.0; } has_center = true; } if (has_center) { // TODO: 16 is an arbitrary choice (32 points/full circle) int n = int (ceil (fabs (a1 - a0) / (M_PI / 16.0) - 1e-4)); double da = (a1 - a0) / n; for (int i = 0; i < n; ++i) { double ae = a0 + (i + 1) * da; db::DPoint pe = center + db::DVector (rx * cos (ae), ry * sin (ae)); if (m_polygon_mode) { m_polygon_points.push_back (pe); } else { if (! m_current_aperture) { throw tl::Exception (tl::to_string (tr ("No aperture defined (missing G54 block)"))); } m_current_aperture->produce_linear (db::DCplxTrans (db::DVector (m_x, m_y)) * object_trans (), pe - db::DPoint (m_x, m_y), *this, ep (), is_clear_polarity ()); } m_x = pe.x (); m_y = pe.y (); } } } } else if (m_current_gcode == 0) { // is it correct to ignore G00? warn (tl::to_string (tr ("Block with G00 interpolation mode is ignored"))); } else if (m_current_gcode == 1 || m_current_gcode < 0) { // .. linear interpolation if (m_polygon_mode) { m_polygon_points.push_back (db::DPoint (x, y)); } else { if (! m_current_aperture) { throw tl::Exception (tl::to_string (tr ("No aperture defined (missing G54 block)"))); } m_current_aperture->produce_linear (db::DCplxTrans (db::DVector (m_x, m_y)) * object_trans (), db::DPoint (x, y) - db::DPoint (m_x, m_y), *this, ep (), is_clear_polarity ()); } } else { throw tl::Exception (tl::to_string (tr ("G00 or unspecified 'G' code requires D03"))); } } m_x = x; m_y = y; } } } if (! graphics_stack_empty ()) { throw tl::Exception (tl::to_string (tr ("AB block not closed"))); } } void RS274XReader::process_mcode (int /*mcode*/) { // no processing for M codes currently. } const std::string & RS274XReader::get_block () { progress_checkpoint (); m_buffer.clear (); char c; while (! stream ().at_end () && (c = stream ().get_char ()) != '*') { m_buffer += c; } return m_buffer; } void RS274XReader::read_as_parameter (const std::string &block) { if (block == "AXBY") { m_axis_mapping = ab_xy; } else if (block == "AYBX") { m_axis_mapping = ab_yx; } else { throw tl::Exception (tl::to_string (tr ("Invalid argument '%s' for AS parameter")), block); } } void RS274XReader::read_fs_parameter (const std::string &block) { bool omit_lz = true; int ld = -1; int td = -1; tl::Extractor ex (block.c_str ()); if (ex.test ("L")) { omit_lz = true; } else if (ex.test ("T")) { omit_lz = false; } else if (ex.test ("D")) { // TODO: clarify what to do in that case .. } if (ex.test ("A")) { m_relative = false; } else if (ex.test ("I")) { m_relative = true; } int i; if (ex.test ("N")) { ex.read (i); } if (ex.test ("G")) { ex.read (i); } ex.expect ("X"); ex.read (i); ld = i / 10; td = i % 10; int j = i; ex.expect ("Y"); ex.read (j); if (i != j) { throw tl::Exception (tl::to_string (tr ("X and Y format must be identical currently"))); } if (ex.test ("D")) { ex.read (i); } if (ex.test ("M")) { ex.read (i); } ex.expect_end (); set_format (ld, td, omit_lz); } void RS274XReader::read_mi_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); ex.expect ("A"); int ma = 0; ex.read (ma); ex.expect ("B"); int mb = 0; ex.read (mb); ex.expect_end (); bool mx = (ma != 0); bool my = (mb != 0); if (m_axis_mapping != ab_xy) { std::swap (mx, my); } update_local_mirror (mx, my); } void RS274XReader::read_mo_parameter (const std::string &block) { if (block == "IN") { set_unit (25400); } else if (block == "MM") { set_unit (1000); } else { throw tl::Exception (tl::to_string (tr ("Invalid argument of M0 parameter - must be 'IN' or 'MM', not '%s'")), block); } } void RS274XReader::read_of_parameter (const std::string &block) { // TODO: relationship to IO paramter??? tl::Extractor ex (block.c_str ()); ex.expect ("A"); double ao = 0.0; ex.read (ao); ao *= unit (); ex.expect ("B"); double bo = 0.0; ex.read (bo); bo *= unit (); ex.expect_end (); double ox = ao; double oy = bo; if (m_axis_mapping != ab_xy) { std::swap (ox, oy); } update_local_offset (ox, oy); } void RS274XReader::read_sf_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); ex.expect ("A"); double sx = 1.0; ex.read (sx); ex.expect ("B"); double sy = 1.0; ex.read (sy); ex.expect_end (); if (m_axis_mapping != ab_xy) { std::swap (sx, sy); } if (fabs (sx - sy) > 1e-6) { throw tl::Exception (tl::to_string (tr ("Different scalings for x and y axis is not supported currently."))); } update_local_scale (sx); } void RS274XReader::read_ls_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); double s = 1.0; ex.read (s); update_object_scale (s); } void RS274XReader::read_lr_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); double a = 0.0; ex.read (a); update_object_angle (a); } void RS274XReader::read_lm_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); bool omx = false, omy = false; while (! ex.at_end ()) { if (ex.test ("X")) { omy = true; // "my == mirror at y axis" is "X == mirror along x axis" } else if (ex.test ("Y")) { omx = true; // "mx == mirror at x axis" is "Y == mirror along y axis" } else { break; } } update_object_mirror (omx, omy); } void RS274XReader::read_ij_parameter (const std::string & /*block*/) { warn (tl::to_string (tr ("IJ parameters are ignored currently"))); } void RS274XReader::read_in_parameter (const std::string & /*block*/) { // image name ignored currently } void RS274XReader::read_io_parameter (const std::string &block) { // TODO: clarify: relationship to OF paramter??? tl::Extractor ex (block.c_str ()); ex.expect ("A"); double ao = 0.0; ex.read (ao); ao *= unit (); ex.expect ("B"); double bo = 0.0; ex.read (bo); bo *= unit (); ex.expect_end (); double ox = ao; double oy = bo; if (m_axis_mapping != ab_xy) { std::swap (ox, oy); } update_local_offset (ox, oy); } void RS274XReader::read_ip_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); if (ex.test ("POS")) { set_inverse (false); } else if (ex.test ("NEG")) { set_inverse (true); } ex.expect_end (); } void RS274XReader::read_ir_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); double rot = 0.0; ex.read (rot); update_local_angle (rot); } void RS274XReader::read_pf_parameter (const std::string & /*block*/) { warn (tl::to_string (tr ("PF parameters are ignored"))); } void RS274XReader::read_ad_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); if (ex.at_end ()) { // ignore "%AD*" commands .. return; } ex.expect ("D"); int dcode = 0; ex.read (dcode); if (dcode < 0) { throw tl::Exception (tl::to_string (tr ("Invalid D code for AD parameter"))); } while (int (m_apertures.size ()) <= dcode) { m_apertures.push_back (0); } std::string name; while (*ex && *ex != '*' && *ex != ',') { name += *ex; ++ex; } if (name == "C") { m_apertures[dcode] = new RS274XCircleAperture (*this, ex); } else if (name == "R") { m_apertures[dcode] = new RS274XRectAperture (*this, ex); } else if (name == "O") { m_apertures[dcode] = new RS274XOvalAperture (*this, ex); } else if (name == "P") { m_apertures[dcode] = new RS274XRegularAperture (*this, ex); } else if (m_aperture_macros.find (name) != m_aperture_macros.end ()) { m_apertures[dcode] = new RS274XMacroAperture (*this, name, m_aperture_macros[name], ex); } else { throw tl::Exception (tl::to_string (tr ("Invalid aperture name '%s' (not a macro name and not a standard aperture) for AD parameter")), name); } } void RS274XReader::install_block_aperture (const std::string &d, const db::Region ®ion) { int dcode = 0; try { tl::Extractor ex (d.c_str ()); ex.expect ("D"); ex.read (dcode); ex.expect_end (); } catch (...) { throw tl::Exception (tl::to_string (tr ("Invalid aperture code string for AB command"))); } if (dcode < 0) { throw tl::Exception (tl::to_string (tr ("Invalid D code for AB command"))); } while (int (m_apertures.size ()) <= dcode) { m_apertures.push_back (0); } m_apertures[dcode] = new RS274XRegionAperture (region); } void RS274XReader::read_am_parameter (const std::string &block) { tl::Extractor ex (block.c_str ()); std::string name; while (*ex && *ex != '*') { name += *ex; ++ex; } ex.expect ("*"); m_aperture_macros.insert (std::make_pair (name, std::string (ex.skip ()))); } void RS274XReader::read_ko_parameter (const std::string & /*block*/) { warn (tl::to_string (tr ("KO parameters are not supported currently"))); } void RS274XReader::read_ln_parameter (const std::string & /*block*/) { // TODO: implement layer name } void RS274XReader::read_lp_parameter (const std::string &block) { if (block == "C") { // when we encounter the first LP parameter, and it is a clear layer, we // guess negative polarity (as do some viewers) if (m_guess_polarity) { m_neg_polarity = true; m_guess_polarity = false; } m_clear = true; } else if (block == "D") { if (m_guess_polarity) { m_neg_polarity = false; m_guess_polarity = false; } m_clear = false; } else { throw tl::Exception (tl::to_string (tr ("Invalid argument '%s' for LP parameter")), block); } } void RS274XReader::read_sr_parameter (const std::string &block) { reset_step_and_repeat (); tl::Extractor ex (block.c_str ()); if (ex.at_end ()) { // empty %SR* comment: just reset return; } int nx = 1, ny = 1; double dx = 0.0, dy = 0.0; while (! ex.at_end ()) { if (ex.test ("X")) { ex.read (nx); } else if (ex.test ("Y")) { ex.read (ny); } else if (ex.test ("I")) { ex.read (dx); } else if (ex.test ("J")) { ex.read (dy); } else { break; } } ex.expect_end (); if (nx > 1 || ny > 1) { dx *= unit (); dy *= unit (); std::vector steps; steps.reserve (nx * ny); for (int i = 0; i < nx; ++i) { for (int j = 0; j < ny; ++j) { steps.push_back (db::DVector (i * dx, j * dy)); } } step_and_repeat (steps); } } void RS274XReader::read_if_parameter (const std::string & /*block*/) { warn (tl::to_string (tr ("IF parameters are not supported currently"))); } }