From c1b4f97f337ffc84dce7f486dec0daf6d4c86c2d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 14 Feb 2026 22:03:17 +0100 Subject: [PATCH 01/15] Some fill tool enhancements * It's possible to specify the fill cell origin now (this is the point where the first cell's lower left is placed) * margin is taken into account when no step vectors are specified * "No exclude" is default --- src/lay/lay/FillDialog.ui | 367 +++++++++++++++++++++-------------- src/lay/lay/layFillDialog.cc | 37 +++- src/lay/lay/layFillDialog.h | 2 + 3 files changed, 254 insertions(+), 152 deletions(-) diff --git a/src/lay/lay/FillDialog.ui b/src/lay/lay/FillDialog.ui index 592435ffb..c78340400 100644 --- a/src/lay/lay/FillDialog.ui +++ b/src/lay/lay/FillDialog.ui @@ -86,87 +86,6 @@ - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Select how the region to fill is specified. - - - - All (whole cell) - - - - - Shapes on layer ... - - - - - Selected shapes - - - - - Single box with ... - - - - - Ruler bounding boxes - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 20 - - - - - - - @@ -277,22 +196,6 @@ 6 - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 241 - 20 - - - - @@ -316,6 +219,22 @@ + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 241 + 20 + + + + @@ -436,6 +355,87 @@ + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Select how the region to fill is specified. + + + + All (whole cell) + + + + + Shapes on layer ... + + + + + Selected shapes + + + + + Single box with ... + + + + + Ruler bounding boxes + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + @@ -491,6 +491,9 @@ The fill will not be generated over the areas specified by these layers + + 3 + All layers @@ -810,6 +813,83 @@ + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Raster origin + + + + + + + + 0 + 0 + + + + + + + + (x, y of lower-left corner of fill cell in µm) + + + + + + + Qt::Horizontal + + + + 135 + 20 + + + + + + + @@ -837,13 +917,6 @@ 6 - - - - µm (keep distance between fill cells unless stitched) - - - @@ -851,39 +924,13 @@ - - - - - 0 - 0 - - - - Leave empty for no distance. Otherwise enter a distance in micron (can be anisotropic in the form "dx,dy") - - - - - + + - ... + µm (keep distance between fill cells unless stitched) - - - - Qt::Horizontal - - - - 141 - 20 - - - - @@ -891,6 +938,13 @@ + + + + ... + + + @@ -904,16 +958,6 @@ - - - - The second order fill cell is used to fill space remaining from the first fill step. Thus, the second order fill cell must be smaller than the first order fill cell. The boundary layer must be the same for the second order fill cell. - - - true - - - @@ -980,6 +1024,42 @@ + + + + Qt::Horizontal + + + + 141 + 20 + + + + + + + + The second order fill cell is used to fill space remaining from the first fill step. Thus, the second order fill cell must be smaller than the first order fill cell. The boundary layer must be the same for the second order fill cell. + + + true + + + + + + + + 0 + 0 + + + + Leave empty for no distance. Otherwise enter a distance in micron (can be anisotropic in the form "dx,dy") + + + @@ -1030,6 +1110,7 @@ row_le column_le enhanced_cb + origin_le second_order_fill_cb fill_cell_2nd_le choose_fc_2nd_pb diff --git a/src/lay/lay/layFillDialog.cc b/src/lay/lay/layFillDialog.cc index 6ed037fc8..1d02e8241 100644 --- a/src/lay/lay/layFillDialog.cc +++ b/src/lay/lay/layFillDialog.cc @@ -92,6 +92,7 @@ FillDialog::FillDialog (QWidget *parent, LayoutViewBase *view) fill_area_stack->setCurrentIndex (0); connect (fill_area_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (fill_area_changed (int))); + connect (enhanced_cb, SIGNAL (stateChanged (int)), this, SLOT (enhanced_fill_changed (int))); connect (button_box, SIGNAL (accepted ()), this, SLOT (ok_pressed ())); connect (choose_fc_pb, SIGNAL (clicked ()), this, SLOT (choose_fc ())); connect (choose_fc_2nd_pb, SIGNAL (clicked ()), this, SLOT (choose_fc_2nd ())); @@ -167,6 +168,8 @@ FillDialog::generate_fill (const FillParameters &fp) bool enhanced_fill = enhanced_cb->isChecked (); + db::Point fill_origin = db::CplxTrans (ly.dbu ()).inverted () * fp.fill_origin; + db::Coord exclude_x = db::coord_traits::rounded (fp.exclude_distance.x () / ly.dbu ()); db::Coord exclude_y = db::coord_traits::rounded (fp.exclude_distance.y () / ly.dbu ()); @@ -232,8 +235,6 @@ FillDialog::generate_fill (const FillParameters &fp) fill_region.merge (); } - db::Box fr_bbox = fill_region.bbox (); - if (tl::verbosity () >= 20) { tl::info << "Collecting exclude areas"; } @@ -263,8 +264,6 @@ FillDialog::generate_fill (const FillParameters &fp) // Perform the NOT operation to create the fill region fill_region -= es; - db::Region new_fill_area; - int step = 0; do { @@ -276,7 +275,7 @@ FillDialog::generate_fill (const FillParameters &fp) } if (! enhanced_fill) { - db::fill_region (cv.cell (), fill_region, fill_cell->cell_index (), fc_bbox, row_step, column_step, fr_bbox.p1 (), false, fill_cell2 ? &fill_region : 0, fill_margin, fill_cell2 ? &fill_region : 0); + db::fill_region (cv.cell (), fill_region, fill_cell->cell_index (), fc_bbox, row_step, column_step, fill_origin, false, fill_cell2 ? &fill_region : 0, fill_margin, fill_cell2 ? &fill_region : 0); } else { db::fill_region_repeat (cv.cell (), fill_region, fill_cell->cell_index (), fc_bbox, row_step, column_step, fill_margin, fill_cell2 ? &fill_region : 0); } @@ -478,6 +477,20 @@ FillDialog::get_fill_parameters () fp.enhanced_fill = enhanced_cb->isChecked (); + // read origin + x = 0.0, y = 0.0; + s = tl::to_string (origin_le->text ()); + ex = tl::Extractor (s.c_str ()); + if (ex.try_read (x)) { + if (ex.test (",") && ex.try_read (y)) { + // take x, y + } else { + y = x; + } + } + + fp.fill_origin = db::DPoint (x, y); + db::DBox fc_bbox = db::CplxTrans (cv->layout ().dbu ()) * (fc_bbox_layer < 0 ? fill_cell->bbox () : fill_cell->bbox (fc_bbox_layer)); if (fc_bbox.empty ()) { if (fc_bbox_layer >= 0) { @@ -492,7 +505,7 @@ FillDialog::get_fill_parameters () if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) { fp.row_step = db::DVector (x, y); } else { - fp.row_step = db::DVector (fc_bbox.width (), 0.0); + fp.row_step = db::DVector (fc_bbox.width () + fp.fill_cell_margin.x (), 0.0); } s = tl::to_string (column_le->text ()); @@ -500,7 +513,7 @@ FillDialog::get_fill_parameters () if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) { fp.column_step = db::DVector (x, y); } else { - fp.column_step = db::DVector (0.0, fc_bbox.height ()); + fp.column_step = db::DVector (0.0, fc_bbox.height () + fp.fill_cell_margin.y ()); } fp.fc_bbox = fc_bbox; @@ -528,7 +541,7 @@ FillDialog::get_fill_parameters () if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) { fp.row_step2 = db::DVector (x, y); } else { - fp.row_step2 = db::DVector (fc_bbox2.width (), 0.0); + fp.row_step2 = db::DVector (fc_bbox2.width () + fp.fill_cell_margin2.x (), 0.0); } s = tl::to_string (column_2nd_le->text ()); @@ -536,7 +549,7 @@ FillDialog::get_fill_parameters () if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) { fp.column_step2 = db::DVector (x, y); } else { - fp.column_step2 = db::DVector (0.0, fc_bbox2.height ()); + fp.column_step2 = db::DVector (0.0, fc_bbox2.height () + fp.fill_cell_margin2.y ()); } fp.fc_bbox2 = fc_bbox2; @@ -575,6 +588,12 @@ BEGIN_PROTECTED END_PROTECTED } +void +FillDialog::enhanced_fill_changed (int ef) +{ + origin_le->setEnabled (ef != Qt::Checked); +} + void FillDialog::fill_area_changed (int fa) { diff --git a/src/lay/lay/layFillDialog.h b/src/lay/lay/layFillDialog.h index cbd614e0f..bb4706cd5 100644 --- a/src/lay/lay/layFillDialog.h +++ b/src/lay/lay/layFillDialog.h @@ -53,6 +53,7 @@ struct LAY_PUBLIC FillParameters bool exclude_all_layers; std::vector exclude_layers; FillRegionMode fill_region_mode; + db::DPoint fill_origin; db::Region fill_region; db::LayerProperties fill_region_layer; db::DVector exclude_distance; @@ -81,6 +82,7 @@ Q_OBJECT public slots: void fill_area_changed (int); + void enhanced_fill_changed (int); void ok_pressed (); void choose_fc (); void choose_fc_2nd (); From c3989f63e3f7b3fd0913c10fc990145de6c28a97 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 14 Feb 2026 23:38:48 +0100 Subject: [PATCH 02/15] [consider merging] Performance improvement of netlist step (see https://www.klayout.de/forum/discussion/2839/drc-scripts-reports-and-parallelism#latest) --- src/db/db/dbHierNetworkProcessor.cc | 68 ++++++++++++++++++++++++++++- src/db/db/dbHierNetworkProcessor.h | 7 +++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbHierNetworkProcessor.cc b/src/db/db/dbHierNetworkProcessor.cc index be8f84f17..f187468fd 100644 --- a/src/db/db/dbHierNetworkProcessor.cc +++ b/src/db/db/dbHierNetworkProcessor.cc @@ -1686,6 +1686,69 @@ connected_clusters::join_cluster_with (typename local_cluster::id_type id, } } +template +void +connected_clusters::join_clusters_with (typename local_cluster::id_type id, typename std::set::id_type>::const_iterator with_from, typename std::set::id_type>::const_iterator with_to) +{ + if (with_from != with_to && *with_from == id) { + ++with_from; + } + if (with_from == with_to) { + return; + } + + connections_type &target = m_connections [id]; + std::set target_set; + bool target_set_valid = false; + + for (auto w = with_from; w != with_to; ++w) { + + local_clusters::join_cluster_with (id, *w); + + // handle the connections by translating + + typename std::map::id_type, connections_type>::iterator tc = m_connections.find (*w); + if (tc != m_connections.end ()) { + + connections_type &to_join = tc->second; + + for (connections_type::const_iterator c = to_join.begin (); c != to_join.end (); ++c) { + m_rev_connections [*c] = id; + } + + if (target.empty ()) { + + target.swap (to_join); + + } else if (! to_join.empty ()) { + + // Join while removing duplicates + if (! target_set_valid) { + target_set.insert (target.begin (), target.end ()); + target_set_valid = true; + } + + for (auto j = to_join.begin (); j != to_join.end (); ++j) { + if (target_set.find (*j) == target_set.end ()) { + target.push_back (*j); + target_set.insert (*j); + } + } + + } + + m_connections.erase (tc); + + } + + if (m_connected_clusters.find (*w) != m_connected_clusters.end ()) { + m_connected_clusters.insert (id); + m_connected_clusters.erase (*w); + } + + } +} + template typename local_cluster::id_type connected_clusters::find_cluster_with_connection (const ClusterInstance &inst) const @@ -1995,8 +2058,9 @@ struct hc_receiver typename std::set::const_iterator c = sc->begin (); typename std::set::const_iterator cc = c; - for (++cc; cc != sc->end (); ++cc) { - mp_cell_clusters->join_cluster_with (*c, *cc); + ++cc; + if (cc != sc->end ()) { + mp_cell_clusters->join_clusters_with (*c, cc, sc->end ()); } } diff --git a/src/db/db/dbHierNetworkProcessor.h b/src/db/db/dbHierNetworkProcessor.h index 8c8d588e7..e24605270 100644 --- a/src/db/db/dbHierNetworkProcessor.h +++ b/src/db/db/dbHierNetworkProcessor.h @@ -1274,6 +1274,13 @@ class DB_PUBLIC_TEMPLATE connected_clusters */ void join_cluster_with (typename local_cluster::id_type id, typename local_cluster::id_type with_id); + /** + * @brief Joins a cluster id with the a set of clusters given by an iterator interval with_to .. with_from + * + * This function is equivalent to calling "join_cluster_with" multiple times, but more efficient. + */ + void join_clusters_with (typename local_cluster::id_type id, typename std::set::id_type>::const_iterator with_from, typename std::set::id_type>::const_iterator with_to); + /** * @brief An iterator delivering all clusters (even the connectors) * From 199cf1edfa038b99fa2e85199e95a7a016049f7d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 15 Feb 2026 15:17:13 +0100 Subject: [PATCH 03/15] Small enhancement: DRC 'profile' accepts 'true' as argument too --- src/drc/drc/built-in-macros/_drc_engine.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 627a64cc2..e2ff2b7ee 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -942,15 +942,18 @@ def capacitor_with_bulk(name, area_cap, cls = nil) # Turns profiling on or off (default). In profiling mode, the # system will collect statistics about rules executed, their execution time # and memory information. The argument specifies how many operations to - # print at the end of the run. Without an argument, all operations are + # print at the end of the run. Without an argument or when passing "true", all operations are # printed. Passing "false" for the argument will disable profiling. This is the # default. def profile(n = 0) - if !n.is_a?(1.class) && n != nil && n != false - raise("Argument to 'profile' must be either an integer number or nil") + if !n.is_a?(1.class) && n != nil && n != false && n != true + raise("Argument to 'profile' must be either an integer number, true, false or nil") end @profile = !!n + if n == true + n = 0 + end @profile_n = [n || 0, 0].max end From 3df88ae279f1a8c2387bb07799e7981d60fc2d06 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 15 Feb 2026 19:17:21 +0100 Subject: [PATCH 04/15] A small, but sometimes effective optimization of DRC check functions: if a feature is entirely covered (with interaction distance) by a box, it is enough to check against the box --- src/db/db/dbRegionLocalOperations.cc | 40 +++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/db/db/dbRegionLocalOperations.cc b/src/db/db/dbRegionLocalOperations.cc index 6d0a20d60..274b1c334 100644 --- a/src/db/db/dbRegionLocalOperations.cc +++ b/src/db/db/dbRegionLocalOperations.cc @@ -232,6 +232,8 @@ check_local_operation_base::compute_results (db::Layout *layout, db::Cel bool take_all = edge_check.has_negative_edge_output () || intruders.empty (); db::Box common_box; + bool subjects_are_fully_covered = false; + if (! take_all) { db::Vector e (edge_check.distance (), edge_check.distance ()); @@ -241,14 +243,24 @@ check_local_operation_base::compute_results (db::Layout *layout, db::Cel subject_box += db::box_convert () (**i); } - if (edge_check.requires_different_layers ()) { - db::Box intruder_box; + common_box = subject_box.enlarged (e); + + if (edge_check.requires_different_layers () && ! common_box.empty ()) { + + subjects_are_fully_covered = false; + + db::Box all_intruders_box; + for (auto i = intruders.begin (); i != intruders.end (); ++i) { - intruder_box += db::box_convert () (**i); + db::Box intruder_box = db::box_convert () (**i); + if (! subjects_are_fully_covered && (*i)->is_box () && common_box.inside (intruder_box)) { + subjects_are_fully_covered = true; + } + all_intruders_box += intruder_box.enlarged (e); } - common_box = subject_box.enlarged (e) & intruder_box.enlarged (e); - } else { - common_box = subject_box.enlarged (e); + + common_box &= all_intruders_box; + } } @@ -306,6 +318,22 @@ check_local_operation_base::compute_results (db::Layout *layout, db::Cel // empty intruders + } else if (subjects_are_fully_covered) { + + // optimization: can use a single box for the intruders + + n = 1; + + db::Point ul = common_box.upper_left (); + db::Point lr = common_box.lower_right (); + + poly_check.enter (db::Edge (common_box.p1 (), ul), n); + poly_check.enter (db::Edge (ul, common_box.p2 ()), n); + poly_check.enter (db::Edge (common_box.p2 (), lr), n); + poly_check.enter (db::Edge (lr, common_box.p1 ()), n); + + n += 2; + } else if (! m_other_is_merged && (intruders.size () > 1 || ! (*intruders.begin ())->is_box ())) { // NOTE: this local merge is not necessarily giving the same results than a global merge before running From eb23f2690e9c9d3f2fdb91c3f30553225f1b2d89 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 15 Feb 2026 23:07:04 +0100 Subject: [PATCH 05/15] WIP --- .../gds2/db_plugin/dbGDS2WriterBase.cc | 124 +++++++++++++----- .../gds2/db_plugin/dbGDS2WriterBase.h | 4 + 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc index 3f37a0606..f8e4e93f3 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc @@ -220,7 +220,11 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, std::vector context_prop_strings; - if (layout.has_context_info ()) { + layout.get_context_info (context_prop_strings); + + // @@@ Add context strings for m_prop_names_map and m_prop_values_map and layout properties if needed + + if (! context_prop_strings.empty ()) { // Use a dummy BOUNDARY element to attach the global context @@ -241,18 +245,12 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, write_int (0); } - context_prop_strings.clear (); - - if (layout.get_context_info (context_prop_strings)) { - - // Hint: write in the reverse order since this way, the reader is more efficient (it knows how many strings - // will arrive) - for (std::vector ::const_iterator s = context_prop_strings.end (); s != context_prop_strings.begin (); ) { - --s; - size_t n = std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s); - write_context_string (n, *s); - } - + // 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 (); ) { + --s; + size_t n = std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s); + write_context_string (n, *s); } write_record_size (4); @@ -262,7 +260,12 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { - if (layout.has_context_info (*cell)) { + context_prop_strings.clear (); + layout.get_context_info (*cell, context_prop_strings); + + // @@@ Add cell properties if needed + + if (! context_prop_strings.empty ()) { write_record_size (4); write_record (sSREF); @@ -276,16 +279,12 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, context_prop_strings.clear (); - if (layout.get_context_info (*cell, context_prop_strings)) { - - // Hint: write in the reverse order since this way, the reader is more efficient (it knows how many strings - // will arrive) - for (std::vector ::const_iterator s = context_prop_strings.end (); s != context_prop_strings.begin (); ) { - --s; - size_t n = std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s); - write_context_string (n, *s); - } - + // 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 (); ) { + --s; + size_t n = std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s); + write_context_string (n, *s); } write_record_size (4); @@ -538,23 +537,32 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S write_double (m_dbu / std::max (1e-9, gds2_options.user_units)); write_double (m_dbu * 1e-6); + // build property translation maps if possible + // Property translation maps non-numeric property keys to numeric ones and + // complex-type values to string ones. The neccessary maps are included in + // the meta data + // layout properties - if (gds2_options.write_file_properties && layout.prop_id () != 0) { - try { - write_properties (layout, layout.prop_id ()); - } catch (tl::Exception &ex) { - throw tl::Exception (ex.msg () + tl::to_string (tr (", writing 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 () // write context info bool has_context = false; if (options.write_context_info ()) { - has_context = layout.has_context_info (); - for (std::vector::const_iterator cell = cells.begin (); cell != cells.end () && !has_context; ++cell) { + @@@ require a context if meta data has to be added + if (! has_context) { + has_context = layout.has_context_info (); + } + for (std::vector::const_iterator cell = cells.begin (); cell != cells.end () && ! has_context; ++cell) { has_context = layout.has_context_info (*cell); } } @@ -1106,10 +1114,14 @@ GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Sha void GDS2WriterBase::write_properties (const db::Layout & /*layout*/, db::properties_id_type prop_id) { - auto props = db::properties (prop_id).to_map (); + auto props = db::properties (prop_id); for (auto p = props.begin (); p != props.end (); ++p) { - const tl::Variant &name = p->first; + 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; long attr = -1; if (name.can_convert_to_long ()) { @@ -1122,7 +1134,7 @@ GDS2WriterBase::write_properties (const db::Layout & /*layout*/, db::properties_ write_record (sPROPATTR); write_short ((int16_t) attr); - write_string_record (sPROPVALUE, p->second.to_string ()); + write_string_record (sPROPVALUE, value.to_string ()); } @@ -1152,5 +1164,45 @@ GDS2WriterBase::write_string_record (short record, const std::string &t) write_string (t); } +void +GDS2WriterBase::collect_property_ids (std::set &property_ids, const db::Layout &layout, const std::vector &cells, const std::vector > &layers) +{ + if (layout.prop_id () != 0) { + property_ids.insert (layout.prop_id ()); + } + + for (auto cell = cells.begin (); cell != cells.end (); ++cell) { + + const db::Cell &cref (layout.cell (*cell)); + + if (cref.prop_id () != 0) { + property_ids.insert (cref.prop_id ()); + } + + 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 ()); + } + } + + for (auto l = layers.begin (); l != layers.end (); ++l) { + 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 ()); + } + shape.finish_array (); + } + } + + } +} + +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 1bc41594c..95dd5d1eb 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h @@ -176,6 +176,8 @@ class DB_PLUGIN_PUBLIC GDS2WriterBase bool m_write_cell_properties; bool m_keep_instances; double m_default_text_size; + std::map m_prop_values_map; + std::map m_prop_names_map; 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); @@ -183,6 +185,8 @@ class DB_PLUGIN_PUBLIC GDS2WriterBase 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); }; } // namespace db From 4cd84898995a6a638f6bdc3bce4ad3fa27d5cac5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 21 Feb 2026 20:44:02 +0100 Subject: [PATCH 06/15] [consider merging] proper cleanup of properties repo - this enables using user classes for names or values. Without this pre-finalization cleanup, the user class 'destroy' method may end up calling a method of an already destroyed class object. --- src/db/db/dbPropertiesRepository.cc | 13 +++++++++++-- src/db/unit_tests/dbPropertiesRepositoryTests.cc | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbPropertiesRepository.cc b/src/db/db/dbPropertiesRepository.cc index f10dd7050..22129dd52 100644 --- a/src/db/db/dbPropertiesRepository.cc +++ b/src/db/db/dbPropertiesRepository.cc @@ -27,6 +27,7 @@ #include "tlString.h" #include "tlAssert.h" #include "tlHash.h" +#include "tlStaticObjects.h" namespace db { @@ -348,13 +349,21 @@ PropertiesSet::hash () const // ---------------------------------------------------------------------------------- // PropertiesRepository implementation -static PropertiesRepository s_instance; +static PropertiesRepository *sp_global_instance = 0; static PropertiesRepository *sp_temp_instance = 0; PropertiesRepository & PropertiesRepository::instance () { - return sp_temp_instance ? *sp_temp_instance : s_instance; + if (sp_temp_instance) { + return *sp_temp_instance; + } else { + if (! sp_global_instance) { + sp_global_instance = new PropertiesRepository (); + tl::StaticObjects::reg (&sp_global_instance); + } + return *sp_global_instance; + } } void diff --git a/src/db/unit_tests/dbPropertiesRepositoryTests.cc b/src/db/unit_tests/dbPropertiesRepositoryTests.cc index 7363be12d..fe8dc27a3 100644 --- a/src/db/unit_tests/dbPropertiesRepositoryTests.cc +++ b/src/db/unit_tests/dbPropertiesRepositoryTests.cc @@ -503,3 +503,19 @@ TEST(SameValueDifferentTypes) EXPECT_EQ (db::property_name (rp.prop_name_id ((int) 5)).to_parsable_string (), "#5"); } } + +TEST(ComplexTypes) +{ + // This is also a smoke test: we intentionally register globally as the finalization code + // is critical: without the right destruction order we destroy the class object before the + // variant and trigger an assertion (pure virtual function called) + + db::PropertiesSet ps; + ps.insert (tl::Variant (17), db::DBox (0, 0, 1.5, 2.5)); + + db::properties_id_type pid = db::properties_id (ps); + + auto const &ps_out = db::properties (pid); + + EXPECT_EQ (ps_out.to_dict_var ().to_string (), "{17=>(0,0;1.5,2.5)}"); +} From 91b4714a917fe0436254ce3f2e42ed483ea9014e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 21 Feb 2026 23:27:33 +0100 Subject: [PATCH 07/15] [consider merging] Bugfix: 'oasis_read_all_options' as always on in buddy tools. --- src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc b/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc index efa503565..90fd665de 100644 --- a/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc +++ b/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc @@ -38,7 +38,7 @@ static void set_oasis_read_all_properties (db::LoadLayoutOptions *options, bool options->get_options ().read_all_properties = f; } -static int get_oasis_read_all_properties (const db::LoadLayoutOptions *options) +static bool get_oasis_read_all_properties (const db::LoadLayoutOptions *options) { return options->get_options ().read_all_properties; } From 1f4126036d77efd844b38d90abb291129eaed3ac Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 21 Feb 2026 23:29:39 +0100 Subject: [PATCH 08/15] Implementation of GDS2 extended features. Currently, the extended features are enabled by default. --- src/buddies/src/bd/bdWriterOptions.cc | 10 + src/buddies/src/bd/bdWriterOptions.h | 1 + src/db/db/dbReader.h | 24 + .../streamers/gds2/db_plugin/dbGDS2Format.h | 17 + .../gds2/db_plugin/dbGDS2ReaderBase.cc | 118 ++++- .../gds2/db_plugin/dbGDS2ReaderBase.h | 6 + .../gds2/db_plugin/dbGDS2WriterBase.cc | 255 +++++++++-- .../gds2/db_plugin/dbGDS2WriterBase.h | 7 +- .../streamers/gds2/db_plugin/gsiDeclDbGDS2.cc | 33 ++ .../gds2/unit_tests/dbGDS2WriterTests.cc | 411 ++++++++++++++++++ .../oasis/db_plugin/dbOASISReader.cc | 24 - 11 files changed, 837 insertions(+), 69 deletions(-) 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 @@ class BD_PUBLIC GenericWriterOptions 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 @@ class DB_PUBLIC ReaderUnknownFormatException 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 @@ class DB_PLUGIN_PUBLIC GDS2WriterOptions 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 @@ class DB_PLUGIN_PUBLIC GDS2WriterOptions */ 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 @@ class DB_PLUGIN_PUBLIC GDS2ReaderBase 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 @@ class DB_PLUGIN_PUBLIC GDS2ReaderBase 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 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 context strings for m_prop_names_map and m_prop_values_map and layout properties if needed + // 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 @@ class DB_PLUGIN_PUBLIC GDS2WriterBase 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 * From a98cf19c780be4fb66edde2b915263435cb7382d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 15:16:56 +0100 Subject: [PATCH 09/15] [consider merging] Adding an option '-of|--format' to strmxor and strmclip to specify the output format instead of taking it from the suffix --- src/buddies/src/bd/bdWriterOptions.cc | 39 +++++++++++++++++++++++++++ src/buddies/src/bd/bdWriterOptions.h | 7 +++++ 2 files changed, 46 insertions(+) diff --git a/src/buddies/src/bd/bdWriterOptions.cc b/src/buddies/src/bd/bdWriterOptions.cc index f4eacb492..4c894d8f1 100644 --- a/src/buddies/src/bd/bdWriterOptions.cc +++ b/src/buddies/src/bd/bdWriterOptions.cc @@ -99,6 +99,20 @@ const std::string GenericWriterOptions::dxf_format_name = "DXF"; const std::string GenericWriterOptions::cif_format_name = "CIF"; const std::string GenericWriterOptions::mag_format_name = "MAG"; +std::vector +GenericWriterOptions::all_format_names () +{ + std::vector names; + names.push_back (gds2_format_name); + names.push_back (gds2text_format_name); + names.push_back (oasis_format_name); + names.push_back (lstream_format_name); + names.push_back (dxf_format_name); + names.push_back (cif_format_name); + names.push_back (mag_format_name); + return names; +} + void GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::string &format) { @@ -110,6 +124,15 @@ GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::strin "given factor." ); + if (format.empty ()) { + cmd << tl::arg (group + + "-of|--format=format", &m_format, "Specifies the output format", + "By default, the output format is derived from the file name suffix. " + "You can also specify the format directly using this option. Allowed format names are: " + + tl::join (all_format_names (), ", ") + ); + } + if (format.empty () || format == gds2_format_name || format == gds2text_format_name || format == oasis_format_name) { cmd << tl::arg (group + "-od|--dbu-out=dbu", &m_dbu, "Uses the specified database unit", @@ -435,6 +458,22 @@ GenericWriterOptions::configure (db::SaveLayoutOptions &save_options, const db:: save_options.set_keep_instances (m_keep_instances); save_options.set_write_context_info (m_write_context_info); + if (! m_format.empty ()) { + + // check, if the format name is a valid one + std::vector af = all_format_names (); + auto i = af.begin (); + while (i != af.end () && *i != m_format) { + ++i; + } + if (i == af.end ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid fornat name %s. Allowed names are: %s")), m_format, tl::join (af, ", "))); + } + + save_options.set_format (m_format); + + } + save_options.set_option_by_name ("gds2_max_vertex_count", m_gds2_max_vertex_count); save_options.set_option_by_name ("gds2_no_zero_length_paths", m_gds2_no_zero_length_paths); save_options.set_option_by_name ("gds2_multi_xy_records", m_gds2_multi_xy_records); diff --git a/src/buddies/src/bd/bdWriterOptions.h b/src/buddies/src/bd/bdWriterOptions.h index 50cdd19bd..25bd6d922 100644 --- a/src/buddies/src/bd/bdWriterOptions.h +++ b/src/buddies/src/bd/bdWriterOptions.h @@ -26,6 +26,7 @@ #include "bdCommon.h" #include +#include namespace tl { @@ -60,6 +61,11 @@ class BD_PUBLIC GenericWriterOptions */ GenericWriterOptions (const db::SaveLayoutOptions &options); + /** + * @brief Gets a list with all format names available + */ + static std::vector all_format_names (); + /** * @brief Adds the generic options to the command line parser object * The format string gives a hint about the target format. Certain options will be @@ -114,6 +120,7 @@ class BD_PUBLIC GenericWriterOptions static const std::string mag_format_name; private: + std::string m_format; double m_scale_factor; double m_dbu; bool m_dont_write_empty_cells; From c2803f1459e777c431850204600795722e97de10 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 16:22:43 +0100 Subject: [PATCH 10/15] Fixing user properties editor for complex properties --- src/layui/layui/layDialogs.cc | 5 ++- src/layui/layui/syntax/ur_text.xml | 71 +++++++++++++----------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/layui/layui/layDialogs.cc b/src/layui/layui/layDialogs.cc index 6b009a9b7..d450773af 100644 --- a/src/layui/layui/layDialogs.cc +++ b/src/layui/layui/layDialogs.cc @@ -1468,8 +1468,9 @@ normalize (const QString &s) tl::Variant v; - if (*c == '#' || *c == '\"' || *c == '\'') { - tl::Extractor ex (c); + tl::Extractor ex (c); + ex.skip (); + if (*ex == '#' || *ex == '\"' || *ex == '\'' || *ex == '(' || *ex == '{' || *ex == '[') { ex.read (v); ex.expect_end (); } else { diff --git a/src/layui/layui/syntax/ur_text.xml b/src/layui/layui/syntax/ur_text.xml index 5858a407f..ecd4e6a4b 100644 --- a/src/layui/layui/syntax/ur_text.xml +++ b/src/layui/layui/syntax/ur_text.xml @@ -7,67 +7,58 @@ - + - - + + + + + + - - + + - + - - - - - - - - - - - - - - - - + + + + - - + + + + + - + + + + + + - + - + - + - - - - - - - + - - - - - + + From 618d94ab3b1b9a58354064235eba76e387f9d304 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 18:12:40 +0100 Subject: [PATCH 11/15] Added a test for large property payload --- .../gds2/unit_tests/dbGDS2WriterTests.cc | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc index 21f1df5bb..89cb449c5 100644 --- a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc +++ b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc @@ -1989,3 +1989,62 @@ TEST(205_extended_props) } } +// Tests the ability to store huge strings, polygons and lists +TEST(206_large_props) +{ + TempPropertiesRepository temp_pr; + + db::GDS2WriterOptions gds2_opt; + db::SaveLayoutOptions options; + options.set_options (gds2_opt); + + tl::Variant large_list = tl::Variant::empty_list (); + std::string large_string; + large_string.reserve (size_t (150000)); + for (int i = 0; i < 30000; ++i) { + large_string += tl::to_string (i); + large_list.push (i); + } + + int npoints = 10000; + double r = 10000000.0; + std::vector points; + points.reserve (size_t (npoints)); + for (int i = 0; i < npoints; ++i) { + double a = M_PI * 2.0 * i / double (npoints); + points.push_back (db::Point (r * sin (a), r * cos (a))); + } + db::SimplePolygon large_polygon; + large_polygon.assign_hull (points.begin (), points.end ()); + + db::PropertiesSet ps1; + ps1.insert (tl::Variant (1), large_string); + ps1.insert (tl::Variant (2), large_list); + ps1.insert (tl::Variant (3), large_polygon); + + auto ps1_id = db::properties_id (ps1); + + db::Layout layout_org; + layout_org.prop_id (ps1_id); + + std::string tmp_file = tl::TestBase::tmp_file ("tmp_GDS2Writer_206.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); + } + + EXPECT_EQ (layout_read.prop_id (), ps1_id); +} + From e654ef3012a1f2f178724e43e3d6dc32749007a3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 19:03:46 +0100 Subject: [PATCH 12/15] Experimental: enhance OASIS reader/writer to preserve KLayout's extended property data types (uses S_GDS_PROPERTIES for numerical/string combinations and standard OASIS properties where possible, uses specially annotated strings to store other types) --- .../oasis/db_plugin/dbOASISReader.cc | 65 +++++++--- .../streamers/oasis/db_plugin/dbOASISReader.h | 8 +- .../oasis/db_plugin/dbOASISWriter.cc | 113 +++++++++++------- .../streamers/oasis/db_plugin/dbOASISWriter.h | 5 +- .../oasis/unit_tests/dbOASISWriterTests.cc | 8 +- 5 files changed, 131 insertions(+), 68 deletions(-) diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc index 1ad137915..3ca4e6ef7 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc @@ -776,13 +776,13 @@ OASISReader::do_read (db::Layout &layout) get (id); } - if (! m_propnames.insert (std::make_pair (id, name)).second) { + if (! m_propnames.insert (std::make_pair (id, make_prop_value (name))).second) { error (tl::sprintf (tl::to_string (tr ("A PROPNAME with id %ld is present already")), id)); } auto fw = m_propname_forward_references.find (id); if (fw != m_propname_forward_references.end ()) { - fw->second = db::property_names_id (name); + fw->second = db::property_names_id (make_prop_value (name)); } reset_modal_variables (); @@ -818,13 +818,13 @@ OASISReader::do_read (db::Layout &layout) get (id); } - if (! m_propstrings.insert (std::make_pair (id, name)).second) { + if (! m_propstrings.insert (std::make_pair (id, make_prop_value (name))).second) { error (tl::sprintf (tl::to_string (tr ("A PROPSTRING with id %ld is present already")), id)); } - std::map::iterator fw = m_propvalue_forward_references.find (id); + auto fw = m_propvalue_forward_references.find (id); if (fw != m_propvalue_forward_references.end ()) { - fw->second = name; + fw->second = db::property_values_id (make_prop_value (name)); } reset_modal_variables (); @@ -1028,6 +1028,13 @@ OASISReader::do_read (db::Layout &layout) } } + // all forward references to property values must be resolved + for (std::map ::const_iterator fw = m_propvalue_forward_references.begin (); fw != m_propvalue_forward_references.end (); ++fw) { + if (fw->second == 0) { + error (tl::sprintf (tl::to_string (tr ("No property string defined for property string id %ld")), fw->first)); + } + } + // Resolve forward references for stored shape and instance prop_ids. // This makes these shape and instance property IDs valid @@ -1216,6 +1223,32 @@ OASISReader::has_forward_refs (const db::PropertiesSet &properties) return false; } +const std::string klayout_prop_string_prefix = "KLAYOUT_VALUE:"; + +tl::Variant +OASISReader::make_prop_value (const std::string &s) +{ + if (strncmp (s.c_str (), klayout_prop_string_prefix.c_str (), klayout_prop_string_prefix.size ()) == 0) { + + tl::Extractor ex (s.c_str () + klayout_prop_string_prefix.size ()); + try { + + tl::Variant v; + ex.read (v); + return v; + + } catch (tl::Exception &) { + + warn (tl::sprintf (tl::to_string (tr ("Unable to decode special value string (%s) - keeping as a string")), s)); + return tl::Variant (s); + + } + + } else { + return tl::Variant (s); + } +} + properties_id_type OASISReader::make_forward_properties_id (const db::PropertiesSet &properties) { // NOTE: the forward properties ID scheme makes use of the fact that IDs @@ -1331,9 +1364,9 @@ OASISReader::replace_forward_references_in_variant (tl::Variant &v) if (v.is_id ()) { uint64_t id = (uint64_t) v.to_id (); - std::map ::const_iterator fw = m_propvalue_forward_references.find (id); + auto fw = m_propvalue_forward_references.find (id); if (fw != m_propvalue_forward_references.end ()) { - v = tl::Variant (fw->second); + v = db::property_value (fw->second); } else { error (tl::sprintf (tl::to_string (tr ("No property value defined for property value id %ld")), id)); } @@ -1355,9 +1388,9 @@ OASISReader::replace_forward_references_in_variant (tl::Variant &v) for (std::vector::iterator ll = new_list.begin (); ll != new_list.end (); ++ll) { if (ll->is_id ()) { uint64_t id = (uint64_t) ll->to_id (); - std::map ::const_iterator fw = m_propvalue_forward_references.find (id); + auto fw = m_propvalue_forward_references.find (id); if (fw != m_propvalue_forward_references.end ()) { - *ll = tl::Variant (fw->second); + *ll = db::property_value (fw->second); } else { error (tl::sprintf (tl::to_string (tr ("No property value defined for property value id %ld")), id)); } @@ -1484,12 +1517,12 @@ OASISReader::read_properties () uint64_t id; get (id); - std::map ::const_iterator cid = m_propnames.find (id); + auto cid = m_propnames.find (id); if (cid == m_propnames.end ()) { mm_last_property_name = db::property_names_id (tl::Variant (id, true /*dummy for id type*/)); m_propname_forward_references.insert (std::make_pair (id, db::property_names_id_type (0))); } else { - mm_last_property_name = db::property_names_id (tl::Variant (cid->second)); + mm_last_property_name = db::property_names_id (cid->second); } } else { @@ -1498,7 +1531,7 @@ OASISReader::read_properties () warn (tl::to_string (tr ("PROPERTY names must be references to PROPNAME ids in strict mode"))); } - mm_last_property_name = db::property_names_id (tl::Variant (get_str ())); + mm_last_property_name = db::property_names_id (make_prop_value (get_str ())); } } @@ -1547,7 +1580,7 @@ OASISReader::read_properties () } if (m_read_properties) { - mm_last_value_list.get_non_const ().push_back (tl::Variant (get_str ())); + mm_last_value_list.get_non_const ().push_back (make_prop_value (get_str ())); } else { get_str (); } @@ -1557,12 +1590,12 @@ OASISReader::read_properties () uint64_t id; get (id); if (m_read_properties) { - std::map ::const_iterator sid = m_propstrings.find (id); + auto sid = m_propstrings.find (id); if (sid == m_propstrings.end ()) { - m_propvalue_forward_references.insert (std::make_pair (id, std::string ())); + m_propvalue_forward_references.insert (std::make_pair (id, db::property_values_id_type (0))); mm_last_value_list.get_non_const ().push_back (tl::Variant (id, true /*dummy for id type*/)); } else { - mm_last_value_list.get_non_const ().push_back (tl::Variant (sid->second)); + mm_last_value_list.get_non_const ().push_back (sid->second); } } diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.h b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.h index 4420e6c45..32b8430e2 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.h +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.h @@ -166,8 +166,8 @@ class DB_PLUGIN_PUBLIC OASISReader std::map m_cellname_properties; std::map m_textstrings; std::map m_text_forward_references; - std::map m_propstrings; - std::map m_propnames; + std::map m_propstrings; + std::map m_propnames; std::map > m_context_strings_per_cell; @@ -179,7 +179,7 @@ class DB_PLUGIN_PUBLIC OASISReader bool m_read_all_properties; std::map m_propname_forward_references; - std::map m_propvalue_forward_references; + std::map m_propvalue_forward_references; std::map > m_forward_properties_for_shapes; std::map > m_forward_properties_for_instances; std::map m_future_cell_properties; @@ -216,6 +216,8 @@ class DB_PLUGIN_PUBLIC OASISReader void replace_forward_references_in_variant (tl::Variant &v); void extract_context_strings (db::PropertiesSet &properties, std::vector &context_strings); bool has_forward_refs (const db::PropertiesSet &properties); + + tl::Variant make_prop_value (const std::string &s); db::properties_id_type make_forward_properties_id (const db::PropertiesSet &properties); const db::PropertiesSet &forward_properties (db::properties_id_type id) const; bool is_forward_properties_id (db::properties_id_type id) const; diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc index e7ce59048..bbdca29ce 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc @@ -967,20 +967,35 @@ OASISWriter::write_ucoord (db::Coord c) } } +const std::string klayout_prop_string_prefix = "KLAYOUT_VALUE:"; + +std::string +OASISWriter::make_prop_string (const tl::Variant &v) +{ + if (v.is_a_string ()) { + return v.to_stdstring (); + } else { + return klayout_prop_string_prefix + v.to_parsable_string (); + } +} + void OASISWriter::emit_propname_def (db::properties_id_type prop_id) { - auto props = db::properties (prop_id).to_map (); + auto props = db::properties (prop_id); for (auto p = props.begin (); p != props.end (); ++p) { - const tl::Variant &name = p->first; - const char *name_str = s_gds_property_name; - if (! make_gds_property (name)) { - name_str = name.to_string (); + const tl::Variant &name = db::property_name (p->first); + const tl::Variant &value = db::property_value (p->second); + + std::string name_str (s_gds_property_name); + if (! value.is_a_string () || ! make_gds_property (name)) { + name_str = make_prop_string (name); } + if (m_propnames.insert (std::make_pair (name_str, m_propname_id)).second) { write_record_id (7); - write_nstring (name_str); + write_nstring (name_str.c_str ()); ++m_propname_id; } @@ -992,38 +1007,50 @@ OASISWriter::emit_propstring_def (db::properties_id_type prop_id) { std::vector pv_list; - auto props = db::properties (prop_id).to_map (); + auto props = db::properties (prop_id); for (auto p = props.begin (); p != props.end (); ++p) { pv_list.clear (); const std::vector *pvl = &pv_list; - const tl::Variant &name = p->first; - if (! make_gds_property (name)) { + const tl::Variant &name = db::property_name (p->first); + const tl::Variant &value = db::property_value (p->second); - if (p->second.is_list ()) { - pvl = &p->second.get_list (); - } else if (!p->second.is_nil ()) { + if (! value.is_a_string () || ! make_gds_property (name)) { + + if (value.is_list ()) { + pvl = &value.get_list (); + } else if (!value.is_nil ()) { pv_list.reserve (1); - pv_list.push_back (p->second); + pv_list.push_back (value); } - } else { + for (std::vector::const_iterator pv = pvl->begin (); pv != pvl->end (); ++pv) { - pv_list.reserve (2); - pv_list.push_back (name.to_ulong ()); - pv_list.push_back (p->second.to_string ()); + if (!pv->is_double () && !pv->is_longlong () && !pv->is_ulonglong () && !pv->is_long () && !pv->is_ulong ()) { - } + std::string v = make_prop_string (*pv); + + if (m_propstrings.insert (std::make_pair (v, m_propstring_id)).second) { + write_record_id (9); + write_bstring (v.c_str ()); + ++m_propstring_id; + } - for (std::vector::const_iterator pv = pvl->begin (); pv != pvl->end (); ++pv) { - if (!pv->is_double () && !pv->is_longlong () && !pv->is_ulonglong () && !pv->is_long () && !pv->is_ulong ()) { - if (m_propstrings.insert (std::make_pair (pv->to_string (), m_propstring_id)).second) { - write_record_id (9); - write_bstring (pv->to_string ()); - ++m_propstring_id; } + + } + + } else { + + std::string v = make_prop_string (value); + + if (m_propstrings.insert (std::make_pair (v, m_propstring_id)).second) { + write_record_id (9); + write_bstring (v.c_str ()); + ++m_propstring_id; } + } } @@ -2117,37 +2144,37 @@ OASISWriter::write_props (db::properties_id_type prop_id) { std::vector pv_list; - auto props = db::properties (prop_id).to_map (); - + auto props = db::properties (prop_id); for (auto p = props.begin (); p != props.end (); ++p) { - m_progress.set (mp_stream->pos ()); + const tl::Variant &name = db::property_name (p->first); + const tl::Variant &value = db::property_value (p->second); - const tl::Variant &name = p->first; + m_progress.set (mp_stream->pos ()); - const char *name_str = s_gds_property_name; + std::string name_str (s_gds_property_name); bool sflag = true; pv_list.clear (); const std::vector *pvl = &pv_list; - if (! make_gds_property (name)) { + if (! value.is_a_string () || ! make_gds_property (name)) { - name_str = name.to_string (); + name_str = make_prop_string (name); sflag = false; - if (p->second.is_list ()) { - pvl = &p->second.get_list (); - } else if (!p->second.is_nil ()) { + if (value.is_list ()) { + pvl = &value.get_list (); + } else if (!value.is_nil ()) { pv_list.reserve (1); - pv_list.push_back (p->second); + pv_list.push_back (value); } } else { pv_list.reserve (2); pv_list.push_back (name.to_ulong ()); - pv_list.push_back (p->second.to_string ()); + pv_list.push_back (value); } @@ -2157,7 +2184,7 @@ OASISWriter::write_props (db::properties_id_type prop_id) } void -OASISWriter::write_property_def (const char *name_str, const tl::Variant &pv, bool sflag) +OASISWriter::write_property_def (const std::string &name_str, const tl::Variant &pv, bool sflag) { std::vector pvl; pvl.reserve (1); @@ -2166,7 +2193,7 @@ OASISWriter::write_property_def (const char *name_str, const tl::Variant &pv, bo } void -OASISWriter::write_property_def (const char *name_str, const std::vector &pvl, bool sflag) +OASISWriter::write_property_def (const std::string &name_str, const std::vector &pvl, bool sflag) { bool same_name = (mm_last_property_name == name_str); bool same_value = (mm_last_value_list == pvl); @@ -2203,7 +2230,7 @@ OASISWriter::write_property_def (const char *name_str, const std::vector::const_iterator pvi = m_propstrings.find (pvs); // In strict mode always write property string ID's: before we have issued the table we can @@ -2264,11 +2291,11 @@ OASISWriter::write_property_def (const char *name_str, const std::vectorsecond); } else { - write_byte (10 + string_type (pvs)); - write_bstring (pvs); + write_byte (10 + string_type (pvs.c_str ())); + write_bstring (pvs.c_str ()); } } diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h index be28a4527..2263c35a8 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h @@ -300,6 +300,7 @@ class DB_PLUGIN_PUBLIC OASISWriter void reset_modal_variables (); + static std::string make_prop_string (const tl::Variant &v); void emit_propname_def (db::properties_id_type prop_id); void emit_propstring_def (db::properties_id_type prop_id); void write_insts (const std::set &cell_set); @@ -307,8 +308,8 @@ class DB_PLUGIN_PUBLIC OASISWriter void write_shapes (const db::LayerProperties &lprops, const db::Shapes &shapes); void write_props (db::properties_id_type prop_id); - void write_property_def (const char *name_str, const std::vector &pvl, bool sflag); - void write_property_def (const char *name_str, const tl::Variant &pv, bool sflag); + void write_property_def (const std::string &name_str, const std::vector &pvl, bool sflag); + void write_property_def (const std::string &name_str, const tl::Variant &pv, bool sflag); void write_pointlist (const std::vector &pointlist, bool for_polygons); void write_inst_with_rep (const db::CellInstArray &inst, db::properties_id_type prop_id, const db::Vector &disp, const db::Repetition &rep); diff --git a/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc b/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc index e748fe08c..000bb2852 100644 --- a/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc +++ b/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc @@ -1533,14 +1533,14 @@ TEST(116) "set props {\n" " {42 {42}}\n" " {{S_BOUNDING_BOX} {(0,0,100,1000,1100)}}\n" - " {{S_CELL_OFFSET} {231}}\n" + " {{S_CELL_OFFSET} {247}}\n" "}\n" "begin_cellp $props {$1}\n" "path 1 0 0 0 0 {0 100} {1000 1200}\n" "end_cell\n" "set props {\n" " {{S_BOUNDING_BOX} {(2,0,0,0,0)}}\n" - " {{S_CELL_OFFSET} {229}}\n" + " {{S_CELL_OFFSET} {245}}\n" "}\n" "begin_cellp $props {$2}\n" "end_cell\n" @@ -1598,13 +1598,13 @@ TEST(116) "begin_libp $props 0.001\n" "set props {\n" " {42 {42}}\n" - " {{S_CELL_OFFSET} {182}}\n" + " {{S_CELL_OFFSET} {198}}\n" "}\n" "begin_cellp $props {$1}\n" "path 1 0 0 0 0 {0 100} {1000 1200}\n" "end_cell\n" "set props {\n" - " {{S_CELL_OFFSET} {180}}\n" + " {{S_CELL_OFFSET} {196}}\n" "}\n" "begin_cellp $props {$2}\n" "end_cell\n" From 6fd689e3c5bda85841800b1d77fa3e329e87f9af Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 23:49:21 +0100 Subject: [PATCH 13/15] [consider merging] Bugfix: OASIS reader was not able to read S_GDS_PROPERTY from file level or under forward reference conditions --- .../oasis/db_plugin/dbOASISReader.cc | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc index 3ca4e6ef7..ea95e9e03 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc @@ -1331,21 +1331,37 @@ OASISReader::resolve_forward_references (db::PropertiesSet &properties) const tl::Variant &name = db::property_name (p->first); if (name.is_id ()) { - std::map ::iterator pf = m_propname_forward_references.find (name.to_id ()); + uint64_t name_id = name.to_id (); + bool is_sprop = (name_id & 1) != 0; + name_id >>= 1; + + std::map ::iterator pf = m_propname_forward_references.find (name_id); if (pf != m_propname_forward_references.end ()) { - if (pf->second == m_s_gds_property_name_id) { + if (pf->second == m_klayout_context_property_name_id) { + + // NOTE: property names ID 0 is reserved for context strings + new_props.insert (property_names_id_type (0), value); + + } else if (! m_read_properties) { + + // ignore other properties + + } else if (pf->second == m_s_gds_property_name_id && is_sprop) { // S_GDS_PROPERTY translation if (value.is_list () && value.get_list ().size () >= 2) { new_props.insert (value.get_list () [0], value.get_list () [1]); } - } else if (pf->second == m_klayout_context_property_name_id) { - // NOTE: property names ID 0 is reserved for context strings - new_props.insert (property_names_id_type (0), value); + } else if (is_sprop && ! m_read_all_properties) { + + // ignore system properties + } else { + new_props.insert (pf->second, value); + } } @@ -1404,10 +1420,27 @@ OASISReader::replace_forward_references_in_variant (tl::Variant &v) } } +static void +store_properties (db::PropertiesSet &properties, db::property_names_id_type name, const db::OASISReader::property_value_list &value) +{ + if (value.size () == 0) { + properties.insert (name, tl::Variant ()); + } else if (value.size () == 1) { + properties.insert (name, tl::Variant (value [0])); + } else if (value.size () > 1) { + properties.insert (name, tl::Variant (value.begin (), value.end ())); + } +} + void OASISReader::store_last_properties (db::PropertiesSet &properties, bool ignore_special, bool with_context_props) { - if (with_context_props && mm_last_property_name.get () == m_klayout_context_property_name_id) { + const tl::Variant &name = db::property_name (mm_last_property_name.get ()); + if (name.is_id ()) { + + store_properties (properties, mm_last_property_name.get (), mm_last_value_list.get ()); + + } else if (with_context_props && mm_last_property_name.get () == m_klayout_context_property_name_id) { // Context properties are stored with a special property name ID of 0 @@ -1431,12 +1464,10 @@ OASISReader::store_last_properties (db::PropertiesSet &properties, bool ignore_s // This is mode is used for cells and layouts so the standard properties do not appear as user properties. // For shapes we need to keep the special ones since they may be forward-references S_GDS_PROPERTY names. - } else if (mm_last_value_list.get ().size () == 0) { - properties.insert (mm_last_property_name.get (), tl::Variant ()); - } else if (mm_last_value_list.get ().size () == 1) { - properties.insert (mm_last_property_name.get (), tl::Variant (mm_last_value_list.get () [0])); - } else if (mm_last_value_list.get ().size () > 1) { - properties.insert (mm_last_property_name.get (), tl::Variant (mm_last_value_list.get ().begin (), mm_last_value_list.get ().end ())); + } else { + + store_properties (properties, mm_last_property_name.get (), mm_last_value_list.get ()); + } } @@ -1519,7 +1550,7 @@ OASISReader::read_properties () auto cid = m_propnames.find (id); if (cid == m_propnames.end ()) { - mm_last_property_name = db::property_names_id (tl::Variant (id, true /*dummy for id type*/)); + mm_last_property_name = db::property_names_id (tl::Variant ((id << 1) + uint64_t (is_sprop ? 1 : 0), true /*dummy for id type*/)); m_propname_forward_references.insert (std::make_pair (id, db::property_names_id_type (0))); } else { mm_last_property_name = db::property_names_id (cid->second); From 96cf0b640d1ef369f4118017dfce3ccee076ad34 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 23:50:32 +0100 Subject: [PATCH 14/15] More tests for enhanced features mode in GDS2 --- src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc index 89cb449c5..17f6c5c52 100644 --- a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc +++ b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc @@ -1927,10 +1927,10 @@ TEST(205_extended_props) { TempPropertiesRepository temp_pr; - db::GDS2WriterOptions gds2_opt; - gds2_opt.extended_features = false; db::SaveLayoutOptions options; - options.set_options (gds2_opt); + EXPECT_EQ (options.get_option_by_name ("extended_features").to_bool (), true); + options.set_option_by_name ("extended_features", false); + EXPECT_EQ (options.get_option_by_name ("extended_features").to_bool (), false); db::PropertiesSet ps1; ps1.insert (tl::Variant ("prop_name"), db::DBox (0, 0, 1.5, 2.5)); From d24697a5e3ec46e4bad753578e9967296e850ad2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 22 Feb 2026 23:51:43 +0100 Subject: [PATCH 15/15] Adding enhanced property types to OASIS. The writer options have a new flag that turns non-standard types of properties such as objects or nested lists into annotated strings. --- .../streamers/oasis/db_plugin/dbOASISFormat.h | 17 +++- .../oasis/db_plugin/dbOASISWriter.cc | 55 +++++++---- .../streamers/oasis/db_plugin/dbOASISWriter.h | 4 +- .../oasis/db_plugin/gsiDeclDbOASIS.cc | 25 +++++ .../oasis/unit_tests/dbOASISWriterTests.cc | 96 +++++++++++++++++++ 5 files changed, 175 insertions(+), 22 deletions(-) diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISFormat.h b/src/plugins/streamers/oasis/db_plugin/dbOASISFormat.h index 1ddc69a8f..30269bf13 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISFormat.h +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISFormat.h @@ -101,7 +101,7 @@ class DB_PLUGIN_PUBLIC OASISWriterOptions * @brief The constructor */ OASISWriterOptions () - : compression_level (2), write_cblocks (true), strict_mode (true), recompress (false), permissive (false), + : compression_level (2), enhanced_property_types (true), write_cblocks (true), strict_mode (true), recompress (false), permissive (false), write_std_properties (1), subst_char ("*"), tables_at_end (false) { // .. nothing yet .. @@ -116,7 +116,20 @@ class DB_PLUGIN_PUBLIC OASISWriterOptions * 1 - nearest neighbor shape array formation * 2++ - enhanced shape array search algorithm using 2nd and further neighbor distances as well */ - int compression_level; + int compression_level; + + /** + * @brief Enhanced property types + * + * If this option is set to true (the default), complex property types + * such as lists or even objects can be embedded into OASIS files. + * For this, KLayout uses strings with a special annotation + * (i.e. "KLAYOUT_VALUE:..."). + * + * This option also implies that only properties with string values + * (and numerical keys) are written as S_GDS_PROPERTY properties. + */ + bool enhanced_property_types; /** * @brief CBLOCK compression diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc index bbdca29ce..bc4034fcb 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc @@ -78,20 +78,6 @@ struct vector_cmp_y } }; -/** - * @brief Determines whether a property shall be produced as S_GDS_PROPERTY - */ -static bool -make_gds_property (const tl::Variant &name) -{ - // We write S_GDS_PROPERTY properties, because that is the only way to write properties - // with numerical keys - return (name.is_longlong () && name.to_longlong () < 0x8000 && name.to_longlong () >= 0) || - (name.is_ulonglong () && name.to_ulonglong () < 0x8000) || - (name.is_long () && name.to_long () < 0x8000 && name.to_long () >= 0) || - (name.is_ulong () && name.to_ulong () < 0x8000); -} - // --------------------------------------------------------------------------------- /** @@ -970,13 +956,44 @@ OASISWriter::write_ucoord (db::Coord c) const std::string klayout_prop_string_prefix = "KLAYOUT_VALUE:"; std::string -OASISWriter::make_prop_string (const tl::Variant &v) +OASISWriter::make_prop_string (const tl::Variant &v) const { - if (v.is_a_string ()) { + if (! m_options.enhanced_property_types) { + return v.to_stdstring (); + + } else if (v.is_a_string ()) { + + std::string s = v.to_stdstring (); + + // if the string starts with the prefix, encode it using the prefixed notation + if (strncmp (s.c_str (), klayout_prop_string_prefix.c_str (), klayout_prop_string_prefix.size ()) == 0) { + return klayout_prop_string_prefix + v.to_parsable_string (); + } else { + return s; + } + } else { + return klayout_prop_string_prefix + v.to_parsable_string (); + + } +} + +bool +OASISWriter::make_gds_property (const tl::Variant &name, const tl::Variant &value) const +{ + // Only strings will become GDS properties in enhanced properties mode + if (m_options.enhanced_property_types && !value.is_a_string ()) { + return false; } + + // We write S_GDS_PROPERTY properties, because that is the only way to write properties + // with numerical keys + return (name.is_longlong () && name.to_longlong () < 0x8000 && name.to_longlong () >= 0) || + (name.is_ulonglong () && name.to_ulonglong () < 0x8000) || + (name.is_long () && name.to_long () < 0x8000 && name.to_long () >= 0) || + (name.is_ulong () && name.to_ulong () < 0x8000); } void @@ -989,7 +1006,7 @@ OASISWriter::emit_propname_def (db::properties_id_type prop_id) const tl::Variant &value = db::property_value (p->second); std::string name_str (s_gds_property_name); - if (! value.is_a_string () || ! make_gds_property (name)) { + if (! make_gds_property (name, value)) { name_str = make_prop_string (name); } @@ -1016,7 +1033,7 @@ OASISWriter::emit_propstring_def (db::properties_id_type prop_id) const tl::Variant &name = db::property_name (p->first); const tl::Variant &value = db::property_value (p->second); - if (! value.is_a_string () || ! make_gds_property (name)) { + if (! make_gds_property (name, value)) { if (value.is_list ()) { pvl = &value.get_list (); @@ -2158,7 +2175,7 @@ OASISWriter::write_props (db::properties_id_type prop_id) pv_list.clear (); const std::vector *pvl = &pv_list; - if (! value.is_a_string () || ! make_gds_property (name)) { + if (! make_gds_property (name, value)) { name_str = make_prop_string (name); sflag = false; diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h index 2263c35a8..ba605d876 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.h @@ -300,7 +300,9 @@ class DB_PLUGIN_PUBLIC OASISWriter void reset_modal_variables (); - static std::string make_prop_string (const tl::Variant &v); + std::string make_prop_string (const tl::Variant &v) const; + bool make_gds_property (const tl::Variant &name, const tl::Variant &value) const; + void emit_propname_def (db::properties_id_type prop_id); void emit_propstring_def (db::properties_id_type prop_id); void write_insts (const std::set &cell_set); diff --git a/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc b/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc index 90fd665de..41d4892b5 100644 --- a/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc +++ b/src/plugins/streamers/oasis/db_plugin/gsiDeclDbOASIS.cc @@ -133,6 +133,16 @@ static int get_oasis_write_std_properties_ext (const db::SaveLayoutOptions *opti return options->get_options ().write_std_properties; } +static bool get_oasis_enhanced_properties (const db::SaveLayoutOptions *options) +{ + return options->get_options ().enhanced_property_types; +} + +static void set_oasis_enhanced_properties (db::SaveLayoutOptions *options, bool f) +{ + options->get_options ().enhanced_property_types = f; +} + static void set_oasis_write_cell_bounding_boxes (db::SaveLayoutOptions *options, bool f) { db::OASISWriterOptions &oasis_options = options->get_options (); @@ -278,6 +288,21 @@ gsi::ClassExt oasis_writer_options ( // this method is mainly provided as access point for the generic interface "@hide" ) + + gsi::method_ext ("oasis_enhanced_properties=", &set_oasis_enhanced_properties, gsi::arg ("flag"), + "@brief Sets a value indicating whether to write enhanced property values\n" + "With this option set to true (the default), non-standard types like lists or objects are supported " + "for property names and values. These types are encoded in specially annotated strings.\n" + "KLayout's OASIS reader will convert them back to the original types.\n" + "With this option set to false, such values are translated into strings.\n" + "\n" + "This attribute has been introduced in version 0.30.7." + ) + + gsi::method_ext ("oasis_enhanced_properties?", &get_oasis_enhanced_properties, + "@brief Gets a value indicating whether to write enhanced property values\n" + "See \\oasis_enhanced_properties= for details.\n" + "\n" + "This attribute has been introduced in version 0.30.7." + ) + gsi::method_ext ("oasis_compression_level=", &set_oasis_compression, gsi::arg ("level"), "@brief Set the OASIS compression level\n" "The OASIS compression level is an integer number between 0 and 10. 0 basically is no compression, " diff --git a/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc b/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc index 000bb2852..9349f5909 100644 --- a/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc +++ b/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc @@ -33,6 +33,37 @@ #include +namespace { + +static std::string p2s (db::properties_id_type pid) +{ + return db::properties (pid).to_dict_var ().to_parsable_string (); +} + +/** + * @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; +}; + +} + void run_test (tl::TestBase *_this, const char *file, bool scaling_test, int compr, bool recompress, bool tables_at_end) { { @@ -2100,3 +2131,68 @@ TEST(140) db::compare_layouts (_this, gg, tl::testdata () + "/oasis/dbOASISWriter40_au.gds", db::NoNormalization); } } + +// Writing enhanced properties to OASIS +TEST(150) +{ + TempPropertiesRepository temp_pr; + + db::Layout layout_org; + + db::PropertiesSet ps; + tl::Variant list = tl::Variant::empty_list (); + list.push (tl::Variant (-1)); + list.push (tl::Variant (2.5)); + list.push (tl::Variant ("KLAYOUT_VALUE:h*ll*")); // a string that clashes with the annotation scheme + ps.insert (tl::Variant (17), list); + ps.insert (tl::Variant ("x"), tl::Variant (db::DBox (0, 0, 1.5, 2.5))); + + db::properties_id_type ps_id = db::properties_id (ps); + EXPECT_EQ (p2s (ps_id), "{#17=>(#-1,##2.5,'KLAYOUT_VALUE:h*ll*'),'x'=>[dbox:(0,0;1.5,2.5)]}"); + + layout_org.prop_id (ps_id); + + std::string tmp_file = tl::TestBase::tmp_file (tl::sprintf ("tmp_dbOASISWriter150a.oas")); + + { + tl::OutputStream out (tmp_file); + db::SaveLayoutOptions options; + options.set_format ("OASIS"); + EXPECT_EQ (options.get_option_by_name ("oasis_enhanced_properties").to_bool (), true); + db::Writer writer (options); + writer.write (layout_org, out); + } + + { + tl::InputStream in (tmp_file); + db::Reader reader (in); + db::Layout gg; + reader.set_warnings_as_errors (true); + reader.read (gg); + + EXPECT_EQ (p2s (gg.prop_id ()), "{#17=>(#-1,##2.5,'KLAYOUT_VALUE:h*ll*'),'x'=>[dbox:(0,0;1.5,2.5)]}"); + + } + + tmp_file = tl::TestBase::tmp_file (tl::sprintf ("tmp_dbOASISWriter150b.oas")); + + { + tl::OutputStream out (tmp_file); + db::SaveLayoutOptions options; + options.set_format ("OASIS"); + options.set_option_by_name ("oasis_enhanced_properties", false); + EXPECT_EQ (options.get_option_by_name ("oasis_enhanced_properties").to_bool (), false); + db::Writer writer (options); + writer.write (layout_org, out); + } + + { + tl::InputStream in (tmp_file); + db::Reader reader (in); + db::Layout gg; + reader.read (gg); + + EXPECT_EQ (p2s (gg.prop_id ()), "{#17=>'(-1,2.5,KLAYOUT_VALUE:h*ll*)','x'=>'(0,0;1.5,2.5)'}"); + + } +}