Implementation of GDS2 extended features. Currently, the extended features are enabled by default.

This commit is contained in:
Matthias Koefferlein 2026-02-21 23:29:39 +01:00
parent 91b4714a91
commit 1f4126036d
11 changed files with 837 additions and 69 deletions

View File

@ -66,6 +66,7 @@ GenericWriterOptions::init_from_options (const db::SaveLayoutOptions &save_optio
m_gds2_write_file_properties = save_options.get_option_by_name ("gds2_write_file_properties").to_bool ();
tl::Variant def_text_size = save_options.get_option_by_name ("gds2_default_text_size");
m_gds2_default_text_size = def_text_size.is_nil () ? -1.0 : def_text_size.to_double ();
m_gds2_extended_features = save_options.get_option_by_name ("gds2_extended_features").to_bool ();
m_oasis_compression_level = save_options.get_option_by_name ("oasis_compression_level").to_int ();
m_oasis_write_cblocks = save_options.get_option_by_name ("oasis_write_cblocks").to_bool ();
@ -222,6 +223,14 @@ GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::strin
"This option enables a GDS2 extension that allows writing of file properties to GDS2 files. "
"Consumers that don't support this feature, may not be able to read such a GDS2 files."
)
<< tl::arg (group +
"!#--no-extended-features", &m_gds2_extended_features, "Disables extended GDS2 features",
"This option disables extended GDS2 features. Extended GDS features allow writing file and cell level "
"properties without 'write-cell-properties' or 'write-file-properties', store layer names and allow "
"string names for properties and complex property values such as very long strings or lists.\n"
"\n"
"Extended features rely on the context, so they are not available with 'no-context-info'."
)
<< tl::arg (group +
"#--default-text-size", &m_gds2_default_text_size, "Default text size",
"This text size (given in micrometers) is applied to text objects not coming with their "
@ -435,6 +444,7 @@ GenericWriterOptions::configure (db::SaveLayoutOptions &save_options, const db::
save_options.set_option_by_name ("gds2_write_timestamps", m_gds2_write_timestamps);
save_options.set_option_by_name ("gds2_write_cell_properties", m_gds2_write_cell_properties);
save_options.set_option_by_name ("gds2_write_file_properties", m_gds2_write_file_properties);
save_options.set_option_by_name ("gds2_extended_features", m_gds2_extended_features);
save_options.set_option_by_name ("gds2_default_text_size", m_gds2_default_text_size < 0.0 ? tl::Variant () : tl::Variant (m_gds2_default_text_size));
save_options.set_option_by_name ("oasis_compression_level", m_oasis_compression_level);

View File

@ -132,6 +132,7 @@ private:
bool m_gds2_write_cell_properties;
bool m_gds2_write_file_properties;
double m_gds2_default_text_size;
bool m_gds2_extended_features;
int m_oasis_compression_level;
bool m_oasis_write_cblocks;

View File

@ -91,6 +91,30 @@ private:
DB_PUBLIC void
join_layer_names (std::string &s, const std::string &n);
/**
* @brief A helper class to join two datatype layer name map members
*/
struct LNameJoinOp1
{
void operator() (std::string &a, const std::string &b)
{
join_layer_names (a, b);
}
};
/**
* @brief A helper class to join two layer map members
* This implementation basically merged the datatype maps.
*/
struct LNameJoinOp2
{
void operator() (tl::interval_map<db::ld_type, std::string> &a, const tl::interval_map<db::ld_type, std::string> &b)
{
LNameJoinOp1 op1;
a.add (b.begin (), b.end (), op1);
}
};
/**
* @brief The generic reader base class
*/

View File

@ -115,6 +115,7 @@ public:
write_timestamps (true),
write_cell_properties (false),
write_file_properties (false),
extended_features (true),
default_text_size (-1.0)
{
// .. nothing yet ..
@ -184,6 +185,22 @@ public:
*/
bool write_file_properties;
/**
* @brief Write extended features
*
* Extended features are:
* - non-numerical property names
* - complex property values
* - file and cell properties without "write_cell_properties" and "write_file_properties"
* - layer names
*
* These extended features require a context cell to be created (unless
* needed for other reasons). Hence this flag is not compatible with
* "write_context_info = false". On the plus side, GDS files written with extended
* features are backward compatible.
*/
bool extended_features;
/**
* @brief The default text size if none is given (in fact, if the text size is zero)
*

View File

@ -112,7 +112,7 @@ GDS2ReaderBase::finish_element_with_props ()
const char *value = get_string ();
if (m_read_properties) {
properties.insert (tl::Variant (attr), tl::Variant (value));
properties.insert (map_property_name (attr), map_property_value (value));
any = true;
}
@ -135,6 +135,80 @@ GDS2ReaderBase::finish_element_with_props ()
}
tl::Variant
GDS2ReaderBase::map_property_name (long attr) const
{
auto i = m_property_names_map.find (attr);
if (i != m_property_names_map.end ()) {
return i->second;
} else {
return tl::Variant (attr);
}
}
tl::Variant
GDS2ReaderBase::map_property_value (const std::string &value) const
{
auto i = m_property_values_map.find (value);
if (i != m_property_values_map.end ()) {
return i->second;
} else {
return tl::Variant (value);
}
}
void
GDS2ReaderBase::digest_context (db::Layout &layout, const std::vector<std::string> &context)
{
for (auto c = context.begin (); c != context.end (); ++c) {
long kn;
std::string kv;
tl::Variant v;
db::ld_type l = 0, dt = 0;
std::string n;
tl::Extractor ex (c->c_str ());
if (ex.test ("PROP_NAME") && ex.test ("(") && ex.try_read (kn) && ex.test (")") && ex.test ("=") && ex.try_read (v)) {
m_property_names_map[kn].swap (v);
} else if (ex.test ("PROP_VALUE") && ex.test ("(") && ex.try_read_word_or_quoted (kv) && ex.test (")") && ex.test ("=") && ex.try_read (v)) {
m_property_values_map[kv].swap (v);
} else if (ex.test ("LNAME") && ex.test ("(") && ex.try_read (l) && ex.test (",") && ex.try_read (dt) && ex.test (")") && ex.test ("=") && ex.try_read_word_or_quoted (n)) {
// add to the layer name map
tl::interval_map <db::ld_type, std::string> dt_map;
LNameJoinOp1 op1;
dt_map.add (dt, dt + 1, n, op1);
LNameJoinOp2 op2;
layer_names ().add (l, l + 1, dt_map, op2);
// force a layer entry: this way we can have empty, but existing layers, just by naming them
open_dl (layout, db::LDPair (l, dt));
}
}
}
void
GDS2ReaderBase::build_properties_from_context (const std::vector<std::string> &context, db::PropertiesSet &properties) const
{
for (auto c = context.begin (); c != context.end (); ++c) {
tl::Variant pn, pv;
tl::Extractor ex (c->c_str ());
if (ex.test ("PROP") && ex.test ("(") && ex.try_read (pn) && ex.test (")") && ex.test ("=") && ex.try_read (pv)) {
properties.insert (pn, pv);
}
}
}
inline db::Point
pt_conv (const GDS2XY &p)
{
@ -186,6 +260,7 @@ GDS2ReaderBase::do_read (db::Layout &layout)
long attr = 0;
db::PropertiesSet layout_properties;
std::list<std::pair <long, std::string> > basic_layout_properties;
// read until
short rec_id = 0;
@ -221,7 +296,7 @@ GDS2ReaderBase::do_read (db::Layout &layout)
const char *value = get_string ();
if (m_read_properties) {
layout_properties.insert (tl::Variant (attr), tl::Variant (value));
basic_layout_properties.push_back (std::make_pair (attr, value));
}
} else if (rec_id == sUNITS) {
@ -245,11 +320,6 @@ GDS2ReaderBase::do_read (db::Layout &layout)
} while (true);
// set the layout properties
if (! layout_properties.empty ()) {
layout.prop_id (db::properties_id (layout_properties));
}
// this container has been found to grow quite a lot.
// using a list instead of a vector should make this more efficient.
tl::vector<db::CellInstArray> instances;
@ -285,10 +355,24 @@ GDS2ReaderBase::do_read (db::Layout &layout)
read_context_info_cell ();
// deserialize global context information
auto ctx = m_context_info.find (std::string ());
if (ctx != m_context_info.end ()) {
LayoutOrCellContextInfo ci = LayoutOrCellContextInfo::deserialize (ctx->second.begin (), ctx->second.end ());
layout.fill_meta_info_from_context (ci);
build_properties_from_context (ctx->second, layout_properties);
digest_context (layout, ctx->second);
}
} else {
db::cell_index_type cell_index = make_cell (layout, m_cellname);
db::PropertiesSet cell_properties;
bool ignore_cell = false;
auto ctx = m_context_info.find (m_cellname);
if (ctx != m_context_info.end ()) {
@ -303,6 +387,8 @@ GDS2ReaderBase::do_read (db::Layout &layout)
layout.fill_meta_info_from_context (cell_index, ci);
build_properties_from_context (ctx->second, cell_properties);
}
db::Cell *cell = 0;
@ -311,7 +397,6 @@ GDS2ReaderBase::do_read (db::Layout &layout)
}
long attr = 0;
db::PropertiesSet cell_properties;
// read cell content
while ((rec_id = get_record ()) != sENDSTR) {
@ -330,7 +415,7 @@ GDS2ReaderBase::do_read (db::Layout &layout)
const char *value = get_string ();
if (m_read_properties) {
cell_properties.insert (tl::Variant (attr), tl::Variant (value));
cell_properties.insert (map_property_name (attr), map_property_value (value));
}
} else if (rec_id == sBOUNDARY) {
@ -393,14 +478,19 @@ GDS2ReaderBase::do_read (db::Layout &layout)
}
// deserialize global context information
auto ctx = m_context_info.find (std::string ());
if (ctx != m_context_info.end ()) {
LayoutOrCellContextInfo ci = LayoutOrCellContextInfo::deserialize (ctx->second.begin (), ctx->second.end ());
layout.fill_meta_info_from_context (ci);
// creat the set the layout properties
// NOTE: we can only merge now, as we have the property names and values maps
for (auto i = basic_layout_properties.begin (); i != basic_layout_properties.end (); ++i) {
layout_properties.insert (map_property_name (i->first), map_property_value (i->second));
}
if (! layout_properties.empty ()) {
layout.prop_id (db::properties_id (layout_properties));
}
// check, if the last record is a ENDLIB
if (rec_id != sENDLIB) {
error (tl::to_string (tr ("ENDLIB record expected")));
}

View File

@ -89,6 +89,8 @@ private:
unsigned int m_box_mode;
std::map <tl::string, std::vector<std::string> > m_context_info;
std::vector <db::Point> m_all_points;
std::map <long, tl::Variant> m_property_names_map;
std::map <std::string, tl::Variant> m_property_values_map;
void read_context_info_cell ();
void read_boundary (db::Layout &layout, db::Cell &cell, bool from_box_record);
@ -96,6 +98,10 @@ private:
void read_text (db::Layout &layout, db::Cell &cell);
void read_box (db::Layout &layout, db::Cell &cell);
void read_ref (db::Layout &layout, db::Cell &cell, bool array, tl::vector<db::CellInstArray> &instances, tl::vector<db::CellInstArrayWithProperties> &insts_wp);
tl::Variant map_property_name (long attr) const;
tl::Variant map_property_value (const std::string &value) const;
void build_properties_from_context (const std::vector<std::string> &context, db::PropertiesSet &properties) const;
void digest_context (Layout &layout, const std::vector<std::string> &context);
std::pair <bool, db::properties_id_type> finish_element_with_props ();
void finish_element ();

View File

@ -209,7 +209,21 @@ GDS2WriterBase::write_context_string (size_t n, const std::string &s)
}
void
GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, const std::vector<db::cell_index_type> &cells)
GDS2WriterBase::get_property_map_context (std::vector<std::string> &context_strings)
{
for (auto i = m_prop_name_placeholders.begin (); i != m_prop_name_placeholders.end (); ++i) {
context_strings.push_back (std::string ());
context_strings.back () = "PROP_NAME(" + tl::to_string (int (i->second)) + ")=" + i->first.to_parsable_string ();
}
for (auto i = m_prop_value_placeholders.begin (); i != m_prop_value_placeholders.end (); ++i) {
context_strings.push_back (std::string ());
context_strings.back () = "PROP_VALUE(" + tl::to_quoted_string (i->second) + ")=" + i->first.to_parsable_string ();
}
}
void
GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, const std::vector<db::cell_index_type> &cells, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers, const db::GDS2WriterOptions &gds2_options)
{
write_record_size (4 + 12 * 2);
write_record (sBGNSTR);
@ -221,8 +235,34 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data,
std::vector <std::string> context_prop_strings;
layout.get_context_info (context_prop_strings);
get_property_map_context (context_prop_strings);
// @@@ Add context strings for m_prop_names_map and m_prop_values_map and layout properties if needed
// Add file properties if needed
if (layout.prop_id () != 0 && ! gds2_options.write_file_properties && gds2_options.extended_features) {
const auto &props = db::properties (layout.prop_id ());
for (auto p = props.begin (); p != props.end (); ++p) {
const tl::Variant &pn = db::property_name (p->first);
const tl::Variant &pv = db::property_value (p->second);
context_prop_strings.push_back (std::string ());
context_prop_strings.back () = "PROP(" + pn.to_parsable_string () + ")=" + pv.to_parsable_string ();
}
}
// Add layer names if needed
if (gds2_options.extended_features) {
for (auto l = layers.begin (); l != layers.end (); ++l) {
if (! l->second.name.empty ()) {
context_prop_strings.push_back (std::string ());
context_prop_strings.back () = "LNAME(" + tl::to_string (l->second.layer) + "," + tl::to_string (l->second.datatype) + ")=" + tl::to_quoted_string (l->second.name);
}
}
}
if (! context_prop_strings.empty ()) {
@ -263,7 +303,21 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data,
context_prop_strings.clear ();
layout.get_context_info (*cell, context_prop_strings);
// @@@ Add cell properties if needed
const db::Cell &cell_obj = layout.cell (*cell);
// Add cell properties if needed
if (cell_obj.prop_id () != 0 && ! gds2_options.write_cell_properties && gds2_options.extended_features) {
const auto &props = db::properties (cell_obj.prop_id ());
for (auto p = props.begin (); p != props.end (); ++p) {
const tl::Variant &pn = db::property_name (p->first);
const tl::Variant &pv = db::property_value (p->second);
context_prop_strings.push_back (std::string ());
context_prop_strings.back () = "PROP(" + pn.to_parsable_string () + ")=" + pv.to_parsable_string ();
}
}
if (! context_prop_strings.empty ()) {
@ -277,8 +331,6 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data,
write_int (0);
write_int (0);
context_prop_strings.clear ();
// 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 (); ) {
@ -420,6 +472,129 @@ GDS2WriterBase::write_cell (db::Layout &layout, const db::Cell &cref, const std:
write_record (sENDSTR);
}
void
GDS2WriterBase::build_property_translations (const db::Layout &layout, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers, const std::vector <db::cell_index_type> &cells, const db::GDS2WriterOptions &gds2_options)
{
std::set<db::properties_id_type> prop_ids;
if (layout.prop_id () != 0 && gds2_options.write_file_properties) {
prop_ids.insert (layout.prop_id ());
}
for (auto c = cells.begin (); c != cells.end (); ++c) {
const db::Cell &cell = layout.cell (*c);
if (cell.prop_id () != 0 && gds2_options.write_cell_properties) {
prop_ids.insert (cell.prop_id ());
}
for (auto i = cell.begin (); ! i.at_end (); ++i) {
if (i->prop_id () != 0) {
prop_ids.insert (i->prop_id ());
}
}
for (auto l = layers.begin (); l != layers.end (); ++l) {
const db::Shapes &shapes = cell.shapes (l->first);
for (auto s = shapes.begin (db::ShapeIterator::AllWithProperties); ! s.at_end (); ++s) {
if (s->prop_id () != 0) {
prop_ids.insert (s->prop_id ());
}
s.finish_array ();
}
}
}
const size_t max_string_length = 32768 - 6;
std::set<unsigned short> names_taken;
std::set<db::property_names_id_type> name_ids_to_translate;
std::set<db::property_values_id_type> value_ids_to_translate;
for (auto p = prop_ids.begin (); p != prop_ids.end (); ++p) {
const auto &props = db::properties (*p);
for (auto i = props.begin (); i != props.end (); ++i) {
const auto &pn = db::property_name (i->first);
const auto &pv = db::property_value (i->second);
if (pn.is_long ()) {
long iv = pn.to_long ();
if (iv > long (std::numeric_limits<unsigned short>::max ()) || iv < 0) {
name_ids_to_translate.insert (i->first);
} else {
names_taken.insert ((unsigned short) iv);
}
} else if (pn.is_ulong ()) {
unsigned long iv = pn.to_ulong ();
if (iv > (unsigned long) (std::numeric_limits<unsigned short>::max ())) {
name_ids_to_translate.insert (i->first);
} else {
names_taken.insert ((unsigned short) iv);
}
} else {
name_ids_to_translate.insert (i->first);
}
if (pv.is_array () || pv.is_list () || pv.is_user () ||
(pv.is_a_string () && strlen (pv.to_string ()) > max_string_length)) {
value_ids_to_translate.insert (i->second);
}
}
}
// Assign unique numerical keys to names, starting with big numbers
for (auto i = name_ids_to_translate.begin (); i != name_ids_to_translate.end (); ++i) {
m_prop_name_placeholders.insert (std::make_pair (db::property_name (*i), (unsigned short) 0));
}
unsigned short key = 32768;
for (auto i = m_prop_name_placeholders.begin (); i != m_prop_name_placeholders.end (); ++i) {
while (key > 0 && names_taken.find (--key) != names_taken.end ())
;
if (key == 0) {
// if the key reaches zero, we cannot translate further non-numerical property keys
tl::warn << tl::to_string (tr ("Too many non-numerical property keys present - cannot map them to limited GDS property name space"));
m_prop_name_placeholders.clear ();
name_ids_to_translate.clear ();
break;
} else {
i->second = key;
}
}
for (auto i = name_ids_to_translate.begin (); i != name_ids_to_translate.end (); ++i) {
const auto &n = db::property_name (*i);
auto p = m_prop_name_placeholders.find (n);
tl_assert (p != m_prop_name_placeholders.end ());
m_prop_names_map.insert (std::make_pair (*i, tl::Variant (p->second)));
}
// Assign "unique" placeholder strings for the values
for (auto i = value_ids_to_translate.begin (); i != value_ids_to_translate.end (); ++i) {
m_prop_value_placeholders.insert (std::make_pair (db::property_value (*i), std::string ()));
}
size_t value_index = 0;
for (auto i = m_prop_value_placeholders.begin (); i != m_prop_value_placeholders.end (); ++i) {
// TODO: check if this value really is unique
i->second = tl::sprintf ("klayout-prop-value#%u:%x", ++value_index, i->first.hash ());
}
for (auto i = value_ids_to_translate.begin (); i != value_ids_to_translate.end (); ++i) {
const auto &v = db::property_value (*i);
auto p = m_prop_value_placeholders.find (v);
tl_assert (p != m_prop_value_placeholders.end ());
m_prop_values_map.insert (std::make_pair (*i, tl::Variant (p->second)));
}
}
void
GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayoutOptions &options)
{
@ -465,7 +640,20 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
}
}
// collect property translations - these are needed to store properties with non-numerical keys
// and non-scalar values.
m_prop_names_map.clear ();
m_prop_name_placeholders.clear ();
m_prop_values_map.clear ();
m_prop_value_placeholders.clear ();
if (options.write_context_info () && gds2_options.extended_features) {
build_property_translations (layout, layers, cells, gds2_options);
}
// get current time
short time_data [6] = { 0, 0, 0, 0, 0, 0 };
if (gds2_options.write_timestamps) {
time_t ti = 0;
@ -485,6 +673,8 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
layout.add_meta_info ("mod_time", MetaInfo (tl::to_string (tr ("Modification Time")), str_time));
layout.add_meta_info ("access_time", MetaInfo (tl::to_string (tr ("Access Time")), str_time));
// initialize options
m_keep_instances = options.keep_instances ();
m_multi_xy = gds2_options.multi_xy_records;
m_max_vertex_count = std::max (gds2_options.max_vertex_count, (unsigned int)4);
@ -544,32 +734,45 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
// layout properties
if (layout.prop_id () != 0) {
if (gds2_options.write_file_properties) {
try {
write_properties (layout, layout.prop_id ());
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties")));
}
} else if ()
if (layout.prop_id () != 0 && gds2_options.write_file_properties) {
try {
write_properties (layout, layout.prop_id ());
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties")));
}
}
// write context info
// A context info header ("context cell") is needed, if
// * The layout or the cells explicitly need context info (meta data, library references etc.)
// * layout or cell properties are present and "write_file_properties" or "write_cell_properties" is OFF.
// * Property names or values need to be translated
// * Named layers are present
bool has_context = false;
if (options.write_context_info ()) {
@@@ require a context if meta data has to be added
if (! has_context) {
has_context = layout.has_context_info ();
has_context = layout.has_context_info () ||
(! m_prop_names_map.empty () || ! m_prop_values_map.empty ()) ||
(layout.prop_id () != 0 && ! gds2_options.write_file_properties && gds2_options.extended_features);
for (auto cell = cells.begin (); cell != cells.end () && ! has_context; ++cell) {
has_context = layout.has_context_info (*cell) ||
(layout.cell (*cell).prop_id () != 0 && ! gds2_options.write_cell_properties && gds2_options.extended_features);
}
for (std::vector<db::cell_index_type>::const_iterator cell = cells.begin (); cell != cells.end () && ! has_context; ++cell) {
has_context = layout.has_context_info (*cell);
if (gds2_options.extended_features) {
for (auto layer = layers.begin (); layer != layers.end () && ! has_context; ++layer) {
has_context = ! layer->second.name.empty ();
}
}
}
if (has_context) {
try {
write_context_cell (layout, time_data, cells);
write_context_cell (layout, time_data, cells, layers, gds2_options);
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::to_string (tr (", writing context cell")));
}
@ -1120,8 +1323,8 @@ GDS2WriterBase::write_properties (const db::Layout & /*layout*/, db::properties_
auto pn = m_prop_names_map.find (p->first);
auto pv = m_prop_values_map.find (p->second);
const tl::Variant &value = (pn == m_prop_names_map.end ()) ? db::property_value (p->second) : pn->second;
const tl::Variant &name = (pv == m_prop_values_map.end ()) ? db::property_name (p->first) : pv->second;
const tl::Variant &value = (pv == m_prop_values_map.end ()) ? db::property_value (p->second) : pv->second;
const tl::Variant &name = (pn == m_prop_names_map.end ()) ? db::property_name (p->first) : pn->second;
long attr = -1;
if (name.can_convert_to_long ()) {
@ -1181,7 +1384,7 @@ GDS2WriterBase::collect_property_ids (std::set<db::properties_id_type> &property
for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) {
if (inst->has_prop_id () && inst->prop_id () != 0) {
prop_ids_done.insert (inst->prop_id ());
property_ids.insert (inst->prop_id ());
}
}
@ -1189,7 +1392,7 @@ GDS2WriterBase::collect_property_ids (std::set<db::properties_id_type> &property
db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Properties | db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::Paths | db::ShapeIterator::Texts));
while (! shape.at_end ()) {
if (shape->has_prop_id () && shape->prop_id () != 0) {
prop_ids_done.insert (shape->prop_id ());
property_ids.insert (shape->prop_id ());
}
shape.finish_array ();
}
@ -1198,11 +1401,5 @@ GDS2WriterBase::collect_property_ids (std::set<db::properties_id_type> &property
}
}
void
GDS2WriterBase::build_property_maps (const std::set<db::properties_id_type> &property_ids)
{
}
} // namespace db

View File

@ -178,15 +178,18 @@ private:
double m_default_text_size;
std::map<db::property_values_id_type, tl::Variant> m_prop_values_map;
std::map<db::property_names_id_type, tl::Variant> m_prop_names_map;
std::map<tl::Variant, unsigned short> m_prop_name_placeholders;
std::map<tl::Variant, std::string> m_prop_value_placeholders;
void write_properties (const db::Layout &layout, db::properties_id_type prop_id);
void write_context_cell (db::Layout &layout, const short *time_data, const std::vector<cell_index_type> &cells);
void write_context_cell (db::Layout &layout, const short *time_data, const std::vector<cell_index_type> &cells, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers, const db::GDS2WriterOptions &gds2_options);
void write_context_string (size_t n, const std::string &s);
void write_cell (db::Layout &layout, const db::Cell &cref, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers,
const std::set <db::cell_index_type> &cell_set, double sf, short *time_data);
void write_shape (const db::Layout &layout, int layer, int datatype, const db::Shape &shape, double sf);
void collect_property_ids (std::set<db::properties_id_type> &property_ids, const db::Layout &layout, const std::vector<cell_index_type> &cells, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers);
void build_property_maps (const std::set<db::properties_id_type> &property_ids);
void build_property_translations (const db::Layout &layout, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers, const std::vector <db::cell_index_type> &cells, const db::GDS2WriterOptions &gds2_options);
void get_property_map_context (std::vector <std::string> &context_strings);
};
} // namespace db

View File

@ -115,6 +115,16 @@ static bool get_gds2_write_timestamps (const db::SaveLayoutOptions *options)
return options->get_options<db::GDS2WriterOptions> ().write_timestamps;
}
static void set_gds2_extended_features (db::SaveLayoutOptions *options, bool n)
{
options->get_options<db::GDS2WriterOptions> ().extended_features = n;
}
static bool get_gds2_extended_features(const db::SaveLayoutOptions *options)
{
return options->get_options<db::GDS2WriterOptions> ().extended_features;
}
static void set_gds2_default_text_size (db::SaveLayoutOptions *options, const tl::Variant &v)
{
options->get_options<db::GDS2WriterOptions> ().default_text_size = v.is_nil () ? -1.0 : v.to_double ();
@ -190,6 +200,29 @@ gsi::ClassExt<db::SaveLayoutOptions> gds2_writer_options (
"@brief Gets a value indicating whether the current time is written into the GDS2 timestamp fields\n"
"\nThis property has been added in version 0.21.16.\n"
) +
gsi::method_ext ("gds2_extended_features=", &set_gds2_extended_features, gsi::arg ("flag"),
"@brief Enables extended features if set to true\n"
"\n"
"With extended features enabled, the GDS2 writer will support the following features:\n"
"\n"
"@ul\n"
"@li Long property value strings and complex types such a lists @/li\n"
"@li Non-numerical property names - i.e. strings @/li\n"
"@li File and cell level properties in a backward compatible way and with the respective option turned off @/li\n"
"@li Layer names - this includes empty layers, so this is a way to indicate the presence of a layer without a shape on it @/li\n"
"@/ul\n"
"\n"
"KLayout uses the context to implement these features. Therefore, this option is not compatible with \\write_context_info off.\n"
"By default, this feature is enabled.\n"
"\n"
"\nThis property has been added in version 0.30.7.\n"
) +
gsi::method_ext ("gds2_extended_features?", &get_gds2_extended_features,
"@brief Gets a value indicating whether extended features are enabled\n"
"See \\gds2_extended_features= for a description of the extended features.\n"
"\n"
"\nThis property has been added in version 0.30.7.\n"
) +
gsi::method_ext ("gds2_default_text_size=", &set_gds2_default_text_size, gsi::arg ("size"),
"@brief Specifies the default text size to use when a text does not have a size\n"
"\n"

View File

@ -1577,4 +1577,415 @@ TEST(166)
run_test (_this, "t166.oas.gz", "t166_au.gds.gz", false, opt);
}
static std::string p2s (db::properties_id_type pid)
{
return db::properties (pid).to_dict_var ().to_parsable_string ();
}
namespace {
/**
* @brief Installs a temporary repository instance for testing
*
* By using a temp instance, we do not disturb other tests.
*/
class TempPropertiesRepository
{
public:
TempPropertiesRepository ()
{
db::PropertiesRepository::replace_instance_temporarily (&m_temp);
}
~TempPropertiesRepository ()
{
db::PropertiesRepository::replace_instance_temporarily (0);
}
private:
db::PropertiesRepository m_temp;
};
}
// Layout and cell properties are written to the context cell unless this is allowed by "write_cell/file_properties"
TEST(200_extended_props)
{
TempPropertiesRepository temp_pr;
db::GDS2WriterOptions gds2_opt;
gds2_opt.write_cell_properties = false;
gds2_opt.write_file_properties = false;
db::PropertiesSet ps1;
ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5));
ps1.insert (tl::Variant (17), 2.5);
db::PropertiesSet ps2;
tl::Variant l = tl::Variant::empty_list ();
l.push (17);
l.push ("X");
ps2.insert (tl::Variant ("prop_name2"), l);
ps2.insert (tl::Variant (42), "A string");
db::Layout layout_org;
layout_org.prop_id (db::properties_id (ps1));
db::Cell &xcell = layout_org.cell (layout_org.add_cell ("X"));
xcell.prop_id (db::properties_id (ps2));
EXPECT_EQ (p2s (layout_org.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
EXPECT_EQ (p2s (xcell.prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_200.gds");
{
tl::OutputStream out (tmp_file);
db::SaveLayoutOptions options;
options.set_options (gds2_opt);
db::Writer writer (options);
writer.write (layout_org, out);
}
db::Layout layout_read;
{
tl::InputStream in (tmp_file);
db::Reader reader (in);
reader.read (layout_read);
}
EXPECT_EQ (p2s (layout_read.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
auto xc = layout_read.cell_by_name ("X");
tl_assert (xc.first);
EXPECT_EQ (p2s (layout_read.cell (xc.second).prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
}
// Without a context cell, layout and file properties are written if requested, but only
// numerical property keys are supported
TEST(201_extended_props)
{
TempPropertiesRepository temp_pr;
db::GDS2WriterOptions gds2_opt;
gds2_opt.write_cell_properties = true;
gds2_opt.write_file_properties = true;
db::SaveLayoutOptions options;
options.set_write_context_info (false);
options.set_options (gds2_opt);
db::PropertiesSet ps1;
ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5));
ps1.insert (tl::Variant (17), 2.5);
db::PropertiesSet ps2;
tl::Variant l = tl::Variant::empty_list ();
l.push (17);
l.push ("X");
ps2.insert (tl::Variant ("prop_name2"), l);
ps2.insert (tl::Variant (42), "A string");
db::Layout layout_org;
layout_org.prop_id (db::properties_id (ps1));
db::Cell &xcell = layout_org.cell (layout_org.add_cell ("X"));
xcell.prop_id (db::properties_id (ps2));
EXPECT_EQ (p2s (layout_org.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
EXPECT_EQ (p2s (xcell.prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_201.gds");
{
tl::OutputStream out (tmp_file);
db::Writer writer (options);
writer.write (layout_org, out);
}
db::Layout layout_read;
{
tl::InputStream in (tmp_file);
db::Reader reader (in);
reader.read (layout_read);
}
EXPECT_EQ (p2s (layout_read.prop_id ()), "{#17=>'2.5'}");
auto xc = layout_read.cell_by_name ("X");
tl_assert (xc.first);
EXPECT_EQ (p2s (layout_read.cell (xc.second).prop_id ()), "{#42=>'A string'}");
}
// With a context cell, layout and file properties are written if requested, and property
// name and value translation happens
TEST(202_extended_props)
{
TempPropertiesRepository temp_pr;
db::GDS2WriterOptions gds2_opt;
gds2_opt.write_cell_properties = true;
gds2_opt.write_file_properties = true;
db::SaveLayoutOptions options;
options.set_options (gds2_opt);
db::PropertiesSet ps1;
ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5));
ps1.insert (tl::Variant (17), 2.5);
db::PropertiesSet ps2;
tl::Variant l = tl::Variant::empty_list ();
l.push (17);
l.push ("X");
ps2.insert (tl::Variant ("prop_name2"), l);
ps2.insert (tl::Variant (42), "A string");
db::Layout layout_org;
layout_org.prop_id (db::properties_id (ps1));
db::Cell &xcell = layout_org.cell (layout_org.add_cell ("X"));
xcell.prop_id (db::properties_id (ps2));
EXPECT_EQ (p2s (layout_org.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
EXPECT_EQ (p2s (xcell.prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_202.gds");
{
tl::OutputStream out (tmp_file);
db::Writer writer (options);
writer.write (layout_org, out);
}
db::Layout layout_read;
{
tl::InputStream in (tmp_file);
db::Reader reader (in);
reader.read (layout_read);
}
EXPECT_EQ (p2s (layout_read.prop_id ()), "{#17=>'2.5','prop_name'=>[dbox:(0,0;1.5,2.5)]}");
auto xc = layout_read.cell_by_name ("X");
tl_assert (xc.first);
EXPECT_EQ (p2s (layout_read.cell (xc.second).prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
}
// With a context cell, shape and instance properties can have non-numeric names
// and complex types for values
TEST(203_extended_props)
{
TempPropertiesRepository temp_pr;
db::GDS2WriterOptions gds2_opt;
db::SaveLayoutOptions options;
options.set_options (gds2_opt);
db::PropertiesSet ps1;
ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5));
ps1.insert (tl::Variant (17), 2.5);
db::PropertiesSet ps2;
tl::Variant l = tl::Variant::empty_list ();
l.push (17);
l.push ("X");
ps2.insert (tl::Variant ("prop_name2"), l);
ps2.insert (tl::Variant (42), "A string");
db::Layout layout_org;
unsigned int l1 = layout_org.insert_layer (db::LayerProperties (1, 0, "NAME"));
layout_org.insert_layer (db::LayerProperties (2, 17, "U"));
db::Cell &xcell = layout_org.cell (layout_org.add_cell ("X"));
db::Shape shape = xcell.shapes (l1).insert (db::BoxWithProperties (db::Box (0, 0, 1000, 2000), db::properties_id (ps1)));
db::Cell &ycell = layout_org.cell (layout_org.add_cell ("Y"));
db::Instance instance = xcell.insert (db::CellInstArrayWithProperties (db::CellInstArray (ycell.cell_index (), db::Trans ()), db::properties_id (ps2)));
EXPECT_EQ (p2s (shape.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
EXPECT_EQ (p2s (instance.prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_203.gds");
{
tl::OutputStream out (tmp_file);
db::Writer writer (options);
writer.write (layout_org, out);
}
layout_org.clear ();
{
db::Layout layout_read;
{
tl::InputStream in (tmp_file);
db::Reader reader (in);
reader.read (layout_read);
}
unsigned int l1 = layout_read.get_layer (db::LayerProperties (1, 0));
int l2d17 = layout_read.get_layer_maybe (db::LayerProperties (2, 17));
// layer names are also persisted, 2/17 is created even as it is empty
EXPECT_EQ (layout_read.get_properties (l1).name, "NAME");
EXPECT_EQ (l2d17 >= 0, true);
if (l2d17 >= 0) {
EXPECT_EQ (layout_read.get_properties (l2d17).name, "U");
}
auto xc = layout_read.cell_by_name ("X");
tl_assert (xc.first);
const db::Cell &xcell = layout_read.cell (xc.second);
auto s = xcell.shapes (l1).begin (db::ShapeIterator::All);
tl_assert (! s.at_end ());
EXPECT_EQ (p2s (s->prop_id ()), "{#17=>'2.5','prop_name'=>[dbox:(0,0;1.5,2.5)]}");
auto i = xcell.begin ();
tl_assert (! i.at_end ());
EXPECT_EQ (p2s (i->prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
}
}
// Without a context cell, shape and instance properties cannot have non-numeric names
// or complex types for values
TEST(204_extended_props)
{
TempPropertiesRepository temp_pr;
db::GDS2WriterOptions gds2_opt;
db::SaveLayoutOptions options;
options.set_write_context_info (false);
options.set_options (gds2_opt);
db::PropertiesSet ps1;
ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5));
ps1.insert (tl::Variant (17), 2.5);
db::PropertiesSet ps2;
tl::Variant l = tl::Variant::empty_list ();
l.push (17);
l.push ("X");
ps2.insert (tl::Variant ("prop_name2"), l);
ps2.insert (tl::Variant (42), "A string");
db::Layout layout_org;
unsigned int l1 = layout_org.insert_layer (db::LayerProperties (1, 0, "NAME"));
db::Cell &xcell = layout_org.cell (layout_org.add_cell ("X"));
db::Shape shape = xcell.shapes (l1).insert (db::BoxWithProperties (db::Box (0, 0, 1000, 2000), db::properties_id (ps1)));
db::Cell &ycell = layout_org.cell (layout_org.add_cell ("Y"));
db::Instance instance = xcell.insert (db::CellInstArrayWithProperties (db::CellInstArray (ycell.cell_index (), db::Trans ()), db::properties_id (ps2)));
EXPECT_EQ (p2s (shape.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
EXPECT_EQ (p2s (instance.prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_204.gds");
{
tl::OutputStream out (tmp_file);
db::Writer writer (options);
writer.write (layout_org, out);
}
layout_org.clear ();
{
db::Layout layout_read;
{
tl::InputStream in (tmp_file);
db::Reader reader (in);
reader.read (layout_read);
}
unsigned int l1 = layout_read.get_layer (db::LayerProperties (1, 0));
int l2d17 = layout_read.get_layer_maybe (db::LayerProperties (2, 17));
// layer names are not persisted, 2/17 is not created
EXPECT_EQ (layout_read.get_properties (l1).name, "");
EXPECT_EQ (l2d17 >= 0, false);
auto xc = layout_read.cell_by_name ("X");
tl_assert (xc.first);
const db::Cell &xcell = layout_read.cell (xc.second);
auto s = xcell.shapes (l1).begin (db::ShapeIterator::All);
tl_assert (! s.at_end ());
EXPECT_EQ (p2s (s->prop_id ()), "{#17=>'2.5'}");
auto i = xcell.begin ();
tl_assert (! i.at_end ());
EXPECT_EQ (p2s (i->prop_id ()), "{#42=>'A string'}");
}
}
// Without extended features enabled, shape and instance properties cannot have non-numeric names
// or complex types for values
TEST(205_extended_props)
{
TempPropertiesRepository temp_pr;
db::GDS2WriterOptions gds2_opt;
gds2_opt.extended_features = false;
db::SaveLayoutOptions options;
options.set_options (gds2_opt);
db::PropertiesSet ps1;
ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5));
ps1.insert (tl::Variant (17), 2.5);
db::PropertiesSet ps2;
tl::Variant l = tl::Variant::empty_list ();
l.push (17);
l.push ("X");
ps2.insert (tl::Variant ("prop_name2"), l);
ps2.insert (tl::Variant (42), "A string");
db::Layout layout_org;
unsigned int l1 = layout_org.insert_layer (db::LayerProperties (1, 0, "NAME"));
db::Cell &xcell = layout_org.cell (layout_org.add_cell ("X"));
db::Shape shape = xcell.shapes (l1).insert (db::BoxWithProperties (db::Box (0, 0, 1000, 2000), db::properties_id (ps1)));
db::Cell &ycell = layout_org.cell (layout_org.add_cell ("Y"));
db::Instance instance = xcell.insert (db::CellInstArrayWithProperties (db::CellInstArray (ycell.cell_index (), db::Trans ()), db::properties_id (ps2)));
EXPECT_EQ (p2s (shape.prop_id ()), "{#17=>##2.5,'prop_name'=>[dbox:(0,0;1.5,2.5)]}");
EXPECT_EQ (p2s (instance.prop_id ()), "{#42=>'A string','prop_name2'=>(#17,'X')}");
std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_205.gds");
{
tl::OutputStream out (tmp_file);
db::Writer writer (options);
writer.write (layout_org, out);
}
layout_org.clear ();
{
db::Layout layout_read;
{
tl::InputStream in (tmp_file);
db::Reader reader (in);
reader.read (layout_read);
}
unsigned int l1 = layout_read.get_layer (db::LayerProperties (1, 0));
auto xc = layout_read.cell_by_name ("X");
tl_assert (xc.first);
const db::Cell &xcell = layout_read.cell (xc.second);
auto s = xcell.shapes (l1).begin (db::ShapeIterator::All);
tl_assert (! s.at_end ());
EXPECT_EQ (p2s (s->prop_id ()), "{#17=>'2.5'}");
auto i = xcell.begin ();
tl_assert (! i.at_end ());
EXPECT_EQ (p2s (i->prop_id ()), "{#42=>'A string'}");
}
}

View File

@ -479,30 +479,6 @@ OASISReader::warn (const std::string &msg, int wl)
}
}
/**
* @brief A helper class to join two datatype layer name map members
*/
struct LNameJoinOp1
{
void operator() (std::string &a, const std::string &b)
{
join_layer_names (a, b);
}
};
/**
* @brief A helper class to join two layer map members
* This implementation basically merged the datatype maps.
*/
struct LNameJoinOp2
{
void operator() (tl::interval_map<db::ld_type, std::string> &a, const tl::interval_map<db::ld_type, std::string> &b)
{
LNameJoinOp1 op1;
a.add (b.begin (), b.end (), op1);
}
};
/**
* @brief Marks the beginning of a new table
*