2017-02-12 13:21:08 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
KLayout Layout Viewer
|
2019-01-08 01:09:25 +01:00
|
|
|
Copyright (C) 2006-2019 Matthias Koefferlein
|
2017-02-12 13:21:08 +01:00
|
|
|
|
|
|
|
|
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 "tlStream.h"
|
|
|
|
|
#include "tlAssert.h"
|
|
|
|
|
#include "tlException.h"
|
|
|
|
|
#include "dbLayout.h"
|
|
|
|
|
#include "dbShape.h"
|
|
|
|
|
#include "dbPolygonTools.h"
|
|
|
|
|
#include "dbGDS2Writer.h"
|
|
|
|
|
#include "dbGDS2.h"
|
2018-06-20 01:13:31 +02:00
|
|
|
#include "dbGDS2Format.h"
|
2017-02-12 13:21:08 +01:00
|
|
|
#include "dbClip.h"
|
|
|
|
|
#include "dbSaveLayoutOptions.h"
|
|
|
|
|
#include "dbPolygonGenerators.h"
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
|
|
namespace db
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// GDS2WriterBase implementation
|
|
|
|
|
|
|
|
|
|
GDS2WriterBase::GDS2WriterBase ()
|
|
|
|
|
{
|
|
|
|
|
// .. nothing yet ..
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int safe_scale (double sf, int value)
|
|
|
|
|
{
|
|
|
|
|
double i = floor (sf * value + 0.5);
|
|
|
|
|
if (i < double (std::numeric_limits<int>::min ())) {
|
|
|
|
|
throw tl::Exception ("Scaling failed: coordinate underflow");
|
|
|
|
|
}
|
|
|
|
|
if (i > double (std::numeric_limits<int>::max ())) {
|
|
|
|
|
throw tl::Exception ("Scaling failed: coordinate overflow");
|
|
|
|
|
}
|
|
|
|
|
return int (i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline int scale (double sf, int value)
|
|
|
|
|
{
|
|
|
|
|
if (sf == 1.0) {
|
|
|
|
|
return value;
|
|
|
|
|
} else {
|
|
|
|
|
return safe_scale (sf, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayoutOptions &options)
|
|
|
|
|
{
|
|
|
|
|
set_stream (stream);
|
|
|
|
|
|
|
|
|
|
double dbu = (options.dbu () == 0.0) ? layout.dbu () : options.dbu ();
|
|
|
|
|
double sf = options.scale_factor () * (layout.dbu () / dbu);
|
|
|
|
|
if (fabs (sf - 1.0) < 1e-9) {
|
|
|
|
|
// to avoid rounding problems, set to 1.0 exactly if possible.
|
|
|
|
|
sf = 1.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
db::GDS2WriterOptions gds2_options = options.get_options<db::GDS2WriterOptions> ();
|
|
|
|
|
|
2018-07-03 00:51:36 +02:00
|
|
|
layout.add_meta_info (MetaInfo ("dbuu", tl::to_string (tr ("Database unit in user units")), tl::to_string (dbu / std::max (1e-9, gds2_options.user_units))));
|
|
|
|
|
layout.add_meta_info (MetaInfo ("dbum", tl::to_string (tr ("Database unit in meter")), tl::to_string (dbu * 1e-6)));
|
|
|
|
|
layout.add_meta_info (MetaInfo ("libname", tl::to_string (tr ("Library name")), gds2_options.libname));
|
2017-02-12 13:21:08 +01:00
|
|
|
|
|
|
|
|
std::vector <std::pair <unsigned int, db::LayerProperties> > layers;
|
|
|
|
|
options.get_valid_layers (layout, layers, db::SaveLayoutOptions::LP_AssignNumber);
|
|
|
|
|
|
|
|
|
|
std::set <db::cell_index_type> cell_set;
|
|
|
|
|
options.get_cells (layout, cell_set, layers);
|
|
|
|
|
|
|
|
|
|
// create a cell index vector sorted bottom-up
|
|
|
|
|
std::vector <db::cell_index_type> cells;
|
|
|
|
|
cells.reserve (cell_set.size ());
|
|
|
|
|
|
|
|
|
|
for (db::Layout::bottom_up_const_iterator cell = layout.begin_bottom_up (); cell != layout.end_bottom_up (); ++cell) {
|
|
|
|
|
if (cell_set.find (*cell) != cell_set.end ()) {
|
|
|
|
|
cells.push_back (*cell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get current time
|
|
|
|
|
short time_data [6] = { 0, 0, 0, 0, 0, 0 };
|
|
|
|
|
if (gds2_options.write_timestamps) {
|
|
|
|
|
time_t ti = 0;
|
|
|
|
|
time (&ti);
|
|
|
|
|
const struct tm *t = localtime (&ti);
|
|
|
|
|
if (t) {
|
|
|
|
|
time_data[0] = t->tm_year + 1900;
|
|
|
|
|
time_data[1] = t->tm_mon + 1;
|
|
|
|
|
time_data[2] = t->tm_mday;
|
|
|
|
|
time_data[3] = t->tm_hour;
|
|
|
|
|
time_data[4] = t->tm_min;
|
|
|
|
|
time_data[5] = t->tm_sec;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string str_time = tl::sprintf ("%d/%d/%d %d:%02d:%02d", time_data[1], time_data[2], time_data[0], time_data[3], time_data[4], time_data[5]);
|
2018-07-03 00:51:36 +02:00
|
|
|
layout.add_meta_info (MetaInfo ("mod_time", tl::to_string (tr ("Modification Time")), str_time));
|
|
|
|
|
layout.add_meta_info (MetaInfo ("access_time", tl::to_string (tr ("Access Time")), str_time));
|
2017-02-12 13:21:08 +01:00
|
|
|
|
|
|
|
|
bool multi_xy = gds2_options.multi_xy_records;
|
|
|
|
|
size_t max_cellname_length = std::max (gds2_options.max_cellname_length, (unsigned int)8);
|
|
|
|
|
size_t max_vertex_count = std::max (gds2_options.max_vertex_count, (unsigned int)4);
|
|
|
|
|
bool no_zero_length_paths = gds2_options.no_zero_length_paths;
|
|
|
|
|
|
|
|
|
|
m_cell_name_map = db::WriterCellNameMap (max_cellname_length);
|
|
|
|
|
m_cell_name_map.replacement ('$');
|
|
|
|
|
m_cell_name_map.disallow_all ();
|
|
|
|
|
// TODO: restrict character set, i.e allow_standard and "$"
|
|
|
|
|
m_cell_name_map.allow_all_printing ();
|
|
|
|
|
|
|
|
|
|
// For keep instances we need to map all cells since all can be present as instances.
|
|
|
|
|
// We use top-down assignment to make "upper cells less modified".
|
|
|
|
|
if (options.keep_instances ()) {
|
|
|
|
|
for (db::Layout::bottom_up_const_iterator cell = layout.end_bottom_up (); cell != layout.begin_bottom_up (); ) {
|
|
|
|
|
--cell;
|
|
|
|
|
m_cell_name_map.insert(*cell, layout.cell_name (*cell));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (std::vector<db::cell_index_type>::const_iterator cell = cells.end (); cell != cells.begin (); ) {
|
|
|
|
|
--cell;
|
|
|
|
|
m_cell_name_map.insert(*cell, layout.cell_name (*cell));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// write header
|
|
|
|
|
|
|
|
|
|
write_record_size (6);
|
|
|
|
|
write_record (sHEADER);
|
|
|
|
|
write_short (600);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 12 * 2);
|
|
|
|
|
write_record (sBGNLIB);
|
|
|
|
|
write_time (time_data);
|
|
|
|
|
write_time (time_data);
|
|
|
|
|
|
|
|
|
|
write_string_record (sLIBNAME, gds2_options.libname);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 8 * 2);
|
|
|
|
|
write_record (sUNITS);
|
|
|
|
|
write_double (dbu / std::max (1e-9, gds2_options.user_units));
|
|
|
|
|
write_double (dbu * 1e-6);
|
|
|
|
|
|
|
|
|
|
// layout properties
|
|
|
|
|
|
|
|
|
|
if (gds2_options.write_file_properties && layout.prop_id () != 0) {
|
|
|
|
|
write_properties (layout, layout.prop_id ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// write context info
|
|
|
|
|
|
|
|
|
|
bool any_proxy = false;
|
|
|
|
|
|
|
|
|
|
if (options.write_context_info ()) {
|
|
|
|
|
for (std::vector<db::cell_index_type>::const_iterator cell = cells.begin (); cell != cells.end () && !any_proxy; ++cell) {
|
|
|
|
|
const db::Cell &cref = layout.cell (*cell);
|
|
|
|
|
if (cref.is_proxy () && ! cref.is_top ()) {
|
|
|
|
|
any_proxy = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (any_proxy) {
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 12 * 2);
|
|
|
|
|
write_record (sBGNSTR);
|
|
|
|
|
write_time (time_data);
|
|
|
|
|
write_time (time_data);
|
|
|
|
|
|
|
|
|
|
write_string_record (sSTRNAME, "$$$CONTEXT_INFO$$$");
|
|
|
|
|
|
|
|
|
|
std::vector <std::string> context_prop_strings;
|
|
|
|
|
|
|
|
|
|
for (std::vector<db::cell_index_type>::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) {
|
|
|
|
|
|
|
|
|
|
const db::Cell &cref = layout.cell (*cell);
|
|
|
|
|
if (cref.is_proxy () && ! cref.is_top ()) {
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sSREF);
|
|
|
|
|
|
|
|
|
|
write_string_record (sSNAME, m_cell_name_map.cell_name (*cell));
|
|
|
|
|
|
|
|
|
|
write_record_size (12);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
write_int (0);
|
|
|
|
|
write_int (0);
|
|
|
|
|
|
|
|
|
|
context_prop_strings.clear ();
|
|
|
|
|
|
|
|
|
|
if (layout.get_context_info (*cell, context_prop_strings)) {
|
|
|
|
|
|
|
|
|
|
// Hint: write in the reverse order since this way, the reader is more efficient (it knows how many strings
|
|
|
|
|
// will arrive)
|
|
|
|
|
for (std::vector <std::string>::const_iterator s = context_prop_strings.end (); s != context_prop_strings.begin (); ) {
|
|
|
|
|
|
|
|
|
|
--s;
|
|
|
|
|
|
|
|
|
|
write_record_size (6);
|
|
|
|
|
write_record (sPROPATTR);
|
|
|
|
|
write_short (short (std::distance (std::vector <std::string>::const_iterator (context_prop_strings.begin ()), s))); // = user string
|
|
|
|
|
|
|
|
|
|
write_string_record (sPROPVALUE, *s);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sENDEL);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sENDSTR);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// body
|
|
|
|
|
|
|
|
|
|
for (std::vector<db::cell_index_type>::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) {
|
|
|
|
|
|
|
|
|
|
progress_checkpoint ();
|
|
|
|
|
|
|
|
|
|
const db::Cell &cref (layout.cell (*cell));
|
|
|
|
|
|
|
|
|
|
// don't write ghost cells unless they are not empty (any more)
|
|
|
|
|
// also don't write proxy cells which are not employed
|
|
|
|
|
if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) {
|
|
|
|
|
|
|
|
|
|
// cell header
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 12 * 2);
|
|
|
|
|
write_record (sBGNSTR);
|
|
|
|
|
write_time (time_data);
|
|
|
|
|
write_time (time_data);
|
|
|
|
|
|
|
|
|
|
write_string_record (sSTRNAME, m_cell_name_map.cell_name (*cell));
|
|
|
|
|
|
|
|
|
|
// cell body
|
|
|
|
|
|
|
|
|
|
if (gds2_options.write_cell_properties && cref.prop_id () != 0) {
|
|
|
|
|
write_properties (layout, cref.prop_id ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// instances
|
|
|
|
|
|
|
|
|
|
for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) {
|
|
|
|
|
|
|
|
|
|
// write only instances to selected cells
|
|
|
|
|
if (options.keep_instances () || cell_set.find (inst->cell_index ()) != cell_set.end ()) {
|
|
|
|
|
|
|
|
|
|
progress_checkpoint ();
|
|
|
|
|
write_inst (sf, *inst, true /*normalize*/, layout, inst->prop_id ());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// shapes
|
|
|
|
|
|
|
|
|
|
for (std::vector <std::pair <unsigned int, db::LayerProperties> >::const_iterator l = layers.begin (); l != layers.end (); ++l) {
|
|
|
|
|
|
|
|
|
|
if (layout.is_valid_layer (l->first)) {
|
|
|
|
|
|
|
|
|
|
int layer = l->second.layer;
|
|
|
|
|
int datatype = l->second.datatype;
|
|
|
|
|
|
2019-02-16 22:34:36 +01:00
|
|
|
db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts));
|
2017-02-12 13:21:08 +01:00
|
|
|
while (! shape.at_end ()) {
|
|
|
|
|
|
|
|
|
|
progress_checkpoint ();
|
|
|
|
|
|
|
|
|
|
if (shape->is_text ()) {
|
|
|
|
|
write_text (layer, datatype, sf, dbu, *shape, layout, shape->prop_id ());
|
|
|
|
|
} else if (shape->is_polygon ()) {
|
|
|
|
|
write_polygon (layer, datatype, sf, *shape, multi_xy, max_vertex_count, layout, shape->prop_id ());
|
|
|
|
|
} else if (shape->is_edge ()) {
|
|
|
|
|
write_edge (layer, datatype, sf, *shape, layout, shape->prop_id ());
|
2019-02-16 22:34:36 +01:00
|
|
|
} else if (shape->is_edge_pair ()) {
|
|
|
|
|
write_edge (layer, datatype, sf, shape->edge_pair ().first (), layout, shape->prop_id ());
|
|
|
|
|
write_edge (layer, datatype, sf, shape->edge_pair ().second (), layout, shape->prop_id ());
|
2017-02-12 13:21:08 +01:00
|
|
|
} else if (shape->is_path ()) {
|
|
|
|
|
if (no_zero_length_paths && (shape->path_length () - shape->path_extensions ().first - shape->path_extensions ().second) == 0) {
|
|
|
|
|
// eliminate the zero-width path
|
|
|
|
|
db::Polygon poly;
|
|
|
|
|
shape->polygon (poly);
|
|
|
|
|
write_polygon (layer, datatype, sf, poly, multi_xy, max_vertex_count, layout, shape->prop_id (), false);
|
|
|
|
|
} else {
|
2017-09-03 23:24:50 +02:00
|
|
|
write_path (layer, datatype, sf, *shape, multi_xy, layout, shape->prop_id ());
|
2017-02-12 13:21:08 +01:00
|
|
|
}
|
|
|
|
|
} else if (shape->is_box ()) {
|
|
|
|
|
write_box (layer, datatype, sf, *shape, layout, shape->prop_id ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++shape;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// end of cell
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sENDSTR);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sENDLIB);
|
|
|
|
|
|
|
|
|
|
progress_checkpoint ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_inst (double sf, const db::Instance &instance, bool normalize, const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
|
|
|
|
db::Vector a, b;
|
|
|
|
|
unsigned long amax, bmax;
|
|
|
|
|
|
|
|
|
|
bool is_reg = instance.is_regular_array (a, b, amax, bmax);
|
|
|
|
|
|
|
|
|
|
db::Trans t = instance.front ();
|
|
|
|
|
|
|
|
|
|
if (normalize) {
|
|
|
|
|
|
|
|
|
|
// try to normalize orthogonal arrays into "Cadence notation", that is
|
|
|
|
|
// column and row vectors are positive in the coordinate system of the
|
|
|
|
|
// rotated array.
|
|
|
|
|
|
|
|
|
|
if (is_reg) {
|
|
|
|
|
|
|
|
|
|
if (amax < 2) {
|
|
|
|
|
a = db::Vector ();
|
|
|
|
|
}
|
|
|
|
|
if (bmax < 2) {
|
|
|
|
|
b = db::Vector ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// normalisation only works for orthogonal vectors, parallel to x or y axis, which are not parallel
|
|
|
|
|
if ((a.x () == 0 || a.y () == 0) && (b.x () == 0 || b.y () == 0) && !((a.x () != 0 && b.x () != 0) || (a.y () != 0 && b.y () != 0))) {
|
|
|
|
|
|
|
|
|
|
db::FTrans fp = db::FTrans(t.rot ()).inverted ();
|
|
|
|
|
|
|
|
|
|
a.transform (fp);
|
|
|
|
|
b.transform (fp);
|
|
|
|
|
|
|
|
|
|
db::Vector p;
|
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
|
|
|
|
|
|
db::Vector *q = (i == 0) ? &a : &b;
|
|
|
|
|
unsigned long n = (i == 0) ? amax : bmax;
|
|
|
|
|
|
|
|
|
|
if (n == 0) {
|
|
|
|
|
*q = db::Vector ();
|
|
|
|
|
} else {
|
|
|
|
|
if (q->x () < 0) {
|
|
|
|
|
p += db::Vector ((n - 1) * q->x (), 0);
|
|
|
|
|
q->set_x (-q->x ());
|
|
|
|
|
}
|
|
|
|
|
if (q->y () < 0) {
|
|
|
|
|
p += db::Vector (0, (n - 1) * q->y ());
|
|
|
|
|
q->set_y (-q->y ());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (a.x () != 0 || b.y () != 0) {
|
|
|
|
|
std::swap (a, b);
|
|
|
|
|
std::swap (amax, bmax);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fp = db::FTrans (t.rot ());
|
|
|
|
|
a.transform (fp);
|
|
|
|
|
b.transform (fp);
|
|
|
|
|
|
|
|
|
|
t = t * db::Trans (p);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (is_reg ? sAREF : sSREF);
|
|
|
|
|
|
|
|
|
|
write_string_record (sSNAME, m_cell_name_map.cell_name (instance.cell_index ()));
|
|
|
|
|
|
|
|
|
|
if (t.rot () != 0 || instance.is_complex ()) {
|
|
|
|
|
|
|
|
|
|
write_record_size (6);
|
|
|
|
|
write_record (sSTRANS);
|
|
|
|
|
write_short (t.is_mirror () ? 0x8000 : 0);
|
|
|
|
|
|
|
|
|
|
if (instance.is_complex ()) {
|
|
|
|
|
write_record_size (4 + 8);
|
|
|
|
|
write_record (sMAG);
|
|
|
|
|
write_double (instance.complex_trans ().mag ());
|
|
|
|
|
write_record_size (4 + 8);
|
|
|
|
|
write_record (sANGLE);
|
|
|
|
|
write_double (instance.complex_trans ().angle ());
|
|
|
|
|
} else {
|
|
|
|
|
if ((t.rot () % 4) != 0) {
|
|
|
|
|
write_record_size (4 + 8);
|
|
|
|
|
write_record (sANGLE);
|
|
|
|
|
write_double ((t.rot () % 4) * 90.0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_reg) {
|
|
|
|
|
write_record_size (4 + 2 * 2);
|
|
|
|
|
write_record (sCOLROW);
|
|
|
|
|
if (amax > 32767 || bmax > 32767) {
|
2018-07-03 00:51:36 +02:00
|
|
|
throw tl::Exception (tl::to_string (tr ("Cannot write array references with more than 32767 columns or rows to GDS2 streams")));
|
2017-02-12 13:21:08 +01:00
|
|
|
}
|
|
|
|
|
write_short (std::max ((unsigned long) 1, bmax));
|
|
|
|
|
write_short (std::max ((unsigned long) 1, amax));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + (is_reg ? 3 : 1) * 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
write_int (scale (sf, t.disp ().x ()));
|
|
|
|
|
write_int (scale (sf, t.disp ().y ()));
|
|
|
|
|
|
|
|
|
|
if (is_reg) {
|
|
|
|
|
write_int (scale (sf, t.disp ().x () + b.x () * bmax));
|
|
|
|
|
write_int (scale (sf, t.disp ().y () + b.y () * bmax));
|
|
|
|
|
write_int (scale (sf, t.disp ().x () + a.x () * amax));
|
|
|
|
|
write_int (scale (sf, t.disp ().y () + a.y () * amax));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_box (int layer, int datatype, double sf, const db::Shape &shape, const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
|
|
|
|
db::Box box (shape.box ());
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sBOUNDARY);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sLAYER);
|
|
|
|
|
write_short (layer);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sDATATYPE);
|
|
|
|
|
write_short (datatype);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 5 * 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
write_int (scale (sf, box.left ()));
|
|
|
|
|
write_int (scale (sf, box.bottom ()));
|
|
|
|
|
write_int (scale (sf, box.left ()));
|
|
|
|
|
write_int (scale (sf, box.top ()));
|
|
|
|
|
write_int (scale (sf, box.right ()));
|
|
|
|
|
write_int (scale (sf, box.top ()));
|
|
|
|
|
write_int (scale (sf, box.right ()));
|
|
|
|
|
write_int (scale (sf, box.bottom ()));
|
|
|
|
|
write_int (scale (sf, box.left ()));
|
|
|
|
|
write_int (scale (sf, box.bottom ()));
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2017-09-03 23:24:50 +02:00
|
|
|
GDS2WriterBase::write_path (int layer, int datatype, double sf, const db::Shape &shape, bool multi_xy, const db::Layout &layout, db::properties_id_type prop_id)
|
2017-02-12 13:21:08 +01:00
|
|
|
{
|
|
|
|
|
// instantiate the path and draw
|
|
|
|
|
db::Path path;
|
|
|
|
|
shape.path (path);
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sPATH);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sLAYER);
|
|
|
|
|
write_short (layer);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sDATATYPE);
|
|
|
|
|
write_short (datatype);
|
|
|
|
|
|
|
|
|
|
short type = 0;
|
|
|
|
|
db::Coord w = path.width ();
|
|
|
|
|
db::Coord se = path.extensions ().first;
|
|
|
|
|
db::Coord ee = path.extensions ().second;
|
|
|
|
|
|
|
|
|
|
if (se == w / 2 && ee == w / 2) {
|
|
|
|
|
type = path.round () ? 1 : 2;
|
|
|
|
|
} else if (se == 0 && ee == 0) {
|
|
|
|
|
type = 0;
|
|
|
|
|
} else {
|
|
|
|
|
type = 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sPATHTYPE);
|
|
|
|
|
write_short (type);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 4);
|
|
|
|
|
write_record (sWIDTH);
|
|
|
|
|
write_int (scale (sf, w));
|
|
|
|
|
|
|
|
|
|
if (type == 4) {
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 4);
|
|
|
|
|
write_record (sBGNEXTN);
|
|
|
|
|
write_int (scale (sf, se));
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 4);
|
|
|
|
|
write_record (sENDEXTN);
|
|
|
|
|
write_int (scale (sf, ee));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-03 23:24:50 +02:00
|
|
|
size_t n = path.points ();
|
|
|
|
|
|
|
|
|
|
db::Path::iterator p = path.begin ();
|
|
|
|
|
while (n > 0) {
|
|
|
|
|
|
|
|
|
|
// determine number of points to write (all or slice for multi XY mode)
|
|
|
|
|
size_t nxy = n;
|
|
|
|
|
if (n > 8100 && multi_xy) {
|
|
|
|
|
nxy = 8000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + int16_t (nxy) * 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
|
|
|
|
|
// write path ..
|
|
|
|
|
for ( ; p != path.end () && nxy > 0; ++p) {
|
|
|
|
|
write_int (scale (sf, (*p).x ()));
|
|
|
|
|
write_int (scale (sf, (*p).y ()));
|
|
|
|
|
--nxy;
|
|
|
|
|
--n;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-12 13:21:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_edge (int layer, int datatype, double sf, const db::Shape &shape, const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
2019-02-16 22:34:36 +01:00
|
|
|
write_edge (layer, datatype, sf, shape.edge (), layout, prop_id);
|
|
|
|
|
}
|
2017-02-12 13:21:08 +01:00
|
|
|
|
2019-02-16 22:34:36 +01:00
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_edge (int layer, int datatype, double sf, const db::Edge &e, const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
2017-02-12 13:21:08 +01:00
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sPATH);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sLAYER);
|
|
|
|
|
write_short (layer);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sDATATYPE);
|
|
|
|
|
write_short (datatype);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sPATHTYPE);
|
|
|
|
|
write_short (0);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 4);
|
|
|
|
|
write_record (sWIDTH);
|
|
|
|
|
write_int (0);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2 * 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
write_int (scale (sf, e.p1 ().x ()));
|
|
|
|
|
write_int (scale (sf, e.p1 ().y ()));
|
|
|
|
|
write_int (scale (sf, e.p2 ().x ()));
|
|
|
|
|
write_int (scale (sf, e.p2 ().y ()));
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_text (int layer, int datatype, double sf, double dbu, const db::Shape &shape, const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
|
|
|
|
db::Trans trans = shape.text_trans ();
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sTEXT);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sLAYER);
|
|
|
|
|
write_short (layer);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sTEXTTYPE);
|
|
|
|
|
write_short (datatype);
|
|
|
|
|
|
|
|
|
|
if (shape.text_halign () != db::NoHAlign || shape.text_valign () != db::NoVAlign || shape.text_font () != db::NoFont) {
|
|
|
|
|
short ha = short (shape.text_halign () == db::NoHAlign ? db::HAlignLeft : shape.text_halign ());
|
|
|
|
|
short va = short (shape.text_valign () == db::NoVAlign ? db::VAlignBottom : shape.text_valign ());
|
|
|
|
|
// HINT: currently we don't write the font since the font is not well standardized
|
|
|
|
|
// short f = (shape.text_font () == db::NoFont ? 0 : (short (shape.text_font ()) & 0xfff));
|
|
|
|
|
short f = 0;
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sPRESENTATION);
|
|
|
|
|
write_short (ha + va * 4 + f * 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (trans.rot () != 0 || shape.text_size () != 0) {
|
|
|
|
|
|
|
|
|
|
write_record_size (6);
|
|
|
|
|
write_record (sSTRANS);
|
|
|
|
|
write_short (trans.is_mirror () ? 0x8000 : 0);
|
|
|
|
|
|
|
|
|
|
if (shape.text_size () != 0) {
|
|
|
|
|
write_record_size (4 + 8);
|
|
|
|
|
write_record (sMAG);
|
|
|
|
|
write_double (shape.text_size () * sf * dbu);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((trans.rot () % 4) != 0) {
|
|
|
|
|
write_record_size (4 + 8);
|
|
|
|
|
write_record (sANGLE);
|
|
|
|
|
write_double ((trans.rot () % 4) * 90.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
write_int (scale (sf, trans.disp ().x ()));
|
|
|
|
|
write_int (scale (sf, trans.disp ().y ()));
|
|
|
|
|
|
|
|
|
|
write_string_record (sSTRING, shape.text_string ());
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Polygon &polygon, bool multi_xy, size_t max_vertex, const db::Layout &layout, db::properties_id_type prop_id, bool merged)
|
|
|
|
|
{
|
|
|
|
|
bool needs_split = (polygon.vertices () > 4 && polygon.vertices () > max_vertex && !multi_xy);
|
|
|
|
|
|
|
|
|
|
if (polygon.holes () > 0 || (! merged && needs_split)) {
|
|
|
|
|
|
|
|
|
|
// resolve holes or merge polygon as a preparation step for split_polygon which only works properly
|
|
|
|
|
// on merged polygons ...
|
|
|
|
|
std::vector<db::Polygon> polygons;
|
|
|
|
|
|
|
|
|
|
db::EdgeProcessor ep;
|
|
|
|
|
ep.insert_sequence (polygon.begin_edge ());
|
|
|
|
|
db::PolygonContainer pc (polygons);
|
|
|
|
|
db::PolygonGenerator out (pc, true /*resolve holes*/, needs_split /*min coherence for splitting*/);
|
|
|
|
|
db::SimpleMerge op;
|
|
|
|
|
ep.process (out, op);
|
|
|
|
|
|
|
|
|
|
for (std::vector<db::Polygon>::const_iterator p = polygons.begin (); p != polygons.end (); ++p) {
|
|
|
|
|
write_polygon (layer, datatype, sf, *p, multi_xy, max_vertex, layout, prop_id, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if (needs_split) {
|
|
|
|
|
|
|
|
|
|
std::vector <db::Polygon> polygons;
|
|
|
|
|
db::split_polygon (polygon, polygons);
|
|
|
|
|
|
|
|
|
|
for (std::vector<db::Polygon>::const_iterator p = polygons.begin (); p != polygons.end (); ++p) {
|
|
|
|
|
write_polygon (layer, datatype, sf, *p, multi_xy, max_vertex, layout, prop_id, true /*parts need not to be merged again*/);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if (polygon.vertices () > 0) {
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sBOUNDARY);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sLAYER);
|
|
|
|
|
write_short (layer);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sDATATYPE);
|
|
|
|
|
write_short (datatype);
|
|
|
|
|
|
|
|
|
|
size_t n = polygon.vertices ();
|
|
|
|
|
|
|
|
|
|
db::Polygon::polygon_contour_iterator e = polygon.begin_hull ();
|
|
|
|
|
while (n > 0) {
|
|
|
|
|
|
|
|
|
|
// determine number of points to write (all or slice for multi XY mode)
|
|
|
|
|
size_t nxy = n + 1;
|
|
|
|
|
if (n > 8100 && multi_xy) {
|
|
|
|
|
nxy = 8000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + int16_t (nxy) * 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
|
|
|
|
|
// write polygon ..
|
|
|
|
|
for ( ; e != polygon.end_hull () && nxy > 0; ++e) {
|
|
|
|
|
write_int (scale (sf, (*e).x ()));
|
|
|
|
|
write_int (scale (sf, (*e).y ()));
|
|
|
|
|
--nxy;
|
|
|
|
|
--n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// .. and closing point
|
|
|
|
|
if (nxy > 0) {
|
|
|
|
|
e = polygon.begin_hull ();
|
|
|
|
|
write_int (scale (sf, (*e).x ()));
|
|
|
|
|
write_int (scale (sf, (*e).y ()));
|
|
|
|
|
tl_assert (n == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Shape &shape, bool multi_xy, size_t max_vertex, const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
|
|
|
|
if (shape.holes () > 0) {
|
|
|
|
|
|
|
|
|
|
db::Polygon polygon;
|
|
|
|
|
shape.polygon (polygon);
|
|
|
|
|
write_polygon (layer, datatype, sf, polygon, multi_xy, max_vertex, layout, prop_id, false);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
// There is no other way to determine the actual number of points of a generic shape
|
|
|
|
|
// without instantiating a polygon:
|
|
|
|
|
size_t n = 0;
|
|
|
|
|
for (db::Shape::point_iterator e = shape.begin_hull (); e != shape.end_hull (); ++e) {
|
|
|
|
|
++n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (n > 4 && n > max_vertex && !multi_xy) {
|
|
|
|
|
|
|
|
|
|
// split polygons ...
|
|
|
|
|
db::Polygon polygon;
|
|
|
|
|
shape.polygon (polygon);
|
|
|
|
|
write_polygon (layer, datatype, sf, polygon, multi_xy, max_vertex, layout, prop_id, false);
|
|
|
|
|
|
|
|
|
|
} else if (n > 0) {
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sBOUNDARY);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sLAYER);
|
|
|
|
|
write_short (layer);
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + 2);
|
|
|
|
|
write_record (sDATATYPE);
|
|
|
|
|
write_short (datatype);
|
|
|
|
|
|
|
|
|
|
db::Shape::point_iterator e (shape.begin_hull ());
|
|
|
|
|
while (n > 0) {
|
|
|
|
|
|
|
|
|
|
// determine number of points to write (all or slice for multi XY mode)
|
|
|
|
|
size_t nxy = n + 1;
|
|
|
|
|
if (n > 8100 && multi_xy) {
|
|
|
|
|
nxy = 8000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4 + int16_t (nxy) * 2 * 4);
|
|
|
|
|
write_record (sXY);
|
|
|
|
|
|
|
|
|
|
// write polygon ..
|
|
|
|
|
for ( ; e != shape.end_hull () && nxy > 0; ++e) {
|
|
|
|
|
write_int (scale (sf, (*e).x ()));
|
|
|
|
|
write_int (scale (sf, (*e).y ()));
|
|
|
|
|
--nxy;
|
|
|
|
|
--n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// .. and closing point
|
|
|
|
|
if (nxy > 0) {
|
|
|
|
|
e = shape.begin_hull ();
|
|
|
|
|
write_int (scale (sf, (*e).x ()));
|
|
|
|
|
write_int (scale (sf, (*e).y ()));
|
|
|
|
|
tl_assert (n == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finish (layout, prop_id);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_properties (const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
|
|
|
|
const db::PropertiesRepository::properties_set &props = layout.properties_repository ().properties (prop_id);
|
|
|
|
|
for (db::PropertiesRepository::properties_set::const_iterator p = props.begin (); p != props.end (); ++p) {
|
|
|
|
|
|
|
|
|
|
const tl::Variant &name = layout.properties_repository ().prop_name (p->first);
|
|
|
|
|
|
|
|
|
|
long attr = -1;
|
2018-04-06 23:27:29 +02:00
|
|
|
if (name.can_convert_to_long ()) {
|
2017-02-12 13:21:08 +01:00
|
|
|
attr = name.to_long ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attr >= 0 && attr < 65535) {
|
|
|
|
|
|
|
|
|
|
write_record_size (6);
|
|
|
|
|
write_record (sPROPATTR);
|
|
|
|
|
write_short (attr);
|
|
|
|
|
|
|
|
|
|
write_string_record (sPROPVALUE, p->second.to_string ());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::finish (const db::Layout &layout, db::properties_id_type prop_id)
|
|
|
|
|
{
|
|
|
|
|
if (prop_id != 0) {
|
|
|
|
|
write_properties (layout, prop_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write_record_size (4);
|
|
|
|
|
write_record (sENDEL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
GDS2WriterBase::write_string_record (short record, const std::string &t)
|
|
|
|
|
{
|
|
|
|
|
write_record_size (4 + (int16_t (t.size () + 1) / 2) * 2);
|
|
|
|
|
write_record (record);
|
|
|
|
|
write_string (t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace db
|
|
|
|
|
|