mirror of https://github.com/KLayout/klayout.git
Implementation of GDS2 extended features. Currently, the extended features are enabled by default.
This commit is contained in:
parent
91b4714a91
commit
1f4126036d
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in New Issue