diff --git a/src/buddies/src/bd/bdWriterOptions.cc b/src/buddies/src/bd/bdWriterOptions.cc index dc8bc4287..f4eacb492 100644 --- a/src/buddies/src/bd/bdWriterOptions.cc +++ b/src/buddies/src/bd/bdWriterOptions.cc @@ -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); diff --git a/src/buddies/src/bd/bdWriterOptions.h b/src/buddies/src/bd/bdWriterOptions.h index 67119d1d8..50cdd19bd 100644 --- a/src/buddies/src/bd/bdWriterOptions.h +++ b/src/buddies/src/bd/bdWriterOptions.h @@ -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; diff --git a/src/db/db/dbReader.h b/src/db/db/dbReader.h index 374aecdf3..f01189ee8 100644 --- a/src/db/db/dbReader.h +++ b/src/db/db/dbReader.h @@ -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 &a, const tl::interval_map &b) + { + LNameJoinOp1 op1; + a.add (b.begin (), b.end (), op1); + } +}; + /** * @brief The generic reader base class */ diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2Format.h b/src/plugins/streamers/gds2/db_plugin/dbGDS2Format.h index cd6c7f769..c713184c3 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2Format.h +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2Format.h @@ -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) * diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc index a5fba5fba..fd14848b7 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.cc @@ -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 &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 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 &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 > 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 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"))); } diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.h b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.h index 2660d9d5e..ed6d06480 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.h +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2ReaderBase.h @@ -89,6 +89,8 @@ private: unsigned int m_box_mode; std::map > m_context_info; std::vector m_all_points; + std::map m_property_names_map; + std::map 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 &instances, tl::vector &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 &context, db::PropertiesSet &properties) const; + void digest_context (Layout &layout, const std::vector &context); std::pair finish_element_with_props (); void finish_element (); diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc index f8e4e93f3..55e45a43c 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc @@ -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 &cells) +GDS2WriterBase::get_property_map_context (std::vector &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 &cells, const std::vector > &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 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 ::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 > &layers, const std::vector &cells, const db::GDS2WriterOptions &gds2_options) +{ + std::set 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 names_taken; + std::set name_ids_to_translate; + std::set 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::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::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::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 &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 &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 &property } } -void -GDS2WriterBase::build_property_maps (const std::set &property_ids) -{ - -} - } // namespace db diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h index 95dd5d1eb..5d041aff2 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h @@ -178,15 +178,18 @@ private: double m_default_text_size; std::map m_prop_values_map; std::map m_prop_names_map; + std::map m_prop_name_placeholders; + std::map 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 &cells); + void write_context_cell (db::Layout &layout, const short *time_data, const std::vector &cells, const std::vector > &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 > &layers, const std::set &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 &property_ids, const db::Layout &layout, const std::vector &cells, const std::vector > &layers); - void build_property_maps (const std::set &property_ids); + void build_property_translations (const db::Layout &layout, const std::vector > &layers, const std::vector &cells, const db::GDS2WriterOptions &gds2_options); + void get_property_map_context (std::vector &context_strings); }; } // namespace db diff --git a/src/plugins/streamers/gds2/db_plugin/gsiDeclDbGDS2.cc b/src/plugins/streamers/gds2/db_plugin/gsiDeclDbGDS2.cc index 55f9abcbd..e88b7a5f4 100644 --- a/src/plugins/streamers/gds2/db_plugin/gsiDeclDbGDS2.cc +++ b/src/plugins/streamers/gds2/db_plugin/gsiDeclDbGDS2.cc @@ -115,6 +115,16 @@ static bool get_gds2_write_timestamps (const db::SaveLayoutOptions *options) return options->get_options ().write_timestamps; } +static void set_gds2_extended_features (db::SaveLayoutOptions *options, bool n) +{ + options->get_options ().extended_features = n; +} + +static bool get_gds2_extended_features(const db::SaveLayoutOptions *options) +{ + return options->get_options ().extended_features; +} + static void set_gds2_default_text_size (db::SaveLayoutOptions *options, const tl::Variant &v) { options->get_options ().default_text_size = v.is_nil () ? -1.0 : v.to_double (); @@ -190,6 +200,29 @@ gsi::ClassExt 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" diff --git a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc index 36c393681..21f1df5bb 100644 --- a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc +++ b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc @@ -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'}"); + } +} diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc index 2b3256808..1ad137915 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc @@ -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 &a, const tl::interval_map &b) - { - LNameJoinOp1 op1; - a.add (b.begin (), b.end (), op1); - } -}; - /** * @brief Marks the beginning of a new table *