klayout/src/plugins/streamers/magic/db_plugin/dbMAGReader.cc

863 lines
20 KiB
C++
Raw Normal View History

2019-11-25 00:07:56 +01:00
/*
KLayout Layout Viewer
Copyright (C) 2006-2019 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 "dbMAGReader.h"
#include "dbStream.h"
#include "dbObjectWithProperties.h"
#include "dbArray.h"
#include "dbStatic.h"
#include "tlException.h"
#include "tlString.h"
#include "tlClassRegistry.h"
#include <cctype>
namespace db
{
// ---------------------------------------------------------------
// MAGReader
MAGReader::MAGReader (tl::InputStream &s)
: m_stream (s),
m_progress (tl::to_string (tr ("Reading MAG file")), 1000),
m_lambda (1.0), m_dbu (0.001)
{
m_progress.set_format (tl::to_string (tr ("%.0fk lines")));
m_progress.set_format_unit (1000.0);
m_progress.set_unit (100000.0);
}
MAGReader::~MAGReader ()
{
// .. nothing yet ..
}
const LayerMap &
MAGReader::read (db::Layout &layout, const db::LoadLayoutOptions &options)
{
prepare_layers ();
const db::MAGReaderOptions &specific_options = options.get_options<db::MAGReaderOptions> ();
m_lambda = specific_options.lambda;
m_dbu = specific_options.dbu;
m_lib_paths = specific_options.lib_paths;
db::LayerMap lm = specific_options.layer_map;
lm.prepare (layout);
set_layer_map (lm);
set_create_layers (specific_options.create_other_layers);
set_keep_layer_names (specific_options.keep_layer_names);
do_read (layout);
finish_layers (layout);
return layer_map ();
}
const LayerMap &
MAGReader::read (db::Layout &layout)
{
return read (layout, db::LoadLayoutOptions ());
}
void
MAGReader::error (const std::string &msg)
{
throw MAGReaderException (msg, m_stream.line_number (), m_stream.source ());
}
void
MAGReader::warn (const std::string &msg)
{
// TODO: compress
tl::warn << msg
<< tl::to_string (tr (" (line=")) << m_stream.line_number ()
<< tl::to_string (tr (", file=")) << m_stream.source ()
<< ")";
}
#if 0 // @@@
/**
* @brief Skip blanks in the sense of MAG
* A blank in MAG is "any ASCII character except digit, upperChar, '-', '(', ')', or ';'"
*/
void
MAGReader::skip_blanks()
{
while (! m_stream.at_end ()) {
char c = m_stream.peek_char ();
if (isupper (c) || isdigit (c) || c == '-' || c == '(' || c == ')' || c == ';') {
return;
}
m_stream.get_char ();
}
}
/**
* @brief Skips separators
*/
void
MAGReader::skip_sep ()
{
while (! m_stream.at_end ()) {
char c = m_stream.peek_char ();
if (isdigit (c) || c == '-' || c == '(' || c == ')' || c == ';') {
return;
}
m_stream.get_char ();
}
}
/**
* @brief Skip comments
* This assumes that the reader is after the first '(' and it will stop
* after the final ')'.
*/
void
MAGReader::skip_comment ()
{
char c;
int bl = 0;
while (! m_stream.at_end () && ((c = m_stream.get_char ()) != ')' || bl > 0)) {
// check for nested comments (bl is the nesting level)
if (c == '(') {
++bl;
} else if (c == ')') {
--bl;
}
}
}
/**
* @brief Gets a character and issues an error if the stream is at the end
*/
char
MAGReader::get_char ()
{
if (m_stream.at_end ()) {
error ("Unexpected end of file");
return 0;
} else {
return m_stream.get_char ();
}
}
/**
* @brief Tests whether the next character is a semicolon (after blanks)
*/
bool
MAGReader::test_semi ()
{
skip_blanks ();
if (! m_stream.at_end () && m_stream.peek_char () == ';') {
return true;
} else {
return false;
}
}
/**
* @brief Tests whether a semicolon follows and issue an error if not
*/
void
MAGReader::expect_semi ()
{
if (! test_semi ()) {
error ("Expected ';' command terminator");
} else {
get_char ();
}
}
/**
* @brief Skips all until the next semicolon
*/
void
MAGReader::skip_to_end ()
{
while (! m_stream.at_end () && m_stream.get_char () != ';') {
;
}
}
/**
* @brief Fetches an integer
*/
int
MAGReader::read_integer_digits ()
{
if (m_stream.at_end () || ! isdigit (m_stream.peek_char ())) {
error ("Digit expected");
}
int i = 0;
while (! m_stream.at_end () && isdigit (m_stream.peek_char ())) {
if (i > std::numeric_limits<int>::max () / 10) {
error ("Integer overflow");
while (! m_stream.at_end () && isdigit (m_stream.peek_char ())) {
m_stream.get_char ();
}
return 0;
}
char c = m_stream.get_char ();
i = i * 10 + int (c - '0');
}
return i;
}
/**
* @brief Fetches an integer
*/
int
MAGReader::read_integer ()
{
skip_sep ();
return read_integer_digits ();
}
/**
* @brief Fetches a signed integer
*/
int
MAGReader::read_sinteger ()
{
skip_sep ();
bool neg = false;
if (m_stream.peek_char () == '-') {
m_stream.get_char ();
neg = true;
}
int i = read_integer_digits ();
return neg ? -i : i;
}
/**
* @brief Fetches a string (layer name)
*/
const std::string &
MAGReader::read_name ()
{
skip_blanks ();
m_cmd_buffer.clear ();
if (m_stream.at_end ()) {
return m_cmd_buffer;
}
// Note: officially only upper and digits are allowed in names. But we allow lower case and "_" too ...
while (! m_stream.at_end () && (isupper (m_stream.peek_char ()) || islower (m_stream.peek_char ()) || m_stream.peek_char () == '_' || isdigit (m_stream.peek_char ()))) {
m_cmd_buffer += m_stream.get_char ();
}
return m_cmd_buffer;
}
/**
* @brief Fetches a string (in labels, texts)
*/
const std::string &
MAGReader::read_string ()
{
m_stream.skip ();
m_cmd_buffer.clear ();
if (m_stream.at_end ()) {
return m_cmd_buffer;
}
char q = m_stream.peek_char ();
if (q == '"' || q == '\'') {
get_char ();
// read a quoted string (KLayout extension)
while (! m_stream.at_end () && m_stream.peek_char () != q) {
char c = m_stream.get_char ();
if (c == '\\' && ! m_stream.at_end ()) {
c = m_stream.get_char ();
}
m_cmd_buffer += c;
}
if (! m_stream.at_end ()) {
get_char ();
}
} else {
while (! m_stream.at_end () && !isspace (m_stream.peek_char ()) && m_stream.peek_char () != ';') {
m_cmd_buffer += m_stream.get_char ();
}
}
return m_cmd_buffer;
}
/**
* @brief Reads a double value (extension)
*/
double
MAGReader::read_double ()
{
m_stream.skip ();
// read a quoted string (KLayout extension)
m_cmd_buffer.clear ();
while (! m_stream.at_end () && (isdigit (m_stream.peek_char ()) || m_stream.peek_char () == '.' || m_stream.peek_char () == '-' || m_stream.peek_char () == 'e' || m_stream.peek_char () == 'E')) {
m_cmd_buffer += m_stream.get_char ();
}
double v = 0.0;
tl::from_string (m_cmd_buffer, v);
return v;
}
bool
MAGReader::read_cell (db::Layout &layout, db::Cell &cell, double sf, int level)
{
if (fabs (sf - floor (sf + 0.5)) > 1e-6) {
warn ("Scaling factor is not an integer - snapping errors may occur in cell '" + m_cellname + "'");
}
int nx = 0, ny = 0, dx = 0, dy = 0;
int layer = -2; // no layer defined yet.
int path_mode = -1;
size_t insts = 0;
size_t shapes = 0;
size_t layer_specs = 0;
std::vector <db::Point> poly_pts;
while (true) {
skip_blanks ();
char c = get_char ();
if (c == ';') {
// empty command
} else if (c == '(') {
skip_comment ();
} else if (c == 'E') {
if (level > 0) {
error ("'E' command must be outside a cell specification");
}
skip_blanks ();
break;
} else if (c == 'D') {
skip_blanks ();
c = get_char ();
if (c == 'S') {
// DS command:
// "D" blank* "S" integer (sep integer sep integer)?
unsigned int n = 0, denom = 1, divider = 1;
n = read_integer ();
if (! test_semi ()) {
denom = read_integer ();
divider = read_integer ();
if (divider == 0) {
error ("'DS' command: divider cannot be zero");
}
}
expect_semi ();
std::string outer_cell = "C" + tl::to_string (n);
std::swap (m_cellname, outer_cell);
std::map <unsigned int, db::cell_index_type>::const_iterator c = m_cells_by_id.find (n);
db::cell_index_type ci;
if (c == m_cells_by_id.end ()) {
ci = layout.add_cell (m_cellname.c_str ());
m_cells_by_id.insert (std::make_pair (n, ci));
} else {
ci = c->second;
}
db::Cell &cell = layout.cell (ci);
read_cell (layout, cell, sf * double (denom) / double (divider), level + 1);
std::swap (m_cellname, outer_cell);
} else if (c == 'F') {
// DF command:
// "D" blank* "F"
if (level == 0) {
error ("'DF' command must be inside a cell specification");
}
// skip the rest of the command
skip_to_end ();
break;
} else if (c == 'D') {
// DD command:
// "D" blank* "D" integer
read_integer ();
warn ("'DD' command ignored");
skip_to_end ();
} else {
error ("Invalid 'D' sub-command");
skip_to_end ();
}
} else if (c == 'C') {
// C command:
// "C" integer transformation
// transformation := (blank* ("T" point |"M" blank* "X" |"M" blank* "Y" |"R" point)*)*
++insts;
unsigned int n = read_integer ();
std::map <unsigned int, db::cell_index_type>::const_iterator c = m_cells_by_id.find (n);
if (c == m_cells_by_id.end ()) {
std::string cn = "C" + tl::to_string (n);
c = m_cells_by_id.insert (std::make_pair (n, layout.add_cell (cn.c_str ()))).first;
}
db::DCplxTrans trans;
while (! test_semi ()) {
skip_blanks ();
char ct = get_char ();
if (ct == 'M') {
skip_blanks ();
char ct2 = get_char ();
if (ct2 == 'X') {
trans = db::DCplxTrans (db::FTrans::m90) * trans;
} else if (ct2 == 'Y') {
trans = db::DCplxTrans (db::FTrans::m0) * trans;
} else {
error ("Invalid 'M' transformation specification");
// skip everything to avoid more errors here
while (! test_semi ()) {
get_char ();
}
}
} else if (ct == 'T') {
int x = read_sinteger ();
int y = read_sinteger ();
trans = db::DCplxTrans (db::DVector (x * sf, y * sf)) * trans;
} else if (ct == 'R') {
int x = read_sinteger ();
int y = read_sinteger ();
if (y != 0 || x != 0) {
double a = atan2 (double (y), double (x)) * 180.0 / M_PI;
trans = db::DCplxTrans (1.0, a, false, db::DVector ()) * trans;
}
} else {
error ("Invalid transformation specification");
// skip everything to avoid more errors here
while (! test_semi ()) {
get_char ();
}
}
}
if (nx > 0 || ny > 0) {
if (trans.is_ortho () && ! trans.is_mag ()) {
cell.insert (db::CellInstArray (db::CellInst (c->second), db::Trans (db::ICplxTrans (trans)), db::Vector (dx * sf, 0.0), db::Vector (0.0, dy * sf), std::max (1, nx), std::max (1, ny)));
} else {
cell.insert (db::CellInstArray (db::CellInst (c->second), db::ICplxTrans (trans), db::Vector (dx * sf, 0.0), db::Vector (0.0, dy * sf), std::max (1, nx), std::max (1, ny)));
}
} else {
if (trans.is_ortho () && ! trans.is_mag ()) {
cell.insert (db::CellInstArray (db::CellInst (c->second), db::Trans (db::ICplxTrans (trans))));
} else {
cell.insert (db::CellInstArray (db::CellInst (c->second), db::ICplxTrans (trans)));
}
}
nx = ny = 0;
dx = dy = 0;
expect_semi ();
} else if (c == 'L') {
skip_blanks ();
++layer_specs;
std::string name = read_name ();
if (name.empty ()) {
error ("Missing layer name in 'L' command");
}
std::pair<bool, unsigned int> ll = open_layer (layout, name);
if (! ll.first) {
layer = -1; // ignore geometric objects on this layer
} else {
layer = int (ll.second);
}
expect_semi ();
} else if (c == 'B') {
++shapes;
if (layer < 0) {
if (layer < -1) {
warn ("'B' command ignored since no layer was selected");
}
skip_to_end ();
} else {
unsigned int w = 0, h = 0;
int x = 0, y = 0;
w = read_integer ();
h = read_integer ();
x = read_sinteger ();
y = read_sinteger ();
int rx = 0, ry = 0;
if (! test_semi ()) {
rx = read_sinteger ();
ry = read_sinteger ();
}
if (rx >= 0 && ry == 0) {
cell.shapes ((unsigned int) layer).insert (db::Box (sf * (x - 0.5 * w), sf * (y - 0.5 * h), sf * (x + 0.5 * w), sf * (y + 0.5 * h)));
} else {
double n = 1.0 / sqrt (double (rx) * double (rx) + double (ry) * double (ry));
double xw = sf * w * 0.5 * rx * n, yw = sf * w * 0.5 * ry * n;
double xh = -sf * h * 0.5 * ry * n, yh = sf * h * 0.5 * rx * n;
db::Point c (sf * x, sf * y);
db::Point points [4];
points [0] = c + db::Vector (-xw - xh, -yw - yh);
points [1] = c + db::Vector (-xw + xh, -yw + yh);
points [2] = c + db::Vector (xw + xh, yw + yh);
points [3] = c + db::Vector (xw - xh, yw - yh);
db::Polygon p;
p.assign_hull (points, points + 4);
cell.shapes ((unsigned int) layer).insert (p);
}
expect_semi ();
}
} else if (c == 'P') {
++shapes;
if (layer < 0) {
if (layer < -1) {
warn ("'P' command ignored since no layer was selected");
}
skip_to_end ();
} else {
poly_pts.clear ();
while (! test_semi ()) {
int rx = read_sinteger ();
int ry = read_sinteger ();
poly_pts.push_back (db::Point (sf * rx, sf * ry));
}
db::Polygon p;
p.assign_hull (poly_pts.begin (), poly_pts.end ());
cell.shapes ((unsigned int) layer).insert (p);
expect_semi ();
}
} else if (c == 'R') {
++shapes;
if (layer < 0) {
if (layer < -1) {
warn ("'R' command ignored since no layer was selected");
}
skip_to_end ();
} else {
int w = read_integer ();
poly_pts.clear ();
int rx = read_sinteger ();
int ry = read_sinteger ();
poly_pts.push_back (db::Point (sf * rx, sf * ry));
db::Path p (poly_pts.begin (), poly_pts.end (), db::coord_traits <db::Coord>::rounded (sf * w), db::coord_traits <db::Coord>::rounded (sf * w / 2), db::coord_traits <db::Coord>::rounded (sf * w / 2), true);
cell.shapes ((unsigned int) layer).insert (p);
expect_semi ();
}
} else if (c == 'W') {
++shapes;
if (layer < 0) {
if (layer < -1) {
warn ("'W' command ignored since no layer was selected");
}
skip_to_end ();
} else {
int w = read_integer ();
poly_pts.clear ();
while (! test_semi ()) {
int rx = read_sinteger ();
int ry = read_sinteger ();
poly_pts.push_back (db::Point (sf * rx, sf * ry));
}
if (path_mode == 0 || (path_mode < 0 && m_wire_mode == 1)) {
// Flush-ended paths
db::Path p (poly_pts.begin (), poly_pts.end (), db::coord_traits <db::Coord>::rounded (sf * w), 0, 0, false);
cell.shapes ((unsigned int) layer).insert (p);
} else if (path_mode == 1 || (path_mode < 0 && m_wire_mode == 2)) {
// Round-ended paths
db::Path p (poly_pts.begin (), poly_pts.end (), db::coord_traits <db::Coord>::rounded (sf * w), db::coord_traits <db::Coord>::rounded (sf * w / 2), db::coord_traits <db::Coord>::rounded (sf * w / 2), true);
cell.shapes ((unsigned int) layer).insert (p);
} else {
// Square-ended paths
db::Path p (poly_pts.begin (), poly_pts.end (), db::coord_traits <db::Coord>::rounded (sf * w), db::coord_traits <db::Coord>::rounded (sf * w / 2), db::coord_traits <db::Coord>::rounded (sf * w / 2), false);
cell.shapes ((unsigned int) layer).insert (p);
}
expect_semi ();
}
} else if (isdigit (c)) {
char cc = m_stream.peek_char ();
if (c == '9' && cc == '3') {
get_char ();
nx = read_sinteger ();
dx = read_sinteger ();
ny = read_sinteger ();
dy = read_sinteger ();
} else if (c == '9' && cc == '4') {
get_char ();
// label at location
++shapes;
if (layer < 0) {
if (layer < -1) {
warn ("'94' command ignored since no layer was selected");
}
} else {
std::string text = read_string ();
int rx = read_sinteger ();
int ry = read_sinteger ();
double h = 0.0;
if (! test_semi ()) {
h = read_double ();
}
db::Text t (text.c_str (), db::Trans (db::Vector (sf * rx, sf * ry)), db::coord_traits <db::Coord>::rounded (h / m_dbu));
cell.shapes ((unsigned int) layer).insert (t);
}
} else if (c == '9' && cc == '5') {
get_char ();
// label in box
++shapes;
if (layer < 0) {
if (layer < -1) {
warn ("'95' command ignored since no layer was selected");
}
} else {
std::string text = read_string ();
// TODO: box dimensions are ignored currently.
read_sinteger ();
read_sinteger ();
int rx = read_sinteger ();
int ry = read_sinteger ();
db::Text t (text.c_str (), db::Trans (db::Vector (sf * rx, sf * ry)));
cell.shapes ((unsigned int) layer).insert (t);
}
} else if (c == '9' && cc == '8') {
get_char ();
// Path type (0: flush, 1: round, 2: square)
path_mode = read_integer ();
} else if (c == '9' && ! isdigit (cc)) {
m_cellname = read_string ();
m_cellname = layout.uniquify_cell_name (m_cellname.c_str ());
layout.rename_cell (cell.cell_index (), m_cellname.c_str ());
} else {
// ignore the command
}
skip_to_end ();
} else {
// ignore the command
warn ("Unknown command ignored");
skip_to_end ();
}
}
// The cell is considered non-empty if it contains more than one instance, at least one shape or
// has at least one "L" command.
return insts > 1 || shapes > 0 || layer_specs > 0;
}
#endif
void
MAGReader::do_read (db::Layout &layout)
{
#if 0 // @@@
tl::SelfTimer timer (tl::verbosity () >= 21, "File read");
try {
double sf = 0.01 / m_dbu;
layout.dbu (m_dbu);
m_cellname = "{MAG top level}";
db::Cell &cell = layout.cell (layout.add_cell ());
if (! read_cell (layout, cell, sf, 0)) {
// The top cell is empty or contains a single instance: discard it.
layout.delete_cell (cell.cell_index ());
} else {
layout.rename_cell (cell.cell_index (), layout.uniquify_cell_name ("MAG_TOP").c_str ());
}
m_cellname = std::string ();
skip_blanks ();
if (! m_stream.at_end ()) {
warn ("E command is followed by more text");
}
} catch (db::MAGReaderException &ex) {
throw ex;
} catch (tl::Exception &ex) {
error (ex.msg ());
}
#endif
}
}