From 3cf6ef2cec342887ecf3263bca1d3a7ea2fdf5c4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Jul 2024 23:28:19 +0200 Subject: [PATCH 01/29] WIP --- src/edt/edt/edtInstPropertiesPage.cc | 42 ++++++------ src/edt/edt/edtInstPropertiesPage.h | 2 +- src/edt/edt/edtMainService.cc | 9 +-- src/edt/edt/edtPropertiesPages.cc | 26 ++++---- src/edt/edt/edtPropertiesPages.h | 2 +- src/edt/edt/edtService.cc | 90 +++++++++++++++----------- src/edt/edt/edtService.h | 97 ++++++++++++++++------------ src/lay/lay/layFillDialog.cc | 2 +- 8 files changed, 149 insertions(+), 121 deletions(-) diff --git a/src/edt/edt/edtInstPropertiesPage.cc b/src/edt/edt/edtInstPropertiesPage.cc index d84474411..3baeb5a4b 100644 --- a/src/edt/edt/edtInstPropertiesPage.cc +++ b/src/edt/edt/edtInstPropertiesPage.cc @@ -47,7 +47,7 @@ namespace edt // ------------------------------------------------------------------------- -static std::string cell_name_from_sel (const edt::Service::obj_iterator &pos, edt::Service *service) +static std::string cell_name_from_sel (EditableSelectionIterator::pointer pos, edt::Service *service) { if (! pos->is_cell_inst ()) { return std::string (); @@ -81,7 +81,7 @@ struct SelectionPtrSort // .. nothing yet .. } - bool operator() (const edt::Service::obj_iterator &a, const edt::Service::obj_iterator &b) + bool operator() (EditableSelectionIterator::pointer a, EditableSelectionIterator::pointer b) { return cell_name_from_sel (a, mp_service) < cell_name_from_sel (b, mp_service); } @@ -104,10 +104,9 @@ static bool is_orthogonal (const db::DVector &rv, const db::DVector &cv) InstPropertiesPage::InstPropertiesPage (edt::Service *service, db::Manager *manager, QWidget *parent) : lay::PropertiesPage (parent, manager, service), mp_service (service), m_enable_cb_callback (true), mp_pcell_parameters (0) { - const edt::Service::objects &selection = service->selection (); - m_selection_ptrs.reserve (selection.size ()); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { - m_selection_ptrs.push_back (s); + m_selection_ptrs.reserve (service->selection_size ()); + for (EditableSelectionIterator s = service->begin_selection (); ! s.at_end (); ++s) { + m_selection_ptrs.push_back (s.operator-> ()); } std::sort (m_selection_ptrs.begin (), m_selection_ptrs.end (), SelectionPtrSort (service)); @@ -221,7 +220,7 @@ BEGIN_PROTECTED lib = lib_cbx->current_library (); layout = &lib->layout (); } else { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); layout = &cv->layout (); } @@ -300,7 +299,7 @@ InstPropertiesPage::select_entries (const std::vector &entries) std::string InstPropertiesPage::description (size_t entry) const { - edt::Service::obj_iterator pos = m_selection_ptrs [entry]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [entry]; std::string d = cell_name_from_sel (pos, mp_service); if (! pos->is_cell_inst ()) { @@ -343,12 +342,12 @@ InstPropertiesPage::update () return; } - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; tl_assert (pos->is_cell_inst ()); std::set highlights; for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) { - highlights.insert (m_selection_ptrs [*i].operator-> ()); + highlights.insert (m_selection_ptrs [*i]); } mp_service->highlight (highlights); @@ -472,7 +471,7 @@ InstPropertiesPage::show_cell () return; } - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; lay::CellView::unspecific_cell_path_type path (mp_service->view ()->cellview (pos->cv_index ()).combined_unspecific_path ()); for (lay::ObjectInstPath::iterator p = pos->begin (); p != pos->end (); ++p) { @@ -509,7 +508,7 @@ InstPropertiesPage::create_applicator (db::Cell & /*cell*/, const db::Instance & std::unique_ptr appl (new CombinedChangeApplicator ()); - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); bool du = dbu_cb->isChecked (); @@ -786,16 +785,15 @@ InstPropertiesPage::create_applicator (db::Cell & /*cell*/, const db::Instance & void InstPropertiesPage::recompute_selection_ptrs (const std::vector &new_sel) { - std::map ptrs; + std::map ptrs; - const edt::Service::objects &selection = mp_service->selection (); - for (edt::Service::obj_iterator pos = selection.begin (); pos != selection.end (); ++pos) { - ptrs.insert (std::make_pair (*pos, pos)); + for (EditableSelectionIterator pos = mp_service->begin_selection (); ! pos.at_end (); ++pos) { + ptrs.insert (std::make_pair (*pos, pos.operator -> ())); } m_selection_ptrs.clear (); for (std::vector::const_iterator s = new_sel.begin (); s != new_sel.end (); ++s) { - std::map::const_iterator pm = ptrs.find (*s); + std::map::const_iterator pm = ptrs.find (*s); tl_assert (pm != ptrs.end ()); m_selection_ptrs.push_back (pm->second); } @@ -814,7 +812,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) std::unique_ptr applicator; { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; tl_assert (pos->is_cell_inst ()); const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); @@ -846,7 +844,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) std::vector new_sel; new_sel.reserve (m_selection_ptrs.size ()); - for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { + for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { new_sel.push_back (**p); } @@ -859,7 +857,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative) for (auto ii = m_indexes.begin (); ii != m_indexes.end (); ++ii) { size_t index = *ii; - edt::Service::obj_iterator pos = m_selection_ptrs [*ii]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [*ii]; // only update objects from the same layout - this is not practical limitation but saves a lot of effort for // managing different property id's etc. @@ -958,7 +956,7 @@ InstPropertiesPage::update_pcell_parameters () } else { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); layout = &cv->layout (); @@ -982,7 +980,7 @@ InstPropertiesPage::update_pcell_parameters () std::vector parameters; - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); db::Cell &cell = cv->layout ().cell (pos->cell_index ()); std::pair pci = cell.is_pcell_instance (pos->back ().inst_ptr); diff --git a/src/edt/edt/edtInstPropertiesPage.h b/src/edt/edt/edtInstPropertiesPage.h index 713dff328..e9b2f9c81 100644 --- a/src/edt/edt/edtInstPropertiesPage.h +++ b/src/edt/edt/edtInstPropertiesPage.h @@ -58,7 +58,7 @@ private: void recompute_selection_ptrs (const std::vector &new_sel); protected: - std::vector m_selection_ptrs; + std::vector m_selection_ptrs; std::vector m_indexes; edt::Service *mp_service; bool m_enable_cb_callback; diff --git a/src/edt/edt/edtMainService.cc b/src/edt/edt/edtMainService.cc index 2477a7788..461b3c046 100644 --- a/src/edt/edt/edtMainService.cc +++ b/src/edt/edt/edtMainService.cc @@ -310,8 +310,7 @@ MainService::cm_descend () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end () && common_inst.valid (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::objects::const_iterator sel = selection.begin (); sel != selection.end () && common_inst.valid (); ++sel) { + for (EditableSelectionIterator sel = (*es)->begin_selection (); ! sel.at_end () && common_inst.valid (); ++sel) { common_inst.add (*sel, 1); } } @@ -335,12 +334,10 @@ MainService::cm_descend () for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - new_selections.push_back (std::vector ()); - new_selections.back ().reserve (selection.size ()); + new_selections.back ().reserve ((*es)->selection_size ()); - for (edt::Service::objects::const_iterator sel = selection.begin (); sel != selection.end (); ++sel) { + for (EditableSelectionIterator sel = (*es)->begin_selection (); ! sel.at_end (); ++sel) { new_selections.back ().push_back (*sel); lay::ObjectInstPath &new_sel = new_selections.back ().back (); diff --git a/src/edt/edt/edtPropertiesPages.cc b/src/edt/edt/edtPropertiesPages.cc index ac31333c7..1e4bf2114 100644 --- a/src/edt/edt/edtPropertiesPages.cc +++ b/src/edt/edt/edtPropertiesPages.cc @@ -47,10 +47,9 @@ ShapePropertiesPage::ShapePropertiesPage (const std::string &description, edt::S : lay::PropertiesPage (parent, manager, service), m_description (description), mp_service (service), m_enable_cb_callback (true) { - const edt::Service::objects &selection = service->selection (); - m_selection_ptrs.reserve (selection.size ()); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { - m_selection_ptrs.push_back (s); + m_selection_ptrs.reserve (service->selection_size ()); + for (edt::EditableSelectionIterator s = service->begin_selection (); ! s.at_end (); ++s) { + m_selection_ptrs.push_back (s.operator-> ()); } m_prop_id = 0; mp_service->clear_highlights (); @@ -191,7 +190,7 @@ ShapePropertiesPage::update () { std::set highlights; for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) { - highlights.insert (m_selection_ptrs [*i].operator-> ()); + highlights.insert (m_selection_ptrs [*i]); } mp_service->highlight (highlights); @@ -201,16 +200,15 @@ ShapePropertiesPage::update () void ShapePropertiesPage::recompute_selection_ptrs (const std::vector &new_sel) { - std::map ptrs; + std::map ptrs; - const edt::Service::objects &selection = mp_service->selection (); - for (edt::Service::obj_iterator pos = selection.begin (); pos != selection.end (); ++pos) { - ptrs.insert (std::make_pair (*pos, pos)); + for (EditableSelectionIterator pos = mp_service->begin_selection (); ! pos.at_end (); ++pos) { + ptrs.insert (std::make_pair (*pos, pos.operator-> ())); } m_selection_ptrs.clear (); for (std::vector::const_iterator s = new_sel.begin (); s != new_sel.end (); ++s) { - std::map::const_iterator pm = ptrs.find (*s); + std::map::const_iterator pm = ptrs.find (*s); tl_assert (pm != ptrs.end ()); m_selection_ptrs.push_back (pm->second); } @@ -228,7 +226,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) unsigned int cv_index = m_selection_ptrs [m_indexes.front ()]->cv_index (); { - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; tl_assert (! pos->is_cell_inst ()); const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); @@ -261,7 +259,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) std::vector new_sel; new_sel.reserve (m_selection_ptrs.size ()); - for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { + for (std::vector::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) { new_sel.push_back (**p); } @@ -274,7 +272,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative) for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) { size_t index = *i; - edt::Service::obj_iterator pos = m_selection_ptrs [*i]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [*i]; // only update objects from the same layout - this is not practical limitation but saves a lot of effort for // managing different property id's etc. @@ -376,7 +374,7 @@ ShapePropertiesPage::update_shape () return; } - edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()]; + EditableSelectionIterator::pointer pos = m_selection_ptrs [m_indexes.front ()]; const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ()); double dbu = cv->layout ().dbu (); diff --git a/src/edt/edt/edtPropertiesPages.h b/src/edt/edt/edtPropertiesPages.h index ae3ffbd2e..21a22aaaf 100644 --- a/src/edt/edt/edtPropertiesPages.h +++ b/src/edt/edt/edtPropertiesPages.h @@ -71,7 +71,7 @@ private: protected: std::string m_description; - std::vector m_selection_ptrs; + std::vector m_selection_ptrs; std::vector m_indexes; edt::Service *mp_service; bool m_enable_cb_callback; diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index d0f766888..66b4e2ad5 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -459,8 +459,7 @@ Service::copy_selected () unsigned int inst_mode = 0; if (m_hier_copy_mode < 0) { - const objects &sel = selection (); - for (objects::const_iterator r = sel.begin (); r != sel.end () && ! need_to_ask_for_copy_mode; ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end () && ! need_to_ask_for_copy_mode; ++r) { if (r->is_cell_inst ()) { const db::Cell &cell = view ()->cellview (r->cv_index ())->layout ().cell (r->back ().inst_ptr.cell_index ()); if (! cell.is_proxy ()) { @@ -500,12 +499,10 @@ Service::copy_selected () void Service::copy_selected (unsigned int inst_mode) { - const objects &sel = selection (); - // create one ClipboardData object per cv_index because, this one assumes that there is // only one source layout object. std::set cv_indices; - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { cv_indices.insert (r->cv_index ()); } @@ -515,7 +512,7 @@ Service::copy_selected (unsigned int inst_mode) // add the selected objects to the clipboard data objects. const lay::CellView &cv = view ()->cellview (*cvi); - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { if (r->cv_index () == *cvi) { if (! r->is_cell_inst ()) { cd->get ().add (cv->layout (), r->layer (), r->shape (), cv.context_trans () * r->trans ()); @@ -618,8 +615,7 @@ Service::selection_bbox () db::DBox box; - const objects &sel = selection (); - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); const db::Layout &layout = cv->layout (); @@ -699,13 +695,10 @@ Service::transform (const db::DCplxTrans &trans, const std::vector obj_ptrs; - obj_ptrs.reserve (sel.size ()); - for (objects::iterator r = sel.begin (); r != sel.end (); ++r) { - obj_ptrs.push_back (r); + std::vector obj_ptrs; + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { + obj_ptrs.push_back (r.operator-> ()); } // build the transformation variants cache @@ -717,7 +710,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector >, std::vector > shapes_by_cell; n = 0; - for (objects::iterator r = sel.begin (); r != sel.end (); ++r, ++n) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r, ++n) { if (! r->is_cell_inst ()) { shapes_by_cell.insert (std::make_pair (std::make_pair (r->cell_index (), std::make_pair (r->cv_index (), r->layer ())), std::vector ())).first->second.push_back (n); } @@ -740,7 +733,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator si = sbc->second.begin (); si != sbc->second.end (); ++si) { - objects::iterator s = obj_ptrs [*si]; + EditableSelectionIterator::pointer s = obj_ptrs [*si]; // mt = transformation in DBU units db::ICplxTrans mt; @@ -764,14 +757,14 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator si = sbc->second.begin (); si != sbc->second.end (); ++si) { - objects::iterator &s = obj_ptrs [*si]; + EditableSelectionIterator::pointer &s = obj_ptrs [*si]; lay::ObjectInstPath new_path (*s); new_path.set_shape (new_shapes.find (s->shape ())->second); // modify the selection - m_selection.erase (s); - s = m_selection.insert (new_path).first; + m_selection.erase (*s); + s = m_selection.insert (new_path).first.operator-> (); } @@ -787,7 +780,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector, std::vector > insts_by_cell; n = 0; - for (objects::iterator r = sel.begin (); r != sel.end (); ++r, ++n) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r, ++n) { if (r->is_cell_inst ()) { insts_by_cell.insert (std::make_pair (std::make_pair (r->cell_index (), r->cv_index ()), std::vector ())).first->second.push_back (n); } @@ -810,7 +803,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator ii = ibc->second.begin (); ii != ibc->second.end (); ++ii) { - objects::iterator i = obj_ptrs [*ii]; + EditableSelectionIterator::pointer i = obj_ptrs [*ii]; // mt = transformation in DBU units db::ICplxTrans mt; @@ -836,14 +829,14 @@ Service::transform (const db::DCplxTrans &trans, const std::vector::iterator ii = ibc->second.begin (); ii != ibc->second.end (); ++ii) { - objects::iterator &i = obj_ptrs [*ii]; + EditableSelectionIterator::pointer &i = obj_ptrs [*ii]; lay::ObjectInstPath new_path (*i); new_path.back ().inst_ptr = new_insts.find (i->back ().inst_ptr)->second; // modify the selection - m_selection.erase (i); - i = m_selection.insert (new_path).first; + m_selection.erase (*i); + i = m_selection.insert (new_path).first.operator-> (); } @@ -1039,8 +1032,7 @@ Service::del_selected () std::set needs_cleanup; // delete all shapes and instances. - const objects &sel = selection (); - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); if (cv.is_valid ()) { db::Cell &cell = cv->layout ().cell (r->cell_index ()); @@ -1326,11 +1318,12 @@ static std::string path_to_string (const db::Layout &layout, const lay::ObjectIn void Service::display_status (bool transient) { - const objects *sel = transient ? &transient_selection () : &selection (); + EditableSelectionIterator r = transient ? begin_transient_selection () : begin_selection (); + EditableSelectionIterator rr = r; + ++rr; - if (sel->size () == 1) { + if (rr.at_end ()) { - objects::const_iterator r = sel->begin (); const db::Layout &layout = view ()->cellview (r->cv_index ())->layout (); if (m_cell_inst_service) { @@ -1606,6 +1599,18 @@ Service::transient_selection () const return m_transient_selection; } +EditableSelectionIterator +Service::begin_selection () const +{ + return EditableSelectionIterator (this, false); +} + +EditableSelectionIterator +Service::begin_transient_selection () const +{ + return EditableSelectionIterator (this, true); +} + bool Service::select (const lay::ObjectInstPath &obj, lay::Editable::SelectionMode mode) { @@ -1730,17 +1735,15 @@ Service::selection_to_view () void Service::do_selection_to_view () { - const objects &sel = selection (); - // Hint: this is a lower bound: - m_markers.reserve (sel.size ()); + m_markers.reserve (selection_size ()); // build the transformation variants cache TransformationVariants tv (view ()); // Build markers - for (objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); @@ -1939,12 +1942,14 @@ Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const bool Service::handle_guiding_shape_changes () { + EditableSelectionIterator s = begin_selection (); + // just allow one guiding shape to be selected - if (selection ().empty ()) { + if (s.at_end ()) { return false; } - std::pair gs = handle_guiding_shape_changes (*selection ().begin ()); + std::pair gs = handle_guiding_shape_changes (*s); if (gs.first) { // remove superfluous proxies @@ -1966,7 +1971,20 @@ Service::handle_guiding_shape_changes () // Implementation of EditableSelectionIterator EditableSelectionIterator::EditableSelectionIterator (const std::vector &services, bool transient) - : m_services (services), m_service (0), m_transient_selection (transient) + : m_services (services.begin (), services.end ()), m_service (0), m_transient_selection (transient) +{ + init (); +} + +EditableSelectionIterator::EditableSelectionIterator (const edt::Service *service, bool transient) + : m_services (), m_service (0), m_transient_selection (transient) +{ + m_services.push_back (service); + init (); +} + +void +EditableSelectionIterator::init () { if (! m_services.empty ()) { if (m_transient_selection) { diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index dc0250f81..3b34ffe5a 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -71,6 +71,41 @@ std::map pcell_parameters_from_string (const std::stri // ------------------------------------------------------------- +/** + * @brief A utility class to implement a selection iterator across all editor services + */ +class EditableSelectionIterator +{ +public: + typedef std::set objects; + typedef objects::value_type value_type; + typedef objects::const_iterator iterator_type; + typedef const value_type *pointer; + typedef const value_type &reference; + typedef std::forward_iterator_tag iterator_category; + typedef void difference_type; + + EditableSelectionIterator (const std::vector &services, bool transient); + EditableSelectionIterator (const edt::Service *service, bool transient); + + bool at_end () const; + + EditableSelectionIterator &operator++ (); + reference operator* () const; + pointer operator-> () const; + +private: + std::vector m_services; + unsigned int m_service; + bool m_transient_selection; + iterator_type m_iter, m_end; + + void next (); + void init (); +}; + +// ------------------------------------------------------------- + class EDT_PUBLIC Service : public lay::EditorServiceBase, public db::Object @@ -226,16 +261,6 @@ public: return m_color; } - /** - * @brief Get the selection container - */ - const objects &selection () const; - - /** - * @brief Get the transient selection container - */ - const objects &transient_selection () const; - /** * @brief Access to the view object */ @@ -257,11 +282,21 @@ public: */ void clear_previous_selection (); + /** + * @brief Gets the selection iterator + */ + EditableSelectionIterator begin_selection () const; + /** * @brief Establish a transient selection */ bool transient_select (const db::DPoint &pos); + /** + * @brief Gets the transient selection iterator + */ + EditableSelectionIterator begin_transient_selection () const; + /** * @brief Turns the transient selection to the selection */ @@ -585,6 +620,8 @@ protected: lay::PointSnapToObjectResult snap2_details (const db::DPoint &p) const; private: + friend class EditableSelectionIterator; + // The layout view that the editor service is attached to lay::LayoutViewBase *mp_view; @@ -685,6 +722,16 @@ private: */ void apply_highlights (); + /** + * @brief Get the selection container + */ + const objects &selection () const; + + /** + * @brief Get the transient selection container + */ + const objects &transient_selection () const; + private: void copy_selected (unsigned int inst_mode); void update_vector_snapped_point (const db::DPoint &pt, db::DVector &vr, bool &result_set) const; @@ -692,36 +739,6 @@ private: void update_vector_snapped_marker (const lay::InstanceMarker *sm, const db::DTrans &trans, db::DVector &vr, bool &result_set, size_t &count) const; }; -/** - * @brief A utility class to implement a selection iterator across all editor services - */ -class EditableSelectionIterator -{ -public: - typedef edt::Service::objects::value_type value_type; - typedef edt::Service::objects::const_iterator iterator_type; - typedef const value_type *pointer; - typedef const value_type &reference; - typedef std::forward_iterator_tag iterator_category; - typedef void difference_type; - - EditableSelectionIterator (const std::vector &services, bool transient); - - bool at_end () const; - - EditableSelectionIterator &operator++ (); - reference operator* () const; - pointer operator-> () const; - -private: - std::vector m_services; - unsigned int m_service; - bool m_transient_selection; - iterator_type m_iter, m_end; - - void next (); -}; - /** * @brief Gets the combined selections over all editor services in the layout view */ diff --git a/src/lay/lay/layFillDialog.cc b/src/lay/lay/layFillDialog.cc index e37bf4cce..6c6d267b8 100644 --- a/src/lay/lay/layFillDialog.cc +++ b/src/lay/lay/layFillDialog.cc @@ -408,7 +408,7 @@ FillDialog::get_fill_parameters () // selection std::vector edt_services = mp_view->get_plugins (); for (std::vector::const_iterator s = edt_services.begin (); s != edt_services.end (); ++s) { - for (edt::Service::objects::const_iterator sel = (*s)->selection ().begin (); sel != (*s)->selection ().end (); ++sel) { + for (edt::EditableSelectionIterator sel = (*s)->begin_selection (); ! sel.at_end (); ++sel) { if (! sel->is_cell_inst () && (sel->shape ().is_polygon () || sel->shape ().is_path () || sel->shape ().is_box ())) { db::Polygon poly; sel->shape ().polygon (poly); From 12d09687cd40dfbb8066caa5115e2a2b3880105a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Jul 2024 23:51:36 +0200 Subject: [PATCH 02/29] WIP: post-fix for #1780 - minimizing access to 'selection' which is mutable. --- src/edt/edt/edtMainService.cc | 112 +++++++++++++++------------------- 1 file changed, 49 insertions(+), 63 deletions(-) diff --git a/src/edt/edt/edtMainService.cc b/src/edt/edt/edtMainService.cc index 461b3c046..f08334d7c 100644 --- a/src/edt/edt/edtMainService.cc +++ b/src/edt/edt/edtMainService.cc @@ -375,7 +375,14 @@ MainService::cm_ascend () new_selections.reserve (edt_services.size ()); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { new_selections.push_back (std::vector ()); - new_selections.back ().insert (new_selections.back ().end (), (*es)->selection ().begin (), (*es)->selection ().end ()); + size_t n = 0; + for (EditableSelectionIterator i = (*es)->begin_selection (); ! i.at_end (); ++i) { + ++n; + } + new_selections.back ().reserve (n); + for (EditableSelectionIterator i = (*es)->begin_selection (); ! i.at_end (); ++i) { + new_selections.back ().push_back (*i); + } } // this will clear the selection: @@ -436,8 +443,7 @@ MainService::cm_flatten_insts () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &sel = (*es)->selection (); - for (edt::Service::objects::const_iterator r = sel.begin (); r != sel.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); if (cv.is_valid ()) { @@ -491,12 +497,10 @@ MainService::cm_move_hier_up () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - std::vector new_selection; - new_selection.reserve (selection.size ()); + new_selection.reserve ((*es)->selection_size ()); - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { const lay::CellView &cv = view ()->cellview (r->cv_index ()); if (cv.is_valid ()) { @@ -728,8 +732,7 @@ MainService::cm_make_cell_variants () // TODO: this limitation is not really necessary, but makes the code somewhat simpler int cv_index = -1; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (cv_index < 0) { cv_index = r->cv_index (); } else if (cv_index != int (r->cv_index ())) { @@ -752,8 +755,15 @@ MainService::cm_make_cell_variants () } std::vector new_selection; + size_t n = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - new_selection.insert (new_selection.end (), (*es)->selection ().begin (), (*es)->selection ().end ()); + n += (*es)->selection_size (); + } + new_selection.reserve (n); + for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { + new_selection.push_back (*s); + } } size_t num_sel = new_selection.size (); @@ -913,7 +923,7 @@ MainService::cm_make_cell_variants () // Install the new selection size_t i0 = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - size_t n = (*es)->selection ().size (); + size_t n = (*es)->selection_size (); if (n + i0 <= new_selection.size ()) { (*es)->set_selection (new_selection.begin () + i0, new_selection.begin () + i0 + n); } @@ -941,8 +951,7 @@ MainService::cm_resolve_arefs () int cv_index = -1; - const edt::Service::objects &selection = inst_service->selection (); - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = inst_service->begin_selection (); ! r.at_end (); ++r) { if (r->is_cell_inst () && r->back ().inst_ptr.size () > 1) { if (cv_index < 0) { cv_index = r->cv_index (); @@ -1023,8 +1032,7 @@ MainService::cm_make_cell () int cv_index = -1; std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (cv_index < 0) { cv_index = r->cv_index (); } else if (cv_index != int (r->cv_index ())) { @@ -1048,8 +1056,7 @@ MainService::cm_make_cell () db::Box selection_bbox; db::box_convert bc (cv->layout ()); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (r->is_cell_inst ()) { selection_bbox += db::ICplxTrans (r->trans ()) * r->back ().bbox (bc); } else { @@ -1083,8 +1090,7 @@ MainService::cm_make_cell () for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::objects::const_iterator r = selection.begin (); r != selection.end (); ++r) { + for (EditableSelectionIterator r = (*es)->begin_selection (); ! r.at_end (); ++r) { if (r->is_cell_inst ()) { @@ -1145,8 +1151,7 @@ MainService::cm_convert_to_cell () // Do the conversion for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection () ; - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); db::cell_index_type ci = s->cell_index_tot (); @@ -1216,9 +1221,8 @@ MainService::cm_convert_to_pcell () // check whether the selection contains instances and reject it in that case size_t num_selected = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - num_selected += selection.size (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + num_selected += (*es)->selection_size (); + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->is_cell_inst ()) { throw tl::Exception (tl::to_string (tr ("Selection contains instances - they cannot be converted to PCells."))); } @@ -1240,8 +1244,7 @@ MainService::cm_convert_to_pcell () const db::PCellDeclaration *pc_decl = lib->layout ().pcell_declaration (pc->second); size_t n = 1000; // 1000 tries max. for (std::vector::const_iterator es = edt_services.begin (); n > 0 && pc_decl && es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); n > 0 && pc_decl && s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); n > 0 && pc_decl && ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); if (pc_decl->can_create_from_shape (cv->layout (), s->shape (), s->layer ())) { --n; @@ -1306,7 +1309,7 @@ MainService::cm_convert_to_pcell () manager ()->transaction (tl::to_string (tr ("Convert to PCell"))); } - std::vector to_delete; + std::vector to_delete; std::vector new_selection; bool any_non_converted = false; @@ -1318,8 +1321,7 @@ MainService::cm_convert_to_pcell () // convert the shapes which can be converted for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); @@ -1343,7 +1345,7 @@ MainService::cm_convert_to_pcell () new_selection.back ().add_path (db::InstElement (cell_inst)); // mark the shape for delete (later) - to_delete.push_back (s); + to_delete.push_back (s.operator-> ()); any_converted = true; @@ -1366,7 +1368,7 @@ MainService::cm_convert_to_pcell () } // Delete the shapes which have been converted - for (std::vector::const_iterator td = to_delete.begin (); td != to_delete.end (); ++td) { + for (std::vector::const_iterator td = to_delete.begin (); td != to_delete.end (); ++td) { db::Cell &cell = view ()->cellview ((*td)->cv_index ())->layout ().cell ((*td)->cell_index ()); if (cell.shapes ((*td)->layer ()).is_valid ((*td)->shape ())) { cell.shapes ((*td)->layer ()).erase_shape ((*td)->shape ()); @@ -1418,8 +1420,7 @@ void MainService::cm_area_perimeter () // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->is_cell_inst ()) { continue; @@ -1525,8 +1526,7 @@ MainService::cm_round_corners () // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1600,8 +1600,7 @@ MainService::cm_round_corners () // Delete the current selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { db::Cell &cell = view ()->cellview (s->cv_index ())->layout ().cell (s->cell_index ()); if (cell.shapes (s->layer ()).is_valid (s->shape ())) { @@ -1661,8 +1660,7 @@ MainService::cm_size () // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1732,8 +1730,7 @@ MainService::cm_size () // Delete the current selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { db::Cell &cell = view ()->cellview (s->cv_index ())->layout ().cell (s->cell_index ()); if (cell.shapes (s->layer ()).is_valid (s->shape ())) { @@ -1789,8 +1786,7 @@ MainService::boolean_op (int mode) // get (common) cellview index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->seq () == 0 && ! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1824,8 +1820,7 @@ MainService::boolean_op (int mode) // get the secondary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->seq () > 0 && ! s->is_cell_inst () && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { @@ -1862,8 +1857,7 @@ MainService::boolean_op (int mode) // Let's see whether this heuristics is more accepted. for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst () && int (s->layer ()) == layer_index && (s->shape ().is_polygon () || s->shape ().is_path () || s->shape ().is_box ())) { db::Cell &cell = view ()->cellview (s->cv_index ())->layout ().cell (s->cell_index ()); if (cell.shapes (s->layer ()).is_valid (s->shape ())) { @@ -1998,8 +1992,7 @@ MainService::cm_align () // get (common) bbox index of the primary selection for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (s->seq () == 0) { @@ -2029,13 +2022,11 @@ MainService::cm_align () // do the alignment for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - // create a transformation vector that describes each shape's transformation std::vector tv; - tv.reserve (selection.size ()); + tv.reserve ((*es)->selection_size ()); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { db::DVector v; @@ -2102,8 +2093,7 @@ MainService::cm_distribute () // count the items size_t n = 0; for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { ++n; } } @@ -2125,8 +2115,7 @@ MainService::cm_distribute () objects_for_service.push_back (std::make_pair (i, i)); - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const db::Layout &layout = view ()->cellview (s->cv_index ())->layout (); db::CplxTrans tr = db::CplxTrans (layout.dbu ()) * s->trans (); @@ -2208,8 +2197,7 @@ MainService::cm_make_array () std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { ++n; } } @@ -2243,8 +2231,7 @@ MainService::cm_make_array () for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { const lay::CellView &cv = view ()->cellview (s->cv_index ()); if (! cv.is_valid ()) { @@ -2541,8 +2528,7 @@ MainService::check_no_guiding_shapes () { std::vector edt_services = view ()->get_plugins (); for (std::vector::const_iterator es = edt_services.begin (); es != edt_services.end (); ++es) { - const edt::Service::objects &selection = (*es)->selection (); - for (edt::Service::obj_iterator s = selection.begin (); s != selection.end (); ++s) { + for (EditableSelectionIterator s = (*es)->begin_selection (); ! s.at_end (); ++s) { if (! s->is_cell_inst ()) { if (s->layer () == view ()->cellview (s->cv_index ())->layout ().guiding_shape_layer ()) { throw tl::Exception (tl::to_string (tr ("This function cannot be applied to PCell guiding shapes"))); From acbbd92194aecba58ace7249182cb6a290f84842 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 13 Jul 2024 10:33:00 +0200 Subject: [PATCH 03/29] Fixes a potential crash during breakpoint handling This fixes the following problem: * Set a breakpoint in a PCell code * Change the PCell, breakpoint gets triggered * While debugger is in breakpoint, close the view with the PCell To prevent any kind of interference, events are now disabled during breakpoint mode. As this interferes with the help dialog, you cannot open the help dialog either. --- src/lay/lay/layApplication.cc | 31 ++++++++++++----------- src/lay/lay/layMacroEditorDialog.cc | 38 ++++++++++++++++++++++------- src/lay/lay/layMacroEditorDialog.h | 8 ++++++ 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index a087bab37..b38114915 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -94,16 +94,23 @@ namespace lay // -------------------------------------------------------------------------------- // Exception handlers +static void close_transaction () +{ + // if any transaction is pending (this may happen when an operation threw an exception) + // close transactions. + // NOTE: don't do this in breakpoint mode as we do not want to interfere with things happening outside + if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting () && + !(lay::MacroEditorDialog::instance () && lay::MacroEditorDialog::instance ()->in_breakpoint ())) { + lay::MainWindow::instance ()->manager ().commit (); + } +} + static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent) { // Prevents severe side effects if there are pending deferred methods tl::NoDeferredMethods silent; - // if any transaction is pending (this may happen when an operation threw an exception) - // close transactions. - if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { - lay::MainWindow::instance ()->manager ().commit (); - } + close_transaction (); const tl::ExitException *gsi_exit = dynamic_cast (&ex); const tl::BreakException *gsi_break = dynamic_cast (&ex); @@ -155,13 +162,9 @@ static void ui_exception_handler_std (const std::exception &ex, QWidget *parent) // Prevents severe side effects if there are pending deferred methods tl::NoDeferredMethods silent; - // if any transaction is pending (this may happen when an operation threw an exception) - // close transactions. - if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { - lay::MainWindow::instance ()->manager ().commit (); - } + close_transaction (); - tl::error << ex.what (); + tl::error << ex.what (); if (! parent) { parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance (); } @@ -173,11 +176,7 @@ static void ui_exception_handler_def (QWidget *parent) // Prevents severe side effects if there are pending deferred methods tl::NoDeferredMethods silent; - // if any transaction is pending (this may happen when an operation threw an exception) - // close transactions. - if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { - lay::MainWindow::instance ()->manager ().commit (); - } + close_transaction (); if (! parent) { parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance (); diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 08df2a280..380b50b3d 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -1511,26 +1511,25 @@ MacroEditorDialog::eventFilter (QObject *obj, QEvent *event) if (lay::BusySection::is_busy ()) { -#if 0 - if (dynamic_cast (event) != 0 || dynamic_cast (event) != 0) { + if (m_in_breakpoint && (dynamic_cast (event) != 0 || dynamic_cast (event) != 0)) { - // In breakpoint or execution mode and while processing the events inside the debugger, + // In breakpoint mode and while processing the events inside the debugger, // ignore all input or paint events targeted to widgets which are not children of this or the assistant dialog. // Ignoring the paint event is required because otherwise a repaint action would be triggered on a layout which // is potentially unstable or inconsistent. // We nevertheless allow events send to a HelpDialog or ProgressWidget since those are vital for the application's // functionality and are known not to cause any interference. QObject *rec = obj; - while (rec && (rec != this && !dynamic_cast (rec) && !dynamic_cast (rec))) { + while (rec && rec != this) { rec = rec->parent (); } if (! rec) { // TODO: reschedule the paint events (?) + event->accept (); return true; } } -#endif } else { @@ -2267,19 +2266,35 @@ END_PROTECTED } void -MacroEditorDialog::help_requested(const QString &s) +MacroEditorDialog::help_requested (const QString &s) { +BEGIN_PROTECTED + // Do not allow modal popups in breakpoint mode - this would interfere with + // event filtering during breakpoint execution + if (m_in_breakpoint) { + throw tl::Exception (tl::to_string (tr ("The help function is not available in breakpoint mode."))); + } + lay::MainWindow::instance ()->show_assistant_topic (tl::to_string (s)); +END_PROTECTED } void -MacroEditorDialog::help_button_clicked() +MacroEditorDialog::help_button_clicked () { +BEGIN_PROTECTED + // Do not allow modal popups in breakpoint mode - this would interfere with + // event filtering during breakpoint execution + if (m_in_breakpoint) { + throw tl::Exception (tl::to_string (tr ("The help function is not available in breakpoint mode."))); + } + lay::MainWindow::instance ()->show_assistant_url ("int:/code/index.xml"); +END_PROTECTED } void -MacroEditorDialog::add_button_clicked() +MacroEditorDialog::add_button_clicked () { BEGIN_PROTECTED new_macro (); @@ -2287,7 +2302,7 @@ END_PROTECTED } lym::Macro * -MacroEditorDialog::new_macro() +MacroEditorDialog::new_macro () { ensure_writeable_collection_selected (); @@ -3451,6 +3466,11 @@ MacroEditorDialog::leave_breakpoint_mode () mp_current_interpreter = 0; do_update_ui_to_run_mode (); set_exec_point (0, -1, -1); + + // refresh UI that might have been spoiled because we filter events + if (lay::MainWindow::instance ()) { + lay::MainWindow::instance ()->update (); + } } void diff --git a/src/lay/lay/layMacroEditorDialog.h b/src/lay/lay/layMacroEditorDialog.h index 001829924..85befea08 100644 --- a/src/lay/lay/layMacroEditorDialog.h +++ b/src/lay/lay/layMacroEditorDialog.h @@ -166,6 +166,14 @@ public: return m_in_exec; } + /** + * @brief Returns true while the macro IDE is in breakpoint mode + */ + bool in_breakpoint () const + { + return m_in_breakpoint; + } + /** * @brief Selects the current category in the tree view */ From 7146db4762fbde4eeed680f4642aa826cce01dbf Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 13 Jul 2024 15:30:07 +0200 Subject: [PATCH 04/29] Checking some pointers for null (maybe responsible for crashes during PCell development) --- src/db/db/dbLayout.cc | 15 +++++++++------ src/lay/lay/layMacroEditorDialog.cc | 5 +++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index bcd76d706..e4e097c36 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -2872,15 +2872,18 @@ Layout::get_context_info (cell_index_type cell_index, LayoutOrCellContextInfo &i if (pcell_variant) { const db::PCellDeclaration *pcell_decl = ly->pcell_declaration (pcell_variant->pcell_id ()); - - const std::vector &pcp = pcell_decl->parameter_declarations (); - std::vector::const_iterator pd = pcp.begin (); - for (std::vector::const_iterator p = pcell_variant->parameters ().begin (); p != pcell_variant->parameters ().end () && pd != pcp.end (); ++p, ++pd) { - info.pcell_parameters.insert (std::make_pair (pd->get_name (), *p)); + if (pcell_decl) { + const std::vector &pcp = pcell_decl->parameter_declarations (); + std::vector::const_iterator pd = pcp.begin (); + for (std::vector::const_iterator p = pcell_variant->parameters ().begin (); p != pcell_variant->parameters ().end () && pd != pcp.end (); ++p, ++pd) { + info.pcell_parameters.insert (std::make_pair (pd->get_name (), *p)); + } } const db::PCellHeader *header = ly->pcell_header (pcell_variant->pcell_id ()); - info.pcell_name = header->get_name (); + if (header) { + info.pcell_name = header->get_name (); + } } else if (ly != this) { info.cell_name = ly->cell_name (cptr->cell_index ()); diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 380b50b3d..bad1a9db2 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -3468,8 +3468,9 @@ MacroEditorDialog::leave_breakpoint_mode () set_exec_point (0, -1, -1); // refresh UI that might have been spoiled because we filter events - if (lay::MainWindow::instance ()) { - lay::MainWindow::instance ()->update (); + auto tl_widgets = QApplication::topLevelWidgets (); + for (auto w = tl_widgets.begin (); w != tl_widgets.end (); ++w) { + (*w)->update (); } } From a89e295349c5f9ae422957155784684f50a85544 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 17 Jul 2024 00:27:06 +0200 Subject: [PATCH 05/29] WIP: Shapes#break_polygons, Layout#break_polygons, Region#break_polygons (as alias) --- src/db/db/dbLayoutUtils.cc | 83 +++++++++++++++++++++++++++ src/db/db/dbLayoutUtils.h | 27 +++++++++ src/db/db/dbRecursiveShapeIterator.cc | 1 + src/db/db/gsiDeclDbLayout.cc | 38 ++++++++++++ src/db/db/gsiDeclDbRegion.cc | 5 +- src/db/db/gsiDeclDbShapes.cc | 24 ++++++++ 6 files changed, 176 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 2fe5174fb..a5d7b8518 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -683,5 +683,88 @@ scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db } } + +// ------------------------------------------------------------ +// break_polygons implementation + +void +break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_ratio) +{ + std::vector new_polygons; + std::vector to_delete; + + for (auto s = shapes.begin (db::ShapeIterator::Polygons | db::ShapeIterator::Paths); ! s.at_end (); ++s) { + + std::vector polygons; + polygons.push_back (db::Polygon ()); + s->instantiate (polygons.back ()); + + bool first = true; + while (! polygons.empty ()) { + + std::vector split_polygons; + + for (auto p = polygons.begin (); p != polygons.end (); ++p) { + if ((max_vertex_count > 0 && p->vertices () > max_vertex_count) || + (max_area_ratio > 0 && p->area_ratio () > max_area_ratio)) { + if (first) { + to_delete.push_back (*s); + } + db::split_polygon (*p, split_polygons); + } else if (! first) { + new_polygons.push_back (db::Polygon ()); + new_polygons.back ().swap (*p); + } + } + + first = false; + polygons.swap (split_polygons); + + } + + } + + shapes.erase_shapes (to_delete); + + for (auto p = new_polygons.begin (); p != new_polygons.end (); ++p) { + shapes.insert (*p); + } +} + +void +break_polygons (db::Layout &layout, db::cell_index_type cell_index, unsigned int layer, size_t max_vertex_count, double max_area_ratio) +{ + if (layout.is_valid_cell_index (cell_index) && layout.is_valid_layer (layer)) { + db::Cell &cell = layout.cell (cell_index); + break_polygons (cell.shapes (layer), max_vertex_count, max_area_ratio); + } +} + +void +break_polygons (db::Layout &layout, unsigned int layer, size_t max_vertex_count, double max_area_ratio) +{ + for (db::cell_index_type ci = 0; ci < layout.cells (); ++ci) { + if (layout.is_valid_cell_index (ci)) { + db::Cell &cell = layout.cell (ci); + break_polygons (cell.shapes (layer), max_vertex_count, max_area_ratio); + } + } +} + +void +break_polygons (db::Layout &layout, size_t max_vertex_count, double max_area_ratio) +{ + for (db::cell_index_type ci = 0; ci < layout.cells (); ++ci) { + if (layout.is_valid_cell_index (ci)) { + db::Cell &cell = layout.cell (ci); + for (unsigned int li = 0; li < layout.layers (); ++li) { + if (layout.is_valid_layer (li)) { + break_polygons (cell.shapes (li), max_vertex_count, max_area_ratio); + } + } + } + } +} + } diff --git a/src/db/db/dbLayoutUtils.h b/src/db/db/dbLayoutUtils.h index c4d06c239..d3a15c207 100644 --- a/src/db/db/dbLayoutUtils.h +++ b/src/db/db/dbLayoutUtils.h @@ -249,6 +249,33 @@ private: */ DB_PUBLIC void scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db::Coord d); +/** + * @brief Breaks polygons according to max_vertex_count and max_area_ratio + * + * This method will investigate all polygons on the given layer and cell and split them in case they + * have more than the specified vertices and an bounding-box area to polygon area ratio larget + * than the specified max_area_ratio. This serves optimization for algorithms needing a good + * bounding box approximation. + * + * Setting max_vertex_count or max_area_ratio to 0 disables the respective check. + */ +DB_PUBLIC void break_polygons (db::Layout &layout, db::cell_index_type cell_index, unsigned int layer, size_t max_vertex_count, double max_area_ratio); + +/** + * @brief Like "break_polygons" before, but applies it to all cells. + */ +DB_PUBLIC void break_polygons (db::Layout &layout, unsigned int layer, size_t max_vertex_count, double max_area_ratio); + +/** + * @brief Like "break_polygons" before, but applies it to all cells and all layers. + */ +DB_PUBLIC void break_polygons (db::Layout &layout, size_t max_vertex_count, double max_area_ratio); + +/** + * @brief Like "break_polygons" before, but applies it to the given Shapes container. + */ +DB_PUBLIC void break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_ratio); + } // namespace db #endif diff --git a/src/db/db/dbRecursiveShapeIterator.cc b/src/db/db/dbRecursiveShapeIterator.cc index cc02f75a9..5c628def1 100644 --- a/src/db/db/dbRecursiveShapeIterator.cc +++ b/src/db/db/dbRecursiveShapeIterator.cc @@ -32,6 +32,7 @@ namespace db // Recursive shape iterator implementation RecursiveShapeIterator::RecursiveShapeIterator (const RecursiveShapeIterator &d) + : gsi::ObjectBase (d) { operator= (d); } diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index db8c78233..8b547d1b6 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -41,6 +41,7 @@ #include "dbLayerMapping.h" #include "dbCellMapping.h" #include "dbTechnology.h" +#include "dbLayoutUtils.h" #include "tlStream.h" #include "tlGlobPattern.h" @@ -1050,6 +1051,16 @@ static void set_properties (db::Layout *layout, unsigned int index, const db::La } } +void break_polygons2 (db::Layout *layout, unsigned int layer, size_t max_vertex_count, double max_area_ratio) +{ + db::break_polygons (*layout, layer, max_vertex_count, max_area_ratio); +} + +void break_polygons1 (db::Layout *layout, size_t max_vertex_count, double max_area_ratio) +{ + db::break_polygons (*layout, max_vertex_count, max_area_ratio); +} + Class decl_Layout ("db", "Layout", gsi::constructor ("new", &layout_ctor_with_manager, gsi::arg ("manager"), "@brief Creates a layout object attached to a manager\n" @@ -1956,6 +1967,33 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.26.1.\n" ) + + gsi::method_ext ("break_polygons", &break_polygons1, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + "@brief Breaks the polygons of the layout into smaller ones\n" + "\n" + "There are two criteria for splitting a polygon: a polygon is split into parts with less then " + "'max_vertex_count' points and an bounding box-to-polygon area ratio less than 'max_area_ratio'. " + "The area ratio is supposed to render polygons whose bounding box is a better approximation. " + "This applies for example to 'L' shape polygons.\n" + "\n" + "Using a value of 0 for either limit means that the respective limit isn't checked. " + "Breaking happens by cutting the polygons into parts at 'good' locations. The " + "algorithm does not have a specific goal to minimize the number of parts for example. " + "The only goal is to achieve parts within the given limits.\n" + "\n" + "Breaking also applies to paths if their polygon representation satisfies the breaking criterion. " + "In that case, paths are converted to polygons and broken into smaller parts.\n" + "\n" + "This variant applies breaking to all cells and layers.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + + gsi::method_ext ("break_polygons", &break_polygons2, gsi::arg ("layer"), gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + "@brief Breaks the polygons of the layer into smaller ones\n" + "\n" + "This variant applies breaking to all cells and the given layer.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method ("transform", (void (db::Layout::*) (const db::Trans &t)) &db::Layout::transform, gsi::arg ("trans"), "@brief Transforms the layout with the given transformation\n" "\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 8e98a9656..059dd4d67 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -2895,7 +2895,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "This method returns all polygons in self which are not rectilinear." "Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n" ) + - method_ext ("break", &break_polygons, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + method_ext ("break_polygons|#break", &break_polygons, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), "@brief Breaks the polygons of the region into smaller ones\n" "\n" "There are two criteria for splitting a polygon: a polygon is split into parts with less then " @@ -2908,7 +2908,8 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "algorithm does not have a specific goal to minimize the number of parts for example. " "The only goal is to achieve parts within the given limits.\n" "\n" - "This method has been introduced in version 0.26." + "This method has been introduced in version 0.26. The 'break_polygons' alias has been introduced " + "in version 0.29.5 to avoid issues with reserved keywords." ) + method_ext ("delaunay", &delaunay, "@brief Computes a constrained Delaunay triangulation from the given region\n" diff --git a/src/db/db/gsiDeclDbShapes.cc b/src/db/db/gsiDeclDbShapes.cc index f2334f33b..7393f3a89 100644 --- a/src/db/db/gsiDeclDbShapes.cc +++ b/src/db/db/gsiDeclDbShapes.cc @@ -30,6 +30,7 @@ #include "dbRegion.h" #include "dbEdgePairs.h" #include "dbEdges.h" +#include "dbLayoutUtils.h" namespace gsi { @@ -441,6 +442,11 @@ static db::Layout *layout (db::Shapes *sh) } } +static void break_polygons (db::Shapes *sh, size_t max_vertex_count, double max_area_ratio) +{ + db::break_polygons (*sh, max_vertex_count, max_area_ratio); +} + static unsigned int s_all () { return db::ShapeIterator::All; } static unsigned int s_all_with_properties () { return db::ShapeIterator::AllWithProperties; } static unsigned int s_properties () { return db::ShapeIterator::Properties; } @@ -791,6 +797,24 @@ Class decl_Shapes ("db", "Shapes", "\n" "This method has been introduced in version 0.25.\n" ) + + gsi::method_ext ("break_polygons", &break_polygons, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + "@brief Breaks the polygons of the shape container into smaller ones\n" + "\n" + "There are two criteria for splitting a polygon: a polygon is split into parts with less then " + "'max_vertex_count' points and an bounding box-to-polygon area ratio less than 'max_area_ratio'. " + "The area ratio is supposed to render polygons whose bounding box is a better approximation. " + "This applies for example to 'L' shape polygons.\n" + "\n" + "Using a value of 0 for either limit means that the respective limit isn't checked. " + "Breaking happens by cutting the polygons into parts at 'good' locations. The " + "algorithm does not have a specific goal to minimize the number of parts for example. " + "The only goal is to achieve parts within the given limits.\n" + "\n" + "Breaking also applies to paths if their polygon representation satisfies the breaking criterion. " + "In that case, paths are converted to polygons and broken into smaller parts.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method_ext ("replace", &replace, gsi::arg ("shape"), gsi::arg ("box"), "@brief Replaces the given shape with a box\n" "@return A reference to the new shape (a \\Shape object)\n" From b6cc636b05f77145bf8f3db2fe89b01b2f491d13 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 17 Jul 2024 00:51:01 +0200 Subject: [PATCH 06/29] Debugging Layout#break_polygons etc. --- src/db/db/dbLayoutUtils.cc | 56 +++++++++++++++++++----------------- src/db/db/gsiDeclDbLayout.cc | 4 +-- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index a5d7b8518..eaa67e6dc 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -687,6 +687,31 @@ scale_and_snap (db::Layout &layout, db::Cell &cell, db::Coord g, db::Coord m, db // ------------------------------------------------------------ // break_polygons implementation +static bool split_polygon (bool first, db::Polygon &poly, size_t max_vertex_count, double max_area_ratio, std::vector &parts) +{ + if ((max_vertex_count > 0 && poly.vertices () > max_vertex_count) || + (max_area_ratio > 0 && poly.area_ratio () > max_area_ratio)) { + + std::vector sp; + db::split_polygon (poly, sp); + for (auto p = sp.begin (); p != sp.end (); ++p) { + split_polygon (false, *p, max_vertex_count, max_area_ratio, parts); + } + + return true; + + } else { + + if (! first) { + parts.push_back (db::Polygon ()); + parts.back ().swap (poly); + } + + return false; + + } +} + void break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_ratio) { @@ -694,34 +719,11 @@ break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_rat std::vector to_delete; for (auto s = shapes.begin (db::ShapeIterator::Polygons | db::ShapeIterator::Paths); ! s.at_end (); ++s) { - - std::vector polygons; - polygons.push_back (db::Polygon ()); - s->instantiate (polygons.back ()); - - bool first = true; - while (! polygons.empty ()) { - - std::vector split_polygons; - - for (auto p = polygons.begin (); p != polygons.end (); ++p) { - if ((max_vertex_count > 0 && p->vertices () > max_vertex_count) || - (max_area_ratio > 0 && p->area_ratio () > max_area_ratio)) { - if (first) { - to_delete.push_back (*s); - } - db::split_polygon (*p, split_polygons); - } else if (! first) { - new_polygons.push_back (db::Polygon ()); - new_polygons.back ().swap (*p); - } - } - - first = false; - polygons.swap (split_polygons); - + db::Polygon poly; + s->instantiate (poly); + if (split_polygon (true, poly, max_vertex_count, max_area_ratio, new_polygons)) { + to_delete.push_back (*s); } - } shapes.erase_shapes (to_delete); diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index 8b547d1b6..9549fea59 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -1967,7 +1967,7 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.26.1.\n" ) + - gsi::method_ext ("break_polygons", &break_polygons1, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + gsi::method_ext ("break_polygons", &break_polygons1, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio"), "@brief Breaks the polygons of the layout into smaller ones\n" "\n" "There are two criteria for splitting a polygon: a polygon is split into parts with less then " @@ -1987,7 +1987,7 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.29.5." ) + - gsi::method_ext ("break_polygons", &break_polygons2, gsi::arg ("layer"), gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio", 0.0), + gsi::method_ext ("break_polygons", &break_polygons2, gsi::arg ("layer"), gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio"), "@brief Breaks the polygons of the layer into smaller ones\n" "\n" "This variant applies breaking to all cells and the given layer.\n" From 1ed69e9b468874c15ffff3ce69cc58f31a8aeab5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 17 Jul 2024 23:09:04 +0200 Subject: [PATCH 07/29] Added test --- src/db/unit_tests/dbLayoutUtilsTests.cc | 39 ++++++++++++++++++++++++ testdata/algo/break_polygons_test.gds | Bin 0 -> 1342 bytes testdata/algo/layout_utils_au_bp1.gds | Bin 0 -> 2140 bytes testdata/algo/layout_utils_au_bp2.gds | Bin 0 -> 2026 bytes 4 files changed, 39 insertions(+) create mode 100644 testdata/algo/break_polygons_test.gds create mode 100644 testdata/algo/layout_utils_au_bp1.gds create mode 100644 testdata/algo/layout_utils_au_bp2.gds diff --git a/src/db/unit_tests/dbLayoutUtilsTests.cc b/src/db/unit_tests/dbLayoutUtilsTests.cc index ce0a352c2..24d95c441 100644 --- a/src/db/unit_tests/dbLayoutUtilsTests.cc +++ b/src/db/unit_tests/dbLayoutUtilsTests.cc @@ -779,3 +779,42 @@ TEST(20_scale_and_snap) db::compare_layouts (_this, l1, tl::testdata () + "/algo/layout_utils_au_sns4.oas", db::NormalizationMode (db::WriteOAS + db::WithArrays)); } + +TEST(21_break1) +{ + db::Layout l1; + { + std::string fn (tl::testdata ()); + fn += "/algo/break_polygons_test.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l1); + } + + db::break_polygons (l1, 10, 3.0); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testdata () + "/algo/layout_utils_au_bp1.gds"); +} + +TEST(22_break2) +{ + db::Layout l1; + { + std::string fn (tl::testdata ()); + fn += "/algo/break_polygons_test.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (l1); + } + + unsigned int li1 = find_layer (l1, 1, 0); + unsigned int li2 = find_layer (l1, 2, 0); + + db::break_polygons (l1, li1, 10, 0.0); + db::break_polygons (l1, li2, 0, 3.0); + + CHECKPOINT(); + db::compare_layouts (_this, l1, tl::testdata () + "/algo/layout_utils_au_bp2.gds"); +} + diff --git a/testdata/algo/break_polygons_test.gds b/testdata/algo/break_polygons_test.gds new file mode 100644 index 0000000000000000000000000000000000000000..089d24e177ebbfea6719f02323b5af69b3f3a32a GIT binary patch literal 1342 zcmai!F=$g!6o&u2yxhFjmozQWnEDV~BSa0P6fsmvOiP4_1%m`E;?Tjt!NH+}ql1Hj zgM)+M(m^nzV@HP~85|rPIyeYsaEMUQq450AyYG;MraD}{_s+fN+;jeO?}dTmY$8ZF z=|0k!MF9z9M!(@XX5;2MkWZ%SYcFcIKD{5TGlG9#;gIZ2bhe*s3z+G zSj{DYoD2AY<;)In@-(2#w1ByffKu0y?(ETchY~N}28s_X7q@_H4p7b?08@K_@_ZRc zKLV8V6$ahle9X)DnPm@9R-1r#1yEjm$hFs=V|XILG~! zlE>M+%||Gs*c)<@xfgua6I1IXBR!_Z^)(BkvNuCjl6?fCj{luY8de=8*Zkl6op>h> zfa@*G+P>97S$|5cw*lqV57gotpcH%X%+3qNuhbb*;#Qv9_`=!~V7WJBgR!r@0hF~o zun}YNZG>`O zg8qPn$|7Etwr+y3ESA6APN&9H`)NexXSH4(|z)W_JCH`vb}G2RI0tzwbl5nlP?w3JSBI?6cX^HX1*U= PjM9WD8oq<7o$KN+`fs@Z literal 0 HcmV?d00001 diff --git a/testdata/algo/layout_utils_au_bp1.gds b/testdata/algo/layout_utils_au_bp1.gds new file mode 100644 index 0000000000000000000000000000000000000000..c66a1daf6c4a1be33c2c6d9e5e0a5260467cbf18 GIT binary patch literal 2140 zcmaKu&r4KM6vw~w>(0~0akQyP8zRlZa!3iqh9(=$U?L$2Aqf{QTC`}KAA%~6R$JIOaf^SFda!G zVDd=-l|o8_GeB>}@zNlWeGVu!E=+g*ub-j5I1BVVb)@b>(dkLvJzqoiZvmwopj4eO z%D+bcHqrF{cBDV^Df<_Jd_nvI>N;VRpI`5pb52hV)`0W#AxB;Sm&<_i+I!%l^h3$K zfj-KALHo&d?Ca2ibXoSV9G(WeQ9yb18Bma0IlSilmFgpm&3E$?Fm@3jCzgTHjgV8~ zU3&*8s~%97ekmtpuXm4ReL(dQM&WytC`mq$szV11OIzkkk)dm`EM z)B7RmuX#xJzO)@ub;2lrfB(5)h9vdvgCXh9yh{27>!bYqdLbA0#p_N3NXc&{_igGj zuYmMrKBekT>Te+}HI$_;VdtuQew7`PuX__v>N=rcZ(HkmWcqk2d7bjsOO<85O}X!` zLcaB^l$0xKw-v7E>2+$|F~8$>qtv)CR-Y*XnO(Vs-XFO?gw(h&R?pKeFI^-#sq$V( zjSHLg$>!6T>v?jJJSlE%h19q(RzEJaQ%6?bIK5Ki!YKU!`*%Z{n_C9@WZ$fTe=8;K z>#`C$;alSwGxt!&lx~-ib@RKEE2OObbpRw*S~9nMhw4k#9*wwX?~w=G+Xoyq?z6e_ z7Ja+CD_l=LmK{*9)VMH4Z|#q5YY%rEk2g6%Yn2*5+&Ni)yh*)ue!$wF8%p~8^KH)G zGj AegFUf literal 0 HcmV?d00001 diff --git a/testdata/algo/layout_utils_au_bp2.gds b/testdata/algo/layout_utils_au_bp2.gds new file mode 100644 index 0000000000000000000000000000000000000000..aacb20a86226932e8905959ab52e822dcf4172f3 GIT binary patch literal 2026 zcmai#y-yTT6vfYe%?`LM3$Cu<#sH#0-3T!d+_>U`m|!H47!rx0ps=v8u%NK8ps>)y zg2KYYhQdVt1PTiaVkj&uC@d&Vq@cjWL<*DfoOyFI7-w;k$#3`O-E+U*yax~I^a84> zbbc3kbfE$tg~Pw$r@h(RQ$RVBo0xn#e&^Hs{rdFA@Y>r4^Qh)p`+1%>31of1>qsL7 zub2iP7EKx6_ts|54!hBDha{l5_130}9bL0tdt_m10z6Sc(52NBn z{z>|a)=#ZdUoS1P%dFoxJPr7xfbqg3pv2cWyzcakrbD)jcl8}G)(40aE5PW7m{Zhl zyaJ4MA84{)#tGJX^MLCknhu#1FYKRsG4|Z`WU>KVeg+up?A6FN^&={-@-dnpnWP`i zTeBS7bu*Q&;u){c1LtP|qpc%b>h-QVJ+ZmWz4$a{{fmoZlwPzxNiVEl^HNQsdVYK_ zrt~e2sNPq$W11hCq#xE_h-N6tzkMjC^cB}Ay=Z-sUYIZVs4hJ>W`GQT8&$X0{k>US#K*+rP#R#_RqF7;POH=G*r7dF1u$RPsCd)icYgzNW%Am!ZCT zWh(MXsHp^YCZ^fRmi{{JitM7ONmX}anw>oC zpFVsVYrRemQ76T<&6s8(;$fC}o1+$3Bo+{VTHFL*c$}E(gKEh}(Pjd`^yg-*}%H-P!{j zZC%JPqyO!rAo#w;ehi91@awbY6Edj}+($vM&%V#BJI?L6h(@!|th;IoZWnWoC$)dQ XN=o;(y4kiwm1%fA9`9`HT{eq9-x>oB literal 0 HcmV?d00001 From 4d80fb37c38af4a3f7d03635e7ed504d7938e18a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 17 Jul 2024 23:13:44 +0200 Subject: [PATCH 08/29] Enabled 'break_polygons' for editable Shapes containers --- src/db/db/dbLayoutUtils.cc | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index eaa67e6dc..8b7a22df8 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -715,21 +715,35 @@ static bool split_polygon (bool first, db::Polygon &poly, size_t max_vertex_coun void break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_ratio) { - std::vector new_polygons; - std::vector to_delete; + if (shapes.is_editable ()) { - for (auto s = shapes.begin (db::ShapeIterator::Polygons | db::ShapeIterator::Paths); ! s.at_end (); ++s) { - db::Polygon poly; - s->instantiate (poly); - if (split_polygon (true, poly, max_vertex_count, max_area_ratio, new_polygons)) { - to_delete.push_back (*s); + std::vector new_polygons; + std::vector to_delete; + + for (auto s = shapes.begin (db::ShapeIterator::Polygons | db::ShapeIterator::Paths); ! s.at_end (); ++s) { + db::Polygon poly; + s->instantiate (poly); + if (split_polygon (true, poly, max_vertex_count, max_area_ratio, new_polygons)) { + to_delete.push_back (*s); + } } - } - shapes.erase_shapes (to_delete); + shapes.erase_shapes (to_delete); + + for (auto p = new_polygons.begin (); p != new_polygons.end (); ++p) { + shapes.insert (*p); + } + + } else { + + // In non-editable mode we cannot do "erase", so we use a temporary, editable Shapes container + db::Shapes tmp (true); + tmp.insert (shapes); + + shapes.clear (); + break_polygons (tmp, max_vertex_count, max_area_ratio); + shapes.insert (tmp); - for (auto p = new_polygons.begin (); p != new_polygons.end (); ++p) { - shapes.insert (*p); } } From ee07e4b3b9d369673a4d7ca9f14fda832f2836c6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 17 Jul 2024 23:25:20 +0200 Subject: [PATCH 09/29] Added smoke test for Layout#break_polygons and Shapes#break_polygons to RBA --- src/db/db/dbLayoutUtils.cc | 2 ++ testdata/ruby/dbLayoutTests1.rb | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 8b7a22df8..4829e9131 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -744,6 +744,8 @@ break_polygons (db::Shapes &shapes, size_t max_vertex_count, double max_area_rat break_polygons (tmp, max_vertex_count, max_area_ratio); shapes.insert (tmp); + tl_assert (!shapes.is_editable ()); + } } diff --git a/testdata/ruby/dbLayoutTests1.rb b/testdata/ruby/dbLayoutTests1.rb index 25c8bb7de..d1479b2f3 100644 --- a/testdata/ruby/dbLayoutTests1.rb +++ b/testdata/ruby/dbLayoutTests1.rb @@ -2100,6 +2100,48 @@ class DBLayoutTests1_TestClass < TestBase end + # break_polygons + def test_25 + + def shapes2str(shapes) + str = [] + shapes.each do |s| + str << s.to_s + end + str.join(";") + end + + ly = RBA::Layout::new + top = ly.create_cell("TOP") + l1 = ly.layer(1, 0) + l2 = ly.layer(2, 0) + + top.shapes(l1).insert(RBA::Polygon::new([ [0, 0], [0, 10000], [10000, 10000], [10000, 9000], [1000, 9000], [1000, 0] ])) + top.shapes(l2).insert(RBA::Polygon::new([ [0, 0], [0, 10000], [10000, 10000], [10000, 9000], [1000, 9000], [1000, 0] ])) + + assert_equal(shapes2str(top.shapes(l1)), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + assert_equal(shapes2str(top.shapes(l2)), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + + s1 = top.shapes(l1).dup + assert_equal(shapes2str(s1), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + s1.break_polygons(10, 3.0) + assert_equal(shapes2str(s1), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + + ly2 = ly.dup + top2 = ly2.top_cell + + ly.break_polygons(10, 3.0) + + assert_equal(shapes2str(top.shapes(l1)), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + assert_equal(shapes2str(top.shapes(l2)), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + + ly2.break_polygons(ly2.layer(1, 0), 10, 3.0) + + assert_equal(shapes2str(top2.shapes(ly2.layer(1, 0))), "polygon (0,0;0,9000;1000,9000;1000,0);polygon (0,9000;0,10000;10000,10000;10000,9000)") + assert_equal(shapes2str(top2.shapes(ly2.layer(2, 0))), "polygon (0,0;0,10000;10000,10000;10000,9000;1000,9000;1000,0)") + + end + # Iterating while flatten def test_issue200 From 915cc531954adf4c1fea8809bdd6097a19b66e05 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 20 Jul 2024 00:03:43 +0200 Subject: [PATCH 10/29] Convenience: copy cells now has an option to mute the dialog (shallow/deep) and the dialog is only shown if there are subcells --- src/edt/edt/edtService.cc | 2 +- src/laybasic/laybasic/layLayoutViewConfig.cc | 1 + src/laybasic/laybasic/laybasicConfig.h | 1 + src/layui/layui/CopyCellModeDialog.ui | 70 +++++++-------- src/layui/layui/LayoutViewConfigPage8.ui | 69 +++++++++++++++ src/layui/layui/layDialogs.cc | 3 +- src/layui/layui/layDialogs.h | 2 +- src/layui/layui/layHierarchyControlPanel.cc | 92 +++++++++++++------- src/layui/layui/layHierarchyControlPanel.h | 10 +++ src/layui/layui/layLayoutViewConfigPages.cc | 33 +++++++ src/layui/layui/layLayoutViewConfigPages.h | 17 ++++ src/layui/layui/layui.pro | 1 + src/layview/layview/layLayoutView_qt.cc | 8 ++ 13 files changed, 241 insertions(+), 68 deletions(-) create mode 100644 src/layui/layui/LayoutViewConfigPage8.ui diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index 66b4e2ad5..a39fdcd02 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -462,7 +462,7 @@ Service::copy_selected () for (EditableSelectionIterator r = begin_selection (); ! r.at_end () && ! need_to_ask_for_copy_mode; ++r) { if (r->is_cell_inst ()) { const db::Cell &cell = view ()->cellview (r->cv_index ())->layout ().cell (r->back ().inst_ptr.cell_index ()); - if (! cell.is_proxy ()) { + if (! cell.is_proxy () && ! cell.is_leaf ()) { need_to_ask_for_copy_mode = true; } } diff --git a/src/laybasic/laybasic/layLayoutViewConfig.cc b/src/laybasic/laybasic/layLayoutViewConfig.cc index 6956ea9e4..d1372d966 100644 --- a/src/laybasic/laybasic/layLayoutViewConfig.cc +++ b/src/laybasic/laybasic/layLayoutViewConfig.cc @@ -122,6 +122,7 @@ public: options.push_back (std::pair (cfg_line_style_palette, lay::LineStylePalette ().to_string ())); options.push_back (std::pair (cfg_no_stipple, "false")); options.push_back (std::pair (cfg_markers_visible, "true")); + options.push_back (std::pair (cfg_copy_cell_mode, "-1")); } }; diff --git a/src/laybasic/laybasic/laybasicConfig.h b/src/laybasic/laybasic/laybasicConfig.h index 6fc547669..9fb24ee86 100644 --- a/src/laybasic/laybasic/laybasicConfig.h +++ b/src/laybasic/laybasic/laybasicConfig.h @@ -132,6 +132,7 @@ static const std::string cfg_default_font_size ("default-font-size"); static const std::string cfg_hide_empty_layers ("hide-empty-layers"); static const std::string cfg_test_shapes_in_view ("test-shapes-in-view"); +static const std::string cfg_copy_cell_mode ("copy-cell-mode"); static const std::string cfg_flat_cell_list ("flat-cell-list"); static const std::string cfg_split_cell_list ("split-cell-list"); static const std::string cfg_cell_list_sorting ("cell-list-sorting"); diff --git a/src/layui/layui/CopyCellModeDialog.ui b/src/layui/layui/CopyCellModeDialog.ui index 3f8497c35..6dfe18c4c 100644 --- a/src/layui/layui/CopyCellModeDialog.ui +++ b/src/layui/layui/CopyCellModeDialog.ui @@ -1,46 +1,41 @@ - + + CopyCellModeDialog - - + + 0 0 - 400 - 178 + 546 + 198 - + Copy Cell Options - - - 9 - - - 6 - + - - + + Copy Cell Mode - - - 9 - - + + 6 + + 9 + - - + + Shallow copy (don't copy subcells) - - + + Deep copy (include subcells) @@ -48,12 +43,19 @@ + + + + Don't ask again (you can always reset this in Setup: Application/Cells page) + + + - + Qt::Vertical - + 382 31 @@ -62,12 +64,12 @@ - - + + Qt::Horizontal - - QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -86,11 +88,11 @@ CopyCellModeDialog accept() - + 248 254 - + 157 274 @@ -102,11 +104,11 @@ CopyCellModeDialog reject() - + 316 260 - + 286 274 diff --git a/src/layui/layui/LayoutViewConfigPage8.ui b/src/layui/layui/LayoutViewConfigPage8.ui new file mode 100644 index 000000000..9c15bae72 --- /dev/null +++ b/src/layui/layui/LayoutViewConfigPage8.ui @@ -0,0 +1,69 @@ + + + LayoutViewConfigPage8 + + + + 0 + 0 + 414 + 46 + + + + Form + + + + + + Cell copy mode + + + + + + + + 1 + 0 + + + + QComboBox::AdjustToContents + + + + Shallow mode (cell only) + + + + + Deep mode (cell and subcells) + + + + + Ask + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/src/layui/layui/layDialogs.cc b/src/layui/layui/layDialogs.cc index 748c20520..b1c60d1c4 100644 --- a/src/layui/layui/layDialogs.cc +++ b/src/layui/layui/layDialogs.cc @@ -579,7 +579,7 @@ CopyCellModeDialog::~CopyCellModeDialog () } bool -CopyCellModeDialog::exec_dialog (int ©_mode) +CopyCellModeDialog::exec_dialog (int ©_mode, bool &dont_ask) { QRadioButton *buttons [] = { mp_ui->shallow_rb, mp_ui->deep_rb }; @@ -592,6 +592,7 @@ CopyCellModeDialog::exec_dialog (int ©_mode) if (buttons [i]->isChecked ()) { copy_mode = i; } + dont_ask = mp_ui->dont_ask_cbx->isChecked (); } return true; } else { diff --git a/src/layui/layui/layDialogs.h b/src/layui/layui/layDialogs.h index 5aefa8a7c..cea190b36 100644 --- a/src/layui/layui/layDialogs.h +++ b/src/layui/layui/layDialogs.h @@ -233,7 +233,7 @@ public: * * The mode is either 0 (for shallow), 1 (for deep) */ - bool exec_dialog (int ©_mode); + bool exec_dialog (int ©_mode, bool &dont_ask_again); private: Ui::CopyCellModeDialog *mp_ui; diff --git a/src/layui/layui/layHierarchyControlPanel.cc b/src/layui/layui/layHierarchyControlPanel.cc index 417148bee..ba90fa997 100644 --- a/src/layui/layui/layHierarchyControlPanel.cc +++ b/src/layui/layui/layHierarchyControlPanel.cc @@ -218,6 +218,7 @@ HierarchyControlPanel::HierarchyControlPanel (lay::LayoutViewBase *view, QWidget m_flat (false), m_split_mode (false), m_sorting (CellTreeModel::ByName), + m_cell_copy_mode (-1), m_do_update_content_dm (this, &HierarchyControlPanel::do_update_content), m_do_full_update_content_dm (this, &HierarchyControlPanel::do_full_update_content) { @@ -398,6 +399,12 @@ HierarchyControlPanel::clear_all () mp_cell_lists.clear (); } +void +HierarchyControlPanel::set_cell_copy_mode (int m) +{ + m_cell_copy_mode = m; +} + void HierarchyControlPanel::set_flat (bool f) { @@ -1002,6 +1009,47 @@ HierarchyControlPanel::has_focus () const return m_active_index >= 0 && m_active_index < int (mp_cell_lists.size ()) && mp_cell_lists [m_active_index]->hasFocus (); } +bool +HierarchyControlPanel::ask_for_cell_copy_mode (const db::Layout &layout, const std::vector &paths, int &cell_copy_mode) +{ + bool needs_to_ask = false; + cell_copy_mode = 0; + + if (m_cell_copy_mode < 0) { // ask + + // check if there is a cell that we have to ask for + for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { + if (! p->empty ()) { + const db::Cell &cell = layout.cell (p->back ()); + if (! cell.is_proxy () && ! cell.is_leaf ()) { + needs_to_ask = true; + } + } + } + + } else { + cell_copy_mode = m_cell_copy_mode; + } + + if (needs_to_ask) { + + bool dont_ask_again = false; + + lay::CopyCellModeDialog mode_dialog (this); + if (! mode_dialog.exec_dialog (cell_copy_mode, dont_ask_again)) { + return false; + } + + if (dont_ask_again) { + view ()->dispatcher ()->config_set (cfg_copy_cell_mode, tl::to_string (cell_copy_mode)); + view ()->dispatcher ()->config_end (); + } + + } + + return true; +} + void HierarchyControlPanel::cut () { @@ -1017,34 +1065,25 @@ HierarchyControlPanel::cut () } // first copy - bool needs_to_ask = false; db::Layout &layout = m_cellviews [m_active_index]->layout (); if (! layout.is_editable ()) { return; } - // collect the called cells of the cells to copy, so we don't copy a cell twice - db::Clipboard::instance ().clear (); - // don't copy the cells which would be copied anyway + int cut_mode = 1; // 0: shallow, 1: deep + if (! ask_for_cell_copy_mode (layout, paths, cut_mode)) { + return; + } + + // collect the called cells of the cells to copy, so we don't copy a cell twice std::set called_cells; for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { if (! p->empty ()) { const db::Cell &cell = layout.cell (p->back ()); cell.collect_called_cells (called_cells); - if (cell.cell_instances () > 0) { - needs_to_ask = true; - } - } - } - - int cut_mode = 1; // 0: shallow, 1: deep - if (needs_to_ask) { - lay::CopyCellModeDialog mode_dialog (this); - if (! mode_dialog.exec_dialog (cut_mode)) { - return; } } @@ -1115,34 +1154,25 @@ HierarchyControlPanel::copy () return; } - bool needs_to_ask = false; - db::Layout &layout = m_cellviews [m_active_index]->layout (); - // collect the called cells of the cells to copy, so we don't copy a cell twice - db::Clipboard::instance ().clear (); - // don't copy the cells which would be copied anyway + int copy_mode = 1; // 0: shallow, 1: deep + if (! ask_for_cell_copy_mode (layout, paths, copy_mode)) { + return; + } + + // collect the called cells of the cells to copy, so we don't copy a cell twice std::set called_cells; for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { if (! p->empty ()) { const db::Cell &cell = layout.cell (p->back ()); cell.collect_called_cells (called_cells); - if (cell.cell_instances () > 0) { - needs_to_ask = true; - } - } - } - - int copy_mode = 1; // 0: shallow, 1: deep - if (needs_to_ask) { - lay::CopyCellModeDialog mode_dialog (this); - if (! mode_dialog.exec_dialog (copy_mode)) { - return; } } + // actually copy for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { if (! p->empty () && called_cells.find (p->back ()) == called_cells.end ()) { db::ClipboardValue *cd = new db::ClipboardValue (); diff --git a/src/layui/layui/layHierarchyControlPanel.h b/src/layui/layui/layHierarchyControlPanel.h index 6801728d8..2053628d1 100644 --- a/src/layui/layui/layHierarchyControlPanel.h +++ b/src/layui/layui/layHierarchyControlPanel.h @@ -219,6 +219,12 @@ public: */ void paste (); + /** + * @brief Selects cell copy mode + * 0: shallow, 1: deep, -1: ask + */ + void set_cell_copy_mode (int m); + /** * @brief Return true, if the panel has a selection */ @@ -308,6 +314,7 @@ private: QSplitter *mp_splitter; tl::Color m_background_color; tl::Color m_text_color; + int m_cell_copy_mode; tl::DeferredMethod m_do_update_content_dm; tl::DeferredMethod m_do_full_update_content_dm; std::unique_ptr mp_tree_style; @@ -336,6 +343,9 @@ private: // clears all widgets of the cell lists void clear_all (); + + // ask for cell copy mode + bool ask_for_cell_copy_mode (const db::Layout &layout, const std::vector &paths, int &cell_copy_mode); }; } // namespace lay diff --git a/src/layui/layui/layLayoutViewConfigPages.cc b/src/layui/layui/layLayoutViewConfigPages.cc index c7f57f806..81d7cbf1e 100644 --- a/src/layui/layui/layLayoutViewConfigPages.cc +++ b/src/layui/layui/layLayoutViewConfigPages.cc @@ -42,6 +42,7 @@ #include "ui_LayoutViewConfigPage6.h" #include "ui_LayoutViewConfigPage6a.h" #include "ui_LayoutViewConfigPage7.h" +#include "ui_LayoutViewConfigPage8.h" #include "laySelectStippleForm.h" #include "laySelectLineStyleForm.h" @@ -1529,6 +1530,37 @@ LayoutViewConfigPage7::commit (lay::Dispatcher *root) root->config_set (cfg_initial_hier_depth, mp_ui->def_depth->value ()); } +// ------------------------------------------------------------ +// LayoutConfigPage8 implementation + +LayoutViewConfigPage8::LayoutViewConfigPage8 (QWidget *parent) + : lay::ConfigPage (parent) +{ + mp_ui = new Ui::LayoutViewConfigPage8 (); + mp_ui->setupUi (this); +} + +LayoutViewConfigPage8::~LayoutViewConfigPage8 () +{ + delete mp_ui; + mp_ui = 0; +} + +void +LayoutViewConfigPage8::setup (lay::Dispatcher *root) +{ + int cpm = -1; + root->config_get (cfg_copy_cell_mode, cpm); + mp_ui->hier_copy_mode_cbx->setCurrentIndex ((cpm < 0 || cpm > 1) ? 2 : cpm); +} + +void +LayoutViewConfigPage8::commit (lay::Dispatcher *root) +{ + int cpm = mp_ui->hier_copy_mode_cbx->currentIndex (); + root->config_set (cfg_copy_cell_mode, (cpm < 0 || cpm > 1) ? -1 : cpm); +} + // ------------------------------------------------------------ // The dummy plugin declaration to register the configuration options @@ -1554,6 +1586,7 @@ public: pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Tracking")), new LayoutViewConfigPage2d (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Layer Properties")), new LayoutViewConfigPage5 (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Units")), new LayoutViewConfigPage3c (parent))); + pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Cells")), new LayoutViewConfigPage8 (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Navigation|New Cell")), new LayoutViewConfigPage3a (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Navigation|Zoom And Pan")), new LayoutViewConfigPage3b (parent))); diff --git a/src/layui/layui/layLayoutViewConfigPages.h b/src/layui/layui/layLayoutViewConfigPages.h index ab6cbece6..3d6c4718d 100644 --- a/src/layui/layui/layLayoutViewConfigPages.h +++ b/src/layui/layui/layLayoutViewConfigPages.h @@ -50,6 +50,7 @@ namespace Ui { class LayoutViewConfigPage6; class LayoutViewConfigPage6a; class LayoutViewConfigPage7; + class LayoutViewConfigPage8; } namespace lay @@ -355,6 +356,22 @@ private: Ui::LayoutViewConfigPage7 *mp_ui; }; +class LayoutViewConfigPage8 + : public lay::ConfigPage +{ +Q_OBJECT + +public: + LayoutViewConfigPage8 (QWidget *parent); + ~LayoutViewConfigPage8 (); + + virtual void setup (lay::Dispatcher *root); + virtual void commit (lay::Dispatcher *root); + +private: + Ui::LayoutViewConfigPage8 *mp_ui; +}; + } #endif diff --git a/src/layui/layui/layui.pro b/src/layui/layui/layui.pro index 00099e914..ed6ac69a0 100644 --- a/src/layui/layui/layui.pro +++ b/src/layui/layui/layui.pro @@ -40,6 +40,7 @@ FORMS = \ LayoutViewConfigPage6.ui \ LayoutViewConfigPage7.ui \ LayoutViewConfigPage.ui \ + LayoutViewConfigPage8.ui \ LibraryCellSelectionForm.ui \ LoadLayoutOptionsDialog.ui \ MarkerBrowserConfigPage2.ui \ diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index e30283dba..d4f67b772 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -938,6 +938,14 @@ LayoutView::configure (const std::string &name, const std::string &value) } return true; + } else if (name == cfg_copy_cell_mode) { + + if (mp_hierarchy_panel) { + int m = 0; + tl::from_string (value, m); + mp_hierarchy_panel->set_cell_copy_mode (m); + } + } else if (name == cfg_cell_list_sorting) { if (mp_hierarchy_panel) { From 50a9f7004e1ee337cdcaba8a247c5ec76fc539e6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 20 Jul 2024 00:42:38 +0200 Subject: [PATCH 11/29] Fixed a link issue --- src/edt/edt/edtService.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index 3b34ffe5a..b6f65ae82 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -74,7 +74,7 @@ std::map pcell_parameters_from_string (const std::stri /** * @brief A utility class to implement a selection iterator across all editor services */ -class EditableSelectionIterator +class EDT_PUBLIC EditableSelectionIterator { public: typedef std::set objects; From a323bb98bac0757db93eb897f2e9be5684f7920f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 20 Jul 2024 00:54:48 +0200 Subject: [PATCH 12/29] Bugfix - missed return value. --- src/layview/layview/layLayoutView_qt.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index d4f67b772..a63b74492 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -945,6 +945,7 @@ LayoutView::configure (const std::string &name, const std::string &value) tl::from_string (value, m); mp_hierarchy_panel->set_cell_copy_mode (m); } + return true; } else if (name == cfg_cell_list_sorting) { From 6baabc30bb794f6967edc2ba12b4d8bba9351471 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 24 Jul 2024 20:57:17 +0200 Subject: [PATCH 13/29] Added Netlist#top_circuit and Netlist#top_circuits convenience methods --- src/db/db/dbNetlist.cc | 41 +++++++++++++ src/db/db/dbNetlist.h | 26 ++++++++ src/db/db/gsiDeclDbNetlist.cc | 26 ++++++++ src/db/unit_tests/dbNetlistTests.cc | 94 +++++++++++++++++++++++++++++ testdata/ruby/dbNetlist.rb | 11 ++++ 5 files changed, 198 insertions(+) diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index f0f8d177c..e5e801863 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -366,6 +366,47 @@ size_t Netlist::top_circuit_count () const return m_top_circuits; } +Circuit *Netlist::top_circuit () +{ + size_t ntop = top_circuit_count (); + if (ntop == 0) { + return 0; + } else if (ntop > 1) { + throw tl::Exception (tl::to_string (tr ("Netlist contains more than a single top circuit"))); + } else { + return begin_top_down ().operator-> (); + } +} + +const Circuit *Netlist::top_circuit () const +{ + return const_cast (this)->top_circuit (); +} + +std::vector Netlist::top_circuits () +{ + size_t ntop = top_circuit_count (); + std::vector result; + result.reserve (ntop); + for (auto c = begin_top_down (); ntop > 0 && c != end_top_down (); ++c) { + result.push_back (c.operator-> ()); + --ntop; + } + return result; +} + +std::vector Netlist::top_circuits () const +{ + size_t ntop = top_circuit_count (); + std::vector result; + result.reserve (ntop); + for (auto c = begin_top_down (); ntop > 0 && c != end_top_down (); ++c) { + result.push_back (c.operator-> ()); + --ntop; + } + return result; +} + Netlist::bottom_up_circuit_iterator Netlist::begin_bottom_up () { if (! m_valid_topology) { diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 9e5221796..0bff649e1 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -271,6 +271,32 @@ public: return m_circuit_by_cell_index.object_by (cell_index); } + /** + * @brief Gets the top circuit if there is one + * This method will assert if there is more than a single top circuit. + * It will return 0 if there is no top circuit. + */ + Circuit *top_circuit (); + + /** + * @brief Gets the top circuit if there is one (const version) + * This method will assert if there is more than a single top circuit. + * It will return 0 if there is no top circuit. + */ + const Circuit *top_circuit () const; + + /** + * @brief Gets the top circuits + * This convenience method will return a list of top circuits. + */ + std::vector top_circuits (); + + /** + * @brief Gets the top circuits (const version) + * This convenience method will return a list of top circuits. + */ + std::vector top_circuits () const; + /** * @brief Gets the top-down circuits iterator (begin) * This iterator will deliver the circuits in a top-down way - i.e. child circuits diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index f84294c9f..1573631dc 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -2044,6 +2044,32 @@ Class decl_dbNetlist ("db", "Netlist", "\n" "This method has been introduced in version 0.28.4.\n" ) + + gsi::method ("top_circuit", static_cast (&db::Netlist::top_circuit), + "@brief Gets the top circuit.\n" + "This method will return nil, if there is no top circuit. It will raise an error, if there is more than " + "a single top circuit.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + + gsi::method ("top_circuit", static_cast (&db::Netlist::top_circuit), + "@brief Gets the top circuit (const version).\n" + "This method will return nil, if there is no top circuit. It will raise an error, if there is more than " + "a single top circuit.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + + gsi::method ("top_circuits", static_cast (db::Netlist::*) ()> (&db::Netlist::top_circuits), + "@brief Gets the top circuits.\n" + "Returns a list of top circuits.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + + gsi::method ("top_circuits", static_cast (db::Netlist::*) () const> (&db::Netlist::top_circuits), + "@brief Gets the top circuits.\n" + "Returns a list of top circuits.\n" + "\n" + "This convenience method has been added in version 0.29.5." + ) + gsi::method_ext ("nets_by_name", &nets_by_name_const_from_netlist, gsi::arg ("name_pattern"), "@brief Gets the net objects for a given name filter (const version).\n" "The name filter is a glob pattern. This method will return all \\Net objects matching the glob pattern.\n" diff --git a/src/db/unit_tests/dbNetlistTests.cc b/src/db/unit_tests/dbNetlistTests.cc index dbc8b8e9a..6cb051c86 100644 --- a/src/db/unit_tests/dbNetlistTests.cc +++ b/src/db/unit_tests/dbNetlistTests.cc @@ -208,6 +208,18 @@ static std::string parents2string (const db::Circuit *c) return res; } +static std::string td2string_nc (db::Netlist *nl) +{ + std::string res; + for (db::Netlist::top_down_circuit_iterator r = nl->begin_top_down (); r != nl->end_top_down (); ++r) { + if (!res.empty ()) { + res += ","; + } + res += r->name (); + } + return res; +} + static std::string td2string (const db::Netlist *nl) { std::string res; @@ -220,6 +232,64 @@ static std::string td2string (const db::Netlist *nl) return res; } +static std::string tcs2string_nc (db::Netlist *nl) +{ + std::string res; + std::vector tops = nl->top_circuits (); + for (auto i = tops.begin (); i != tops.end (); ++i) { + if (!res.empty ()) { + res += ","; + } + res += (*i)->name (); + } + return res; +} + +static std::string tcs2string (const db::Netlist *nl) +{ + std::string res; + std::vector tops = nl->top_circuits (); + for (auto i = tops.begin (); i != tops.end (); ++i) { + if (!res.empty ()) { + res += ","; + } + res += (*i)->name (); + } + return res; +} + +static std::string tc2string_nc (db::Netlist *nl) +{ + const db::Circuit *tc = nl->top_circuit (); + if (!tc) { + return "(nil)"; + } else { + return tc->name (); + } +} + +static std::string tc2string (const db::Netlist *nl) +{ + const db::Circuit *tc = nl->top_circuit (); + if (!tc) { + return "(nil)"; + } else { + return tc->name (); + } +} + +static std::string bu2string_nc (db::Netlist *nl) +{ + std::string res; + for (db::Netlist::bottom_up_circuit_iterator r = nl->begin_bottom_up (); r != nl->end_bottom_up (); ++r) { + if (!res.empty ()) { + res += ","; + } + res += r->name (); + } + return res; +} + static std::string bu2string (const db::Netlist *nl) { std::string res; @@ -1038,20 +1108,44 @@ TEST(12_NetlistTopology) { std::unique_ptr nl (new db::Netlist ()); EXPECT_EQ (nl->top_circuit_count (), size_t (0)); + EXPECT_EQ (tcs2string (nl.get ()), ""); + EXPECT_EQ (tcs2string_nc (nl.get ()), ""); + EXPECT_EQ (tc2string (nl.get ()), "(nil)"); + EXPECT_EQ (tc2string_nc (nl.get ()), "(nil)"); db::Circuit *c1 = new db::Circuit (); c1->set_name ("c1"); nl->add_circuit (c1); EXPECT_EQ (nl->top_circuit_count (), size_t (1)); EXPECT_EQ (td2string (nl.get ()), "c1"); + EXPECT_EQ (td2string_nc (nl.get ()), "c1"); + EXPECT_EQ (tcs2string (nl.get ()), "c1"); + EXPECT_EQ (tcs2string_nc (nl.get ()), "c1"); + EXPECT_EQ (tc2string (nl.get ()), "c1"); + EXPECT_EQ (tc2string_nc (nl.get ()), "c1"); EXPECT_EQ (bu2string (nl.get ()), "c1"); + EXPECT_EQ (bu2string_nc (nl.get ()), "c1"); db::Circuit *c2 = new db::Circuit (); c2->set_name ("c2"); nl->add_circuit (c2); EXPECT_EQ (nl->top_circuit_count (), size_t (2)); EXPECT_EQ (td2string (nl.get ()), "c2,c1"); + EXPECT_EQ (td2string_nc (nl.get ()), "c2,c1"); + EXPECT_EQ (tcs2string (nl.get ()), "c2,c1"); + EXPECT_EQ (tcs2string_nc (nl.get ()), "c2,c1"); + try { + tc2string (nl.get ()); + EXPECT_EQ (true, false); + } catch (...) { + } + try { + tc2string_nc (nl.get ()); + EXPECT_EQ (true, false); + } catch (...) { + } EXPECT_EQ (bu2string (nl.get ()), "c1,c2"); + EXPECT_EQ (bu2string_nc (nl.get ()), "c1,c2"); std::unique_ptr locker (new db::NetlistLocker (nl.get ())); diff --git a/testdata/ruby/dbNetlist.rb b/testdata/ruby/dbNetlist.rb index 1ceb1331c..07b8bfa5c 100644 --- a/testdata/ruby/dbNetlist.rb +++ b/testdata/ruby/dbNetlist.rb @@ -832,18 +832,25 @@ END nl = RBA::Netlist::new assert_equal(nl.top_circuit_count, 0) + assert_equal(nl.top_circuit == nil, true) c1 = RBA::Circuit::new c1.name = "C1" c1.cell_index = 17 nl.add(c1) assert_equal(nl.top_circuit_count, 1) + assert_equal(nl.top_circuit.name, "C1") c2 = RBA::Circuit::new c2.name = "C2" c1.cell_index = 42 nl.add(c2) assert_equal(nl.top_circuit_count, 2) + begin + nl.top_circuit + assert_equal(true, false) + rescue + end c3 = RBA::Circuit::new c3.name = "C3" @@ -854,6 +861,10 @@ END nl.each_circuit_top_down { |c| names << c.name } assert_equal(names.join(","), "C3,C2,C1") + names = [] + nl.top_circuits.each { |c| names << c.name } + assert_equal(names.join(","), "C3,C2,C1") + names = [] nl.each_circuit_bottom_up { |c| names << c.name } assert_equal(names.join(","), "C1,C2,C3") From 4cd8772e70ccf0f4da24216ae998e33761051ee7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 24 Jul 2024 22:29:09 +0200 Subject: [PATCH 14/29] Convenience methods Shape#properties, Layout#properties, Cell#properties, Instance#properties --- src/db/db/gsiDeclDbCell.cc | 58 +++++++++++++++++++++++++++++++-- src/db/db/gsiDeclDbLayout.cc | 23 ++++++++++++- src/db/db/gsiDeclDbShape.cc | 26 +++++++++++++++ testdata/ruby/dbLayoutTests1.rb | 2 ++ testdata/ruby/dbLayoutTests2.rb | 4 +++ testdata/ruby/dbShapesTest.rb | 22 +++++++++++++ 6 files changed, 131 insertions(+), 4 deletions(-) diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index fc32fa381..c3c053550 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -1104,14 +1104,14 @@ static void set_cell_property (db::Cell *c, const tl::Variant &key, const tl::Va c->prop_id (layout->properties_repository ().properties_id (props)); } -static tl::Variant get_cell_property (db::Cell *c, const tl::Variant &key) +static tl::Variant get_cell_property (const db::Cell *c, const tl::Variant &key) { db::properties_id_type id = c->prop_id (); if (id == 0) { return tl::Variant (); } - db::Layout *layout = c->layout (); + const db::Layout *layout = c->layout (); if (! layout) { throw tl::Exception (tl::to_string (tr ("Cell does not reside inside a layout - cannot retrieve properties"))); } @@ -1130,6 +1130,26 @@ static tl::Variant get_cell_property (db::Cell *c, const tl::Variant &key) } } +static tl::Variant get_cell_properties (const db::Cell *c) +{ + db::properties_id_type id = c->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + const db::Layout *layout = c->layout (); + if (! layout) { + throw tl::Exception (tl::to_string (tr ("Cell does not reside inside a layout - cannot retrieve properties"))); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + static bool is_pcell_variant (const db::Cell *cell) { tl_assert (cell->layout () != 0); @@ -1841,10 +1861,16 @@ Class decl_Cell ("db", "Cell", "@brief Gets the user property with the given key\n" "This method is a convenience method that gets the property with the given key. " "If no property with that key exists, it will return nil. Using that method is more " - "convenient than using the layout object and the properties ID to retrieve the property value. " + "convenient than using the layout object and the properties ID to retrieve the property value.\n" "\n" "This method has been introduced in version 0.23." ) + + gsi::method_ext ("properties", &get_cell_properties, + "@brief Gets the user properties as a hash\n" + "This method is a convenience method that gets all user properties as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method_ext ("add_meta_info", &cell_add_meta_info, gsi::arg ("info"), "@brief Adds meta information to the cell\n" "See \\LayoutMetaInfo for details about cells and meta information.\n" @@ -3539,6 +3565,26 @@ static tl::Variant get_property (const db::Instance *i, const tl::Variant &key) } } +static tl::Variant get_properties (const db::Instance *i) +{ + db::properties_id_type id = i->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + const db::Layout *layout = layout_ptr_const (i); + if (! layout) { + throw tl::Exception (tl::to_string (tr ("Instance does not reside inside a layout - cannot retrieve properties"))); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + static bool inst_is_valid (const db::Instance *inst) { return inst->instances () && inst->instances ()->is_valid (*inst); @@ -4011,6 +4057,12 @@ Class decl_Instance ("db", "Instance", "\n" "This method has been introduced in version 0.22." ) + + gsi::method_ext ("properties", &get_properties, + "@brief Gets the user properties as a hash\n" + "This method is a convenience method that gets all user properties as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + method_ext ("[]", &inst_index, gsi::arg ("key"), "@brief Gets the user property with the given key or, if available, the PCell parameter with the name given by the key\n" "Getting the PCell parameter has priority over the user property." diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index 9549fea59..751c4152e 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -344,7 +344,7 @@ static void set_layout_property (db::Layout *l, const tl::Variant &key, const tl l->prop_id (l->properties_repository ().properties_id (props)); } -static tl::Variant get_layout_property (db::Layout *l, const tl::Variant &key) +static tl::Variant get_layout_property (const db::Layout *l, const tl::Variant &key) { // TODO: check if is editable @@ -367,6 +367,21 @@ static tl::Variant get_layout_property (db::Layout *l, const tl::Variant &key) } } +static tl::Variant get_layout_properties (const db::Layout *layout) +{ + db::properties_id_type id = layout->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + static db::cell_index_type cell_by_name (db::Layout *l, const char *name) { std::pair c = l->cell_by_name (name); @@ -1265,6 +1280,12 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.24." ) + + gsi::method_ext ("properties", &get_layout_properties, + "@brief Gets the user properties as a hash\n" + "This method is a convenience method that gets all user properties as a single hash.\n" + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::method_ext ("properties_id", &properties_id, gsi::arg ("properties"), "@brief Gets the properties ID for a given properties set\n" "\n" diff --git a/src/db/db/gsiDeclDbShape.cc b/src/db/db/gsiDeclDbShape.cc index 708a52f5e..b757703c6 100644 --- a/src/db/db/gsiDeclDbShape.cc +++ b/src/db/db/gsiDeclDbShape.cc @@ -1004,6 +1004,26 @@ static tl::Variant get_property (const db::Shape *s, const tl::Variant &key) } } +static tl::Variant get_properties (const db::Shape *s) +{ + db::properties_id_type id = s->prop_id (); + if (id == 0) { + return tl::Variant::empty_array (); + } + + const db::Layout *layout = layout_ptr_const (s); + if (! layout) { + throw tl::Exception (tl::to_string (tr ("Shape does not reside inside a layout - cannot retrieve properties"))); + } + + tl::Variant res = tl::Variant::empty_array (); + const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (id); + for (auto i = props.begin (); i != props.end (); ++i) { + res.insert (layout->properties_repository ().prop_name (i->first), i->second); + } + return res; +} + namespace { @@ -1354,6 +1374,12 @@ Class decl_Shape ("db", "Shape", "\n" "This method has been introduced in version 0.22." ) + + gsi::method_ext ("properties", &get_properties, + "@brief Gets the user properties\n" + "This method is a convenience method that gets the properties of the shape as a hash. " + "\n" + "This method has been introduced in version 0.29.5." + ) + gsi::iterator ("each_point", &db::Shape::begin_point, &db::Shape::end_point, "@brief Iterates over all points of the object\n" "\n" diff --git a/testdata/ruby/dbLayoutTests1.rb b/testdata/ruby/dbLayoutTests1.rb index d1479b2f3..6acbd2474 100644 --- a/testdata/ruby/dbLayoutTests1.rb +++ b/testdata/ruby/dbLayoutTests1.rb @@ -1334,6 +1334,7 @@ class DBLayoutTests1_TestClass < TestBase i0 = nil c0c.each_inst { |i| i.cell_index == l.cell("c1$1").cell_index && i0 = i } assert_equal(i0.property("p"), 18) + assert_equal(i0.properties, {"p" => 18}) assert_equal(l.cell("c1$1").begin_shapes_rec(0).shape.property("p"), 17) assert_equal(collect(c0c.begin_shapes_rec(0), l), "[c0$1](0,100;1000,1200)/[c2$1](100,0;1100,1100)/[c3$1](1200,0;2200,1100)/[c3$1](-1200,0;-100,1000)/[c1$1](0,100;1000,1200)") @@ -1379,6 +1380,7 @@ class DBLayoutTests1_TestClass < TestBase tt = RBA::Trans.new i0 = c0.insert(RBA::CellInstArray.new(c1.cell_index, tt)) + assert_equal(i0.properties, {}) i0.set_property("p", 18) c0.insert(RBA::CellInstArray.new(c2.cell_index, RBA::Trans.new(RBA::Point.new(100, -100)))) c0.insert(RBA::CellInstArray.new(c3.cell_index, RBA::Trans.new(1))) diff --git a/testdata/ruby/dbLayoutTests2.rb b/testdata/ruby/dbLayoutTests2.rb index bf5349645..fc1be5ac6 100644 --- a/testdata/ruby/dbLayoutTests2.rb +++ b/testdata/ruby/dbLayoutTests2.rb @@ -674,6 +674,7 @@ class DBLayoutTests2_TestClass < TestBase lindex = ly.insert_layer( linfo ) c1 = ly.cell( ci1 ) + assert_equal( c1.properties, {} ) c2 = ly.cell( ci2 ) tr = RBA::Trans::new inst = c2.insert( RBA::CellInstArray::new( c1.cell_index, tr ) ) @@ -701,6 +702,7 @@ class DBLayoutTests2_TestClass < TestBase c1.prop_id = pid assert_equal( c1.prop_id, pid ) assert_equal( c1.property( 17 ).inspect, "\"a\"" ) + assert_equal( c1.properties, { 17 => "a", "b" => [1, 5, 7] } ) c1.set_property( 5, 23 ) c1.delete_property( 17 ) assert_equal( c1.property( 17 ).inspect, "nil" ) @@ -1027,6 +1029,7 @@ class DBLayoutTests2_TestClass < TestBase def test_11 ly = RBA::Layout::new + assert_equal(ly.properties, {}) assert_equal(ly.prop_id, 0) ly.prop_id = 1 @@ -1037,6 +1040,7 @@ class DBLayoutTests2_TestClass < TestBase ly.set_property("x", 1) assert_equal(ly.prop_id, 1) assert_equal(ly.property("x"), 1) + assert_equal(ly.properties, {"x" => 1}) ly.set_property("x", 17) assert_equal(ly.prop_id, 2) assert_equal(ly.property("x"), 17) diff --git a/testdata/ruby/dbShapesTest.rb b/testdata/ruby/dbShapesTest.rb index 61b8c9243..b77f7f183 100644 --- a/testdata/ruby/dbShapesTest.rb +++ b/testdata/ruby/dbShapesTest.rb @@ -1656,6 +1656,28 @@ class DBShapes_TestClass < TestBase end + # Shape objects and properties + def test_13 + + ly = RBA::Layout::new + l1 = ly.layer(1, 0) + tc = ly.create_cell("TOP") + sh = tc.shapes(l1).insert(RBA::Box::new(0, 0, 100, 200)) + + assert_equal(sh.property("k").inspect, "nil") + assert_equal(sh.properties.inspect, "{}") + + sh.set_property("k", 17) + + assert_equal(sh.property("k").inspect, "17") + assert_equal(sh.property("u").inspect, "nil") + assert_equal(sh.properties.inspect, "{\"k\"=>17}") + + sh.set_property("u", "42") + assert_equal(sh.properties.inspect, "{\"k\"=>17, \"u\"=>\"42\"}") + + end + end load("test_epilogue.rb") From 92e44bd9b9025632ffd3cf10c4412ae66cea29ff Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 24 Jul 2024 22:36:06 +0200 Subject: [PATCH 15/29] Small update of doc. --- src/db/db/gsiDeclDbShape.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/db/gsiDeclDbShape.cc b/src/db/db/gsiDeclDbShape.cc index b757703c6..36e6ee4d4 100644 --- a/src/db/db/gsiDeclDbShape.cc +++ b/src/db/db/gsiDeclDbShape.cc @@ -1376,7 +1376,7 @@ Class decl_Shape ("db", "Shape", ) + gsi::method_ext ("properties", &get_properties, "@brief Gets the user properties\n" - "This method is a convenience method that gets the properties of the shape as a hash. " + "This method is a convenience method that gets the properties of the shape as a single hash.\n" "\n" "This method has been introduced in version 0.29.5." ) + From 58d489b39a9ecee1b4bf8518ac3de52c0b6da8ed Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 24 Jul 2024 22:54:47 +0200 Subject: [PATCH 16/29] Added Region#being_shapes_rec and Region#begin_merged_shapes_rec, mainly for being able to retrieve shapes with properties from Region#nets --- src/db/db/gsiDeclDbRegion.cc | 45 ++++++++++++++++++++++++++++++++- testdata/python/dbRegionTest.py | 25 ++++++++++++++++++ testdata/ruby/dbRegionTest.rb | 27 ++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 059dd4d67..886a52ff6 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1132,6 +1132,24 @@ rasterize1 (const db::Region *region, const db::Point &origin, const db::Vector return rasterize2 (region, origin, pixel_size, pixel_size, nx, ny); } +static tl::Variant begin_shapes_rec (const db::Region *region) +{ + auto res = region->begin_iter (); + tl::Variant r = tl::Variant (std::vector ()); + r.push (tl::Variant (res.first)); + r.push (tl::Variant (res.second)); + return r; +} + +static tl::Variant begin_merged_shapes_rec (const db::Region *region) +{ + auto res = region->begin_merged_iter (); + tl::Variant r = tl::Variant (std::vector ()); + r.push (tl::Variant (res.first)); + r.push (tl::Variant (res.second)); + return r; +} + static db::Point default_origin; // provided by gsiDeclDbPolygon.cc: @@ -3757,7 +3775,32 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "metal1_all_nets = metal1.nets\n" "@/code\n" "\n" - "This method was introduced in version 0.28.4" + "This method was introduced in version 0.28.4." + ) + + gsi::method_ext ("begin_shapes_rec", &begin_shapes_rec, + "@brief Returns a recursive shape iterator plus a transformation for the shapes constituting this region.\n" + "This method returns a pair consisting of a \\RecursiveShapeIterator plus a \\ICplxTrans transformation. " + "Both objects allow accessing the shapes (polygons) of the region in a detailed fashion. To iterate the " + "the polygons use a code like this:\n" + "\n" + "@code\n" + "iter, trans = region.begin_shapes_rec\n" + "iter.each do |i|\n" + " polygon = trans * iter.trans * i.shape.polygon\n" + " ...\n" + "end\n" + "@/code\n" + "\n" + "This method is the most powerful way of accessing the shapes inside the region. I allows for example to obtain the " + "properties attached to the polygons of the region. It is primarily intended for special applications like iterating net-annotated shapes.\n" + "\n" + "This speciality method was introduced in version 0.29.5." + ) + + gsi::method_ext ("begin_merged_shapes_rec", &begin_merged_shapes_rec, + "@brief Returns a recursive shape iterator plus a transformation for the shapes constituting the merged region.\n" + "It can be used like \\begin_shapes_rec, but delivers shapes from the merged polygons pool.\n" + "\n" + "This speciality method was introduced in version 0.29.5." ) + gsi::make_property_methods () , diff --git a/testdata/python/dbRegionTest.py b/testdata/python/dbRegionTest.py index 7a2e3af3c..3384f933a 100644 --- a/testdata/python/dbRegionTest.py +++ b/testdata/python/dbRegionTest.py @@ -81,6 +81,31 @@ class DBRegionTest(unittest.TestCase): dss = None self.assertEqual(pya.DeepShapeStore.instance_count(), 0) + # begin_shapes_rec and begin_shapes_merged_rec + def test_extended_iter(self): + + r = pya.Region() + + # NOTE: this also tests the copy semantics of the RecursiveShape to Variant binding in RBA: + it, trans = r.begin_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "") + + it, trans = r.begin_merged_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "") + + r.insert(pya.Box(0, 0, 100, 100)) + r.insert(pya.Box(50, 50, 200, 200)) + + it, trans = r.begin_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "(0,0;0,100;100,100;100,0),(50,50;50,200;200,200;200,50)") + + it, trans = r.begin_merged_shapes_rec() + s = ",".join([ str(trans*i.trans()*i.shape().polygon) for i in it.each() ]) + self.assertEqual(s, "(0,0;0,100;50,100;50,200;200,200;200,50;100,50;100,0)") + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(DBRegionTest) diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index cd4546c7c..149c23171 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1471,6 +1471,33 @@ class DBRegion_TestClass < TestBase end + # begin_shapes_rec and begin_shapes_merged_rec + def test_extended_iter + + r = RBA::Region::new() + + # NOTE: this also tests the copy semantics of the RecursiveShape to Variant binding in RBA: + iter, trans = r.begin_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "") + + iter, trans = r.begin_merged_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "") + + r.insert(RBA::Box::new(0, 0, 100, 100)) + r.insert(RBA::Box::new(50, 50, 200, 200)) + + iter, trans = r.begin_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "(0,0;0,100;100,100;100,0),(50,50;50,200;200,200;200,50)") + + iter, trans = r.begin_merged_shapes_rec + str = iter.each.collect { |i| (trans*i.trans*i.shape.polygon).to_s }.join(",") + assert_equal(str, "(0,0;0,100;50,100;50,200;200,200;200,50;100,50;100,0)") + + end + end load("test_epilogue.rb") From a0d26a59e3ac800382a7033f5b4d8022eb226425 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 25 Jul 2024 21:07:36 +0200 Subject: [PATCH 17/29] Properly transferring ownership for variant to Python/Ruby translation --- src/pya/pya/pyaConvert.cc | 9 +++++++-- src/rba/rba/rbaConvert.cc | 9 +++++++-- src/tl/tl/tlObject.cc | 5 +++++ src/tl/tl/tlObject.h | 12 ++++++++++++ src/tl/tl/tlVariant.cc | 15 +++++++++++++++ src/tl/tl/tlVariant.h | 7 +++++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index d80af95e9..017d030e2 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -533,8 +533,13 @@ PyObject *c2python_func::operator() (const tl::Variant &c) const gsi::ClassBase *cls = c.gsi_cls (); if (cls) { - void *obj = const_cast (c.to_user ()); - return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + if (! c.user_is_ref () && cls->is_managed ()) { + void *obj = c.user_unshare (); + return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), true, c.user_is_const (), false, false); + } else { + void *obj = const_cast (c.to_user ()); + return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + } } else { // not a known type -> return nil Py_RETURN_NONE; diff --git a/src/rba/rba/rbaConvert.cc b/src/rba/rba/rbaConvert.cc index b803b6450..2a05a69cf 100644 --- a/src/rba/rba/rbaConvert.cc +++ b/src/rba/rba/rbaConvert.cc @@ -287,8 +287,13 @@ VALUE c2ruby (const tl::Variant &c) } else if (c.is_user ()) { const gsi::ClassBase *cls = c.gsi_cls (); if (cls) { - void *obj = const_cast (c.to_user ()); - return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + if (! c.user_is_ref () && cls->is_managed ()) { + void *obj = c.user_unshare (); + return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), true, c.user_is_const (), false, false); + } else { + void *obj = const_cast (c.to_user ()); + return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false); + } } else { // not a known type -> return nil return Qnil; diff --git a/src/tl/tl/tlObject.cc b/src/tl/tl/tlObject.cc index b28b0958a..fd4f6aaf4 100644 --- a/src/tl/tl/tlObject.cc +++ b/src/tl/tl/tlObject.cc @@ -227,6 +227,11 @@ const Object *WeakOrSharedPtr::get () const return mp_t; } +void WeakOrSharedPtr::unshare () +{ + m_is_shared = false; +} + void WeakOrSharedPtr::reset_object () { tl::MutexLocker locker (&lock ()); diff --git a/src/tl/tl/tlObject.h b/src/tl/tl/tlObject.h index 9bea96ed4..0157c6887 100644 --- a/src/tl/tl/tlObject.h +++ b/src/tl/tl/tlObject.h @@ -197,6 +197,12 @@ public: */ void detach_from_all_events (); + /** + * @brief Unshares the object + * This will turn a shared reference into a weak one. + */ + void unshare (); + /** * @brief Indicates that this object is an event * This property is intended for internal use only. @@ -401,6 +407,9 @@ public: { // .. nothing yet .. } + +private: + using weak_or_shared_ptr::unshare; }; /** @@ -429,6 +438,9 @@ public: { // .. nothing yet .. } + +private: + using weak_or_shared_ptr::unshare; }; } diff --git a/src/tl/tl/tlVariant.cc b/src/tl/tl/tlVariant.cc index 28d476282..1811606fb 100644 --- a/src/tl/tl/tlVariant.cc +++ b/src/tl/tl/tlVariant.cc @@ -2709,6 +2709,21 @@ void *Variant::user_take () return obj; } +void *Variant::user_unshare () const +{ + tl_assert (is_user () && ! user_is_ref ()); + + if (m_type == t_user) { + Variant *nc_this = const_cast (this); + nc_this->m_var.mp_user.shared = false; + } else if (m_type == t_user_ref) { + tl::WeakOrSharedPtr *wptr = const_cast (reinterpret_cast (m_var.mp_user_ref.ptr)); + wptr->unshare (); + } + + return const_cast (to_user ()); +} + void Variant::user_assign (const tl::Variant &other) { tl_assert (is_user ()); diff --git a/src/tl/tl/tlVariant.h b/src/tl/tl/tlVariant.h index 4b12a892a..762a49eb9 100644 --- a/src/tl/tl/tlVariant.h +++ b/src/tl/tl/tlVariant.h @@ -891,6 +891,13 @@ public: */ void *user_take (); + /** + * @brief Takes the user object and releases ownership by the variant + * This method is const as it does not change the value, but the ownership of + * the contained object. The object must not be "user_is_ref". + */ + void *user_unshare () const; + /** * @brief Assigns the object stored in other to self * From c3fdc6e1bcc42f3e1e6dfe55dc7c245c180ffce2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 14:00:05 +0200 Subject: [PATCH 18/29] Implemented a solution for issue #1790 (Support for recursive PCell instances) This also fixes some other issues, like "display_text_impl" being called when a PCell is run with the debugger open. --- .../pcell_declaration_helper.lym | 90 ++++++++++++++----- .../klayout/db/pcell_declaration_helper.py | 35 +++++++- testdata/python/dbPCells.py | 79 +++++++++++++++- testdata/ruby/dbPCells.rb | 89 ++++++++++++++++++ 4 files changed, 263 insertions(+), 30 deletions(-) diff --git a/src/db/db/built-in-macros/pcell_declaration_helper.lym b/src/db/db/built-in-macros/pcell_declaration_helper.lym index a380b77f7..3561cae82 100644 --- a/src/db/db/built-in-macros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-macros/pcell_declaration_helper.lym @@ -272,6 +272,11 @@ module RBA # of a PCell class PCellDeclarationHelper < PCellDeclaration + # makes PCellDeclaration's "layout" method available + if ! self.method_defined?(:_layout_base) + alias_method :_layout_base, :layout + end + # import the Type... constants from PCellParameterDeclaration PCellParameterDeclaration.constants.each do |c| if !const_defined?(c) @@ -290,18 +295,58 @@ module RBA @cell = nil @layer_param_index = [] @layers = nil + @state_stack = [] end # provide accessors for the current layout and cell (for prod) - attr_reader :layout, :cell, :shape, :layer + attr_reader :cell, :shape, :layer + + def layout + @layout || _layout_base() + end # provide fallback accessors in case of a name clash with a # parameter def _layer; @layer; end - def _layout; @layout; end + def _layout; @layout || _layout_base(); end def _cell; @cell; end def _shape; @shape; end + # Starts an operation - pushes the state on the state stack + + def start + @state_stack << [ @param_values, @param_states, @layers, @cell, @layout, @layer, @shape ] + self._reset_state + end + + # Finishes an operation - pops the state from the state stack + + def finish + if ! @state_stack.empty? + @param_values, @param_states, @layers, @cell, @layout, @layer, @shape = @state_stack.pop + else + self._reset_state + end + end + + # Resets the state to default values + + def _reset_state + + @param_values = nil + @param_states = nil + @layers = nil + @layout = nil + + # This should be here: + # @cell = nil + # @layer = nil + # @shape = nil + # but this would break backward compatibility of "display_text" (actually + # exploiting this bug) - fix this in the next major release. + + end + # A helper method to access the nth parameter def _get_param(nth, name) @@ -410,11 +455,13 @@ module RBA # implementation of display_text def display_text(parameters) + self.start @param_values = parameters + text = "" begin text = display_text_impl ensure - @param_values = nil + self.finish end text end @@ -431,34 +478,33 @@ module RBA # coerce parameters (make consistent) def coerce_parameters(layout, parameters) + self.start @param_values = parameters @layout = layout - ret = parameters begin coerce_parameters_impl ensure - @layout = nil ret = @param_values - @param_values = nil + self.finish end ret end # parameter change callback def callback(layout, name, states) - @param_values = nil + self.start @param_states = states @layout = layout begin callback_impl(name) ensure - @param_states = nil - @layout = nil + self.finish end end # produce the layout def produce(layout, layers, parameters, cell) + self.start @layers = layers @cell = cell @param_values = parameters @@ -466,15 +512,13 @@ module RBA begin produce_impl ensure - @layers = nil - @cell = nil - @param_values = nil - @layout = nil + self.finish end end # produce a helper for can_create_from_shape def can_create_from_shape(layout, shape, layer) + self.start ret = false @layout = layout @shape = shape @@ -482,24 +526,22 @@ module RBA begin ret = can_create_from_shape_impl ensure - @layout = nil - @shape = nil - @layer = nil + self.finish end ret end - # produce a helper for parameters_from_shape + # produce a helper for transformation_from_shape def transformation_from_shape(layout, shape, layer) + self.start @layout = layout @shape = shape @layer = layer + t = nil begin t = transformation_from_shape_impl ensure - @layout = nil - @shape = nil - @layer = nil + self.finish end t end @@ -507,6 +549,7 @@ module RBA # produce a helper for parameters_from_shape # with this helper, the implementation can use the parameter setters def parameters_from_shape(layout, shape, layer) + self.start @param_values = @param_decls.map { |pd| pd.default } @layout = layout @shape = shape @@ -514,11 +557,10 @@ module RBA begin parameters_from_shape_impl ensure - @layout = nil - @shape = nil - @layer = nil + ret = @param_values + self.finish end - @param_values + ret end # default implementation diff --git a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py index ab432550d..3667d3f63 100644 --- a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py +++ b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py @@ -60,6 +60,7 @@ class _PCellDeclarationHelperMixin: self._param_states = None self._layer_param_index = [] self._layers = [] + self._state_stack = [] # public attributes self.layout = None self.shape = None @@ -129,6 +130,7 @@ class _PCellDeclarationHelperMixin: This function delegates the implementation to self.display_text_impl after configuring the PCellDeclaration object. """ + self.start() self._param_values = parameters try: text = self.display_text_impl() @@ -165,6 +167,7 @@ class _PCellDeclarationHelperMixin: "layers" are the layer indexes corresponding to the layer parameters. """ + self.start() self._param_values = None self._param_states = None if states: @@ -177,17 +180,39 @@ class _PCellDeclarationHelperMixin: self._param_values = values self._layers = layers + def start(self): + """ + Is called to prepare the environment for an operation + After the operation, "finish" must be called. + This method will push the state onto a stack, hence implementing + reentrant implementation methods. + """ + self._state_stack.append( (self._param_values, self._param_states, self._layers, self.cell, self.layout, self.layer, self.shape) ) + self._reset_state() + def finish(self): """ Is called at the end of an implementation of a PCellDeclaration method """ + if len(self._state_stack) > 0: + self._param_values, self._param_states, self._layers, self.cell, self.layout, self.layer, self.shape = self._state_stack.pop() + else: + self._reset_state() + + def _reset_state(self): + """ + Resets the internal state + """ self._param_values = None self._param_states = None self._layers = None - self._cell = None - self._layout = None - self._layer = None - self._shape = None + self.layout = super(_PCellDeclarationHelperMixin, self).layout() + # This should be here: + # self.cell = None + # self.layer = None + # self.shape = None + # but this would break backward compatibility of "display_text" (actually + # exploiting this bug) - fix this in the next major release. def get_layers(self, parameters): """ @@ -255,6 +280,7 @@ class _PCellDeclarationHelperMixin: The function delegates the implementation to can_create_from_shape_impl after updating the state of this object with the current parameters. """ + self.start() self.layout = layout self.shape = shape self.layer = layer @@ -271,6 +297,7 @@ class _PCellDeclarationHelperMixin: The function delegates the implementation to transformation_from_shape_impl after updating the state of this object with the current parameters. """ + self.start() self.layout = layout self.shape = shape self.layer = layer diff --git a/testdata/python/dbPCells.py b/testdata/python/dbPCells.py index a68880477..fcfb1e4dc 100644 --- a/testdata/python/dbPCells.py +++ b/testdata/python/dbPCells.py @@ -18,6 +18,7 @@ import pya import unittest +import math import sys class BoxPCell(pya.PCellDeclaration): @@ -77,7 +78,7 @@ class PCellTestLib(pya.Library): sb_cell = self.layout().cell(sb_index) sb_cell.shapes(l10).insert(pya.Box(0, 0, 100, 200)) - # register us with the name "MyLib" + # register us with the name "PCellTestLib" self.register("PCellTestLib") @@ -135,9 +136,62 @@ if "PCellDeclarationHelper" in pya.__dict__: # create the PCell declarations self.layout().register_pcell("Box2", BoxPCell2()) - # register us with the name "MyLib" + # register us with the name "PCellTestLib2" self.register("PCellTestLib2") + # A recursive PCell + + class RecursivePCell(pya.PCellDeclarationHelper): + + def __init__(self): + + super(RecursivePCell, self).__init__() + + self.param("layer", self.TypeLayer, "Layer", default = pya.LayerInfo(0, 0)) + self.param("line", self.TypeShape, "Line", default = pya.Edge(0, 0, 10000, 0)) + self.param("level", self.TypeInt, "Level", default = 1) + + def display_text_impl(self): + # provide a descriptive text for the cell + return "RecursivePCell(L=" + str(self.layer) + ",E=" + str(pya.CplxTrans(self.layout.dbu) * self.line) + ",LVL=" + str(self.level) + + def produce_impl(self): + + # fetch the parameters + l = self.layer_layer + e = self.line + + if self.level <= 0: + self.cell.shapes(l).insert(e) + return + + d3 = e.d() * (1.0 / 3.0) + d3n = pya.Vector(-d3.y, d3.x) + + e1 = pya.Edge(e.p1, e.p1 + d3) + e2 = pya.Edge(e1.p2, e1.p2 + d3 * 0.5 + d3n * math.cos(math.pi / 6)) + e3 = pya.Edge(e2.p2, e.p1 + d3 * 2.0) + e4 = pya.Edge(e3.p2, e.p2) + + for e in [ e1, e2, e3, e4 ]: + t = pya.Trans(e.p1 - pya.Point()) + cc = self.layout.create_cell("RecursivePCell", { "layer": self.layer, "line": t.inverted() * e, "level": self.level - 1 }) + self.cell.insert(pya.CellInstArray(cc, t)) + + class PCellTestLib3(pya.Library): + + def __init__(self): + + # set the description + self.description = "PCell test lib3" + + # create the PCell declarations + self.layout().register_pcell("RecursivePCell", RecursivePCell()) + + # register us with the name "PCellTestLib3" + self.register("PCellTestLib3") + + def inspect_LayerInfo(self): return "<" + str(self) + ">" @@ -501,6 +555,27 @@ class DBPCellTests(unittest.TestCase): self.assertEqual(cell.begin_shapes_rec(ly.layer(5, 0)).shape().__str__(), "box (-100,-300;100,300)") + def test_9(self): + + if not "PCellDeclarationHelper" in pya.__dict__: + return + + # instantiate and register the library + tl = PCellTestLib3() + + ly = pya.Layout(True) + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 == None, True) + + c1 = ly.create_cell("c1") + + c2 = ly.create_cell("RecursivePCell", "PCellTestLib3", { "layer": pya.LayerInfo(1, 0), "level": 4, "line": pya.Edge(0, 0, 20000, 0) }) + c1.insert(pya.CellInstArray(c2.cell_index(), pya.Trans())) + + self.assertEqual(c2.display_title(), "PCellTestLib3.RecursivePCell(L=1/0,E=(0,0;20,0),LVL=4") + self.assertEqual(str(c1.dbbox()), "(0,0;20,5.774)") + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(DBPCellTests) diff --git a/testdata/ruby/dbPCells.rb b/testdata/ruby/dbPCells.rb index afbc7f2f2..36fa4d5ce 100644 --- a/testdata/ruby/dbPCells.rb +++ b/testdata/ruby/dbPCells.rb @@ -170,6 +170,71 @@ if RBA.constants.member?(:PCellDeclarationHelper) end + # A recursive PCell + + class RecursivePCell < RBA::PCellDeclarationHelper + + def initialize + + super() + + param("layer", RecursivePCell::TypeLayer, "Layer", :default => RBA::LayerInfo::new(0, 0)) + param("line", RecursivePCell::TypeShape, "Line", :default => RBA::Edge::new(0, 0, 10000, 0)) + param("level", RecursivePCell::TypeInt, "Level", :default => 1) + + end + + def display_text_impl + # provide a descriptive text for the cell + return "RecursivePCell(L=" + self.layer.to_s + ",E=" + (RBA::CplxTrans::new(self.layout.dbu) * self.line).to_s + ",LVL=" + self.level.to_s + end + + def produce_impl + + # fetch the parameters + l = self.layer_layer + e = self.line + + if self.level <= 0 + self.cell.shapes(l).insert(e) + return + end + + d3 = e.d * (1.0 / 3.0) + d3n = RBA::Vector::new(-d3.y, d3.x) + + e1 = RBA::Edge::new(e.p1, e.p1 + d3) + e2 = RBA::Edge::new(e1.p2, e1.p2 + d3 * 0.5 + d3n * Math::cos(Math::PI / 6)) + e3 = RBA::Edge::new(e2.p2, e.p1 + d3 * 2.0) + e4 = RBA::Edge::new(e3.p2, e.p2) + + [ e1, e2, e3, e4 ].each do |e| + t = RBA::Trans::new(e.p1 - RBA::Point::new) + cc = self.layout.create_cell("RecursivePCell", { "layer" => self.layer, "line" => t.inverted * e, "level" => self.level - 1 }) + self.cell.insert(RBA::CellInstArray::new(cc, t)) + end + + end + + end + + class PCellTestLib3 < RBA::Library + + def initialize + + # set the description + self.description = "PCell test lib3" + + # create the PCell declarations + self.layout().register_pcell("RecursivePCell", RecursivePCell::new) + + # register us with the name "PCellTestLib3" + self.register("PCellTestLib3") + + end + + end + end # A helper for testing: provide an inspect method @@ -809,6 +874,30 @@ class DBPCell_TestClass < TestBase end + def test_12 + + if !RBA.constants.member?(:PCellDeclarationHelper) + return + end + + # instantiate and register the library + tl = PCellTestLib3::new + + ly = RBA::Layout::new + + li1 = ly.find_layer("1/0") + assert_equal(li1 == nil, true) + + c1 = ly.create_cell("c1") + + c2 = ly.create_cell("RecursivePCell", "PCellTestLib3", { "layer" => RBA::LayerInfo::new(1, 0), "level" => 4, "line" => RBA::Edge::new(0, 0, 20000, 0) }) + c1.insert(RBA::CellInstArray::new(c2.cell_index(), RBA::Trans::new)) + + assert_equal(c2.display_title, "PCellTestLib3.RecursivePCell(L=1/0,E=(0,0;20,0),LVL=4") + assert_equal(c1.dbbox.to_s, "(0,0;20,5.774)") + + end + end load("test_epilogue.rb") From 071326ffee7f53869f7ecde97787615ed33d443b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 14:18:42 +0200 Subject: [PATCH 19/29] Small doc update --- src/db/db/gsiDeclDbRegion.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 886a52ff6..04cae2c97 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -2951,6 +2951,8 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " "a minimum angle of abouth 37 degree.\n" "\n" + "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" + "\n" "The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will " "make the implementation skip the refinement step. In that case, the results are identical to " "the standard constrained Delaunay triangulation.\n" From b253eaa51af2be8e5419837276feab3e3b6ad1b2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 14:29:14 +0200 Subject: [PATCH 20/29] Including more GeneratorExit and StopAsyncIteration in the ignored exceptions for Python --- src/pya/pya/pya.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pya/pya/pya.cc b/src/pya/pya/pya.cc index 89ae80c54..5f177fa56 100644 --- a/src/pya/pya/pya.cc +++ b/src/pya/pya/pya.cc @@ -684,7 +684,11 @@ PythonInterpreter::trace_func (PyFrameObject *frame, int event, PyObject *arg) exc_value = PythonPtr (PyTuple_GetItem (arg, 1)); } - if (exc_type && exc_type.get () != PyExc_StopIteration) { +#if PY_VERSION_HEX >= 0x03050000 + if (exc_type && exc_type.get () != PyExc_StopIteration && exc_type.get () != PyExc_GeneratorExit && exc_type.get () != PyExc_StopAsyncIteration) { +#else + if (exc_type && exc_type.get () != PyExc_StopIteration && exc_type.get () != PyExc_GeneratorExit) { +#endif // If the next exception shall be ignored, do so if (m_ignore_next_exception) { From fb83b9fbc9df49c37c6eab56a77a6c9031df24dd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 17:18:29 +0200 Subject: [PATCH 21/29] Trying to improve "grab" behavior of partial selection Problem was: with a partial selection, clicking in the vicinity of a non-selected edge could make this edge the selected one. So it was not possible to drag the selection in some cases. With "move mode" it was. The difference was in the detection logic that decides where a click is on the selection or outside. Now, the logic is aligned, and partial mode clicks will check whether the mouse pointer is inside the selection bbox (plus some margin). In that case, the click applies to the current selection. --- src/edt/edt/edtPartialService.cc | 69 ++++++++++++++------------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index b1861d969..f6973af78 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -1182,6 +1182,19 @@ PartialService::timeout () mp_view->clear_transient_selection (); clear_mouse_cursors (); + double le = catch_distance () * 3.0; // see Editables::selection_catch_bbox() + db::DBox sel_catch_box = selection_bbox ().enlarged (db::DVector (le, le)); + if (has_selection () && sel_catch_box.contains (m_hover_point)) { + + // no transient selection if inside current selection - if we click there, we catch the + // currently selected objects + resize_markers (0, true); + resize_inst_markers (0, true); + + return; + + } + // compute search box double l = catch_distance (); db::DBox search_box = db::DBox (m_hover_point, m_hover_point).enlarged (db::DVector (l, l)); @@ -2035,45 +2048,13 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo // select is allowed to throw an exception try { - // compute search box - double l = catch_distance (); - db::DBox search_box = db::DBox (p, p).enlarged (db::DVector (l, l)); - - // check, if there is a selected shape under the mouse - in this case, we do not do a new selection - PartialShapeFinder finder (true /*point mode*/, m_top_level_sel, db::ShapeIterator::All); - finder.find (view (), search_box); - - // check, if there is a selected shape under the mouse - in this case, we do not do a new selection - lay::InstFinder inst_finder (true /*point mode*/, m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no excludes*/, true /*visible layers*/); - inst_finder.find (view (), search_box); - - // collect the founds from the finder - // consider a new selection if new objects are selected or the current selection is shape-only - // (this may happen if points have been inserted) - bool new_selection = ((finder.begin () == finder.end () && inst_finder.begin () == inst_finder.end ()) - || mode != lay::Editable::Replace); - - for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end () && ! new_selection; ++f) { - partial_objects::const_iterator sel = m_selection.find (f->first); - new_selection = true; - if (sel != m_selection.end ()) { - for (std::vector::const_iterator e = f->second.begin (); e != f->second.end () && new_selection; ++e) { - if (sel->second.find (*e) != sel->second.end ()) { - new_selection = false; - } - } - } - } - - if (finder.begin () == finder.end ()) { - - for (lay::InstFinder::iterator f = inst_finder.begin (); f != inst_finder.end () && ! new_selection; ++f) { - partial_objects::const_iterator sel = m_selection.find (*f); - if (sel == m_selection.end ()) { - new_selection = true; - } - } + bool new_selection = true; + double le = catch_distance () * 3.0; // see Editables::selection_catch_bbox() + db::DBox sel_catch_box = selection_bbox ().enlarged (db::DVector (le, le)); + if (mode == lay::Editable::Replace && has_selection () && sel_catch_box.contains (p)) { + // drag current selection + new_selection = false; } if (new_selection) { @@ -2082,6 +2063,18 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo m_selection.clear (); } + // compute search box + double l = catch_distance (); + db::DBox search_box = db::DBox (p, p).enlarged (db::DVector (l, l)); + + // identify the edges under the mouse + PartialShapeFinder finder (true /*point mode*/, m_top_level_sel, db::ShapeIterator::All); + finder.find (view (), search_box); + + // identify the instances under the mouse + lay::InstFinder inst_finder (true /*point mode*/, m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no excludes*/, true /*visible layers*/); + inst_finder.find (view (), search_box); + // clear the selection if we now select a guiding shape or if it was consisting of a guiding shape before // (that way we ensure there is only a guiding shape selected) PartialShapeFinder::iterator f0 = finder.begin (); From fec61438d45cf03f4eca202127a7a1f0c5ee58b5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 17:26:23 +0200 Subject: [PATCH 22/29] Small bugfix: 'Delete selected stacks' was not working from stack tech component editor's context menu --- .../net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc index 51f8ea1ab..ee3885303 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -140,7 +140,7 @@ NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) connect (action, SIGNAL (triggered ()), this, SLOT (add_clicked ())); stack_tree->addAction (action); action = new QAction (QObject::tr ("Delete Selected Stacks"), this); - connect (action, SIGNAL (triggered ()), this, SLOT (delete_clicked ())); + connect (action, SIGNAL (triggered ()), this, SLOT (del_clicked ())); stack_tree->addAction (action); action = new QAction (QObject::tr ("Duplicate Stack"), this); connect (action, SIGNAL (triggered ()), this, SLOT (clone_clicked ())); From 258aaad29d0e49465cf2e4cc4dd75481becac633 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 20:10:30 +0200 Subject: [PATCH 23/29] Fixed issue #1804 (Instantation path highlight mismatch with view window) In addition, the highlighting was fixed in the presence of a context path (aka "edit in place"). --- src/db/db/dbInstElement.h | 6 +++++- src/edt/edt/edtDialogs.cc | 45 ++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/db/db/dbInstElement.h b/src/db/db/dbInstElement.h index b709b13a4..ac85787de 100644 --- a/src/db/db/dbInstElement.h +++ b/src/db/db/dbInstElement.h @@ -148,7 +148,11 @@ struct DB_PUBLIC InstElement */ db::ICplxTrans complex_trans () const { - return inst_ptr.cell_inst ().complex_trans (*array_inst); + if (array_inst.at_end ()) { + return inst_ptr.cell_inst ().complex_trans (); + } else { + return inst_ptr.cell_inst ().complex_trans (*array_inst); + } } /** diff --git a/src/edt/edt/edtDialogs.cc b/src/edt/edt/edtDialogs.cc index 5519eba14..238e51c70 100644 --- a/src/edt/edt/edtDialogs.cc +++ b/src/edt/edt/edtDialogs.cc @@ -26,6 +26,7 @@ #include "dbLayout.h" #include "edtDialogs.h" +#include "edtUtils.h" #include "layObjectInstPath.h" #include "layCellView.h" #include "layLayoutViewBase.h" @@ -79,29 +80,39 @@ InstantiationForm::double_clicked (QListWidgetItem *item) return; } - lay::CellView::unspecific_cell_path_type path (mp_view->cellview (mp_path->cv_index ()).combined_unspecific_path ()); + int cv_index = mp_path->cv_index (); + const lay::CellView &cv = mp_view->cellview (cv_index); + + lay::CellView::unspecific_cell_path_type path (cv.combined_unspecific_path ()); int nrow = 0; + + for (lay::CellView::specific_cell_path_type::const_iterator p = cv.specific_path ().begin (); p != cv.specific_path ().end () && nrow < row; ++p, ++nrow) { + path.push_back (p->inst_ptr.cell_index ()); + } for (lay::ObjectInstPath::iterator p = mp_path->begin (); p != mp_path->end () && nrow < row; ++p, ++nrow) { path.push_back (p->inst_ptr.cell_index ()); } - mp_view->set_current_cell_path (mp_path->cv_index (), path); + mp_view->set_current_cell_path (cv_index, path); if (! mp_marker) { - mp_marker = new lay::Marker (mp_view, mp_path->cv_index ()); + mp_marker = new lay::Marker (mp_view, cv_index); } - const db::Layout &layout = mp_view->cellview (mp_path->cv_index ())->layout (); - db::Box box = layout.cell (row == 0 ? mp_path->topcell () : path.back ()).bbox (); + const db::Layout &layout = cv->layout (); + db::Box box = layout.cell (row == 0 ? cv.ctx_cell_index (): path.back ()).bbox (); // TODO: this does not consider global transformation and variants of this db::ICplxTrans abs_trans; nrow = 0; + for (lay::CellView::specific_cell_path_type::const_iterator p = cv.specific_path ().begin (); p != cv.specific_path ().end () && nrow < row; ++p, ++nrow) { + abs_trans = abs_trans * p->complex_trans (); + } for (lay::ObjectInstPath::iterator p = mp_path->begin (); p != mp_path->end () && nrow < row; ++p, ++nrow) { - abs_trans = abs_trans * (p->inst_ptr.cell_inst ().complex_trans (*(p->array_inst))); + abs_trans = abs_trans * p->complex_trans (); } - mp_marker->set (box, abs_trans, mp_view->cv_transform_variants (mp_path->cv_index ())); + mp_marker->set (box, abs_trans, mp_view->cv_transform_variants (cv_index)); } void @@ -136,14 +147,15 @@ InstantiationForm::update () list->clear (); list->addItem (tl::to_qstring (cv->layout ().cell_name (cv.ctx_cell_index ()))); - db::CplxTrans abs_trans; + db::ICplxTrans abs_trans; // first include the context path of the cellview in order to tell the path within the cell shown for (lay::CellView::specific_cell_path_type::const_iterator p = cv.specific_path ().begin (); p != cv.specific_path ().end (); ++p) { // build the instance information from the path - db::CplxTrans trans (p->inst_ptr.cell_inst ().complex_trans (*(p->array_inst))); + db::ICplxTrans trans = p->complex_trans (); + size_t n = p->array_inst.at_end () ? p->inst_ptr.cell_inst ().size () : size_t (1); abs_trans = abs_trans * trans; if (abs_coord) { @@ -152,8 +164,11 @@ InstantiationForm::update () std::string line; line += cv->layout ().cell_name (p->inst_ptr.cell_index ()); - line += "\tat "; + line += "\t" + tl::to_string (tr ("at")) + " "; line += trans.to_string (true /*lazy*/, dbu_coord ? 0.0 : dbu); + if (n > 1) { + line += " " + tl::sprintf (tl::to_string (tr ("(first of %lu array members)")), (unsigned long) n); + } list->addItem (tl::to_qstring (line)); @@ -164,7 +179,8 @@ InstantiationForm::update () // build the instance information from the path - db::CplxTrans trans (p->inst_ptr.cell_inst ().complex_trans (*(p->array_inst))); + db::ICplxTrans trans = p->complex_trans (); + size_t n = p->array_inst.at_end () ? p->inst_ptr.cell_inst ().size () : size_t (1); abs_trans = abs_trans * trans; if (abs_coord) { @@ -173,9 +189,12 @@ InstantiationForm::update () std::string line; line += cv->layout ().cell_name (p->inst_ptr.cell_index ()); - line += "\tat "; + line += "\t" + tl::to_string (tr ("at")) + " "; line += trans.to_string (true /*lazy*/, dbu_coord ? 0.0 : dbu); - + if (n > 1) { + line += " " + tl::sprintf (tl::to_string (tr ("(first of %lu array members)")), (unsigned long) n); + } + list->addItem (tl::to_qstring (line)); } From 7caf7c89d36a1f0d2f5c0a5dd289daeccf5e3f43 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 20:35:20 +0200 Subject: [PATCH 24/29] Fixed issue #1768 (Change 'angle constraint' of box and ellipse ruler templates to 'any angle' by default) --- src/ant/ant/antPlugin.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index 06680c2a8..99ff10592 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -63,16 +63,16 @@ static std::vector make_standard_templates () templates.push_back (ant::Template (tl::to_string (tr ("Measure edge")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure_edge")); templates.back ().set_mode (ant::Template::RulerAutoMetricEdge); - templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "", "", "$(sprintf('%.5g',G))°", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle")); + templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "", "", "$(sprintf('%.5g',G))°", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Any, "_angle")); templates.back ().set_mode (ant::Template::RulerThreeClicks); - templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "", "", "R=$D", ant::Object::STY_arrow_end, ant::Object::OL_radius, true, lay::AC_Global, "_radius")); + templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "", "", "R=$D", ant::Object::STY_arrow_end, ant::Object::OL_radius, true, lay::AC_Any, "_radius")); templates.back ().set_mode (ant::Template::RulerThreeClicks); templates.back ().set_main_position (ant::Object::POS_center); - templates.push_back (ant::Template (tl::to_string (tr ("Ellipse")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_ellipse, true, lay::AC_Global, std::string ())); + templates.push_back (ant::Template (tl::to_string (tr ("Ellipse")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_ellipse, true, lay::AC_Any, std::string ())); - templates.push_back (ant::Template (tl::to_string (tr ("Box")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_box, true, lay::AC_Global, std::string ())); + templates.push_back (ant::Template (tl::to_string (tr ("Box")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_box, true, lay::AC_Any, std::string ())); return templates; } From 5b9cb95e68529eb5b3fa9f13366a9ed4f3effd9c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 21:10:05 +0200 Subject: [PATCH 25/29] Fixed issue #1779 (make the state of the "By Cell" or "By Category" tree persistent) --- src/layui/layui/rdbMarkerBrowser.cc | 2 + src/layui/layui/rdbMarkerBrowserDialog.cc | 11 +++- src/layui/layui/rdbMarkerBrowserPage.cc | 78 ++++++++++++++++++++++- src/layui/layui/rdbMarkerBrowserPage.h | 10 +++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/layui/layui/rdbMarkerBrowser.cc b/src/layui/layui/rdbMarkerBrowser.cc index f3d7e24e9..bb3ff132b 100644 --- a/src/layui/layui/rdbMarkerBrowser.cc +++ b/src/layui/layui/rdbMarkerBrowser.cc @@ -46,6 +46,7 @@ std::string cfg_rdb_show_all ("rdb-show-all"); std::string cfg_rdb_list_shapes ("rdb-list-shapes"); std::string cfg_rdb_window_state ("rdb-window-state-v2"); // v2: 0.24++ std::string cfg_rdb_window_mode ("rdb-window-mode"); +std::string cfg_rdb_tree_state ("rdb-tree-state"); std::string cfg_rdb_window_dim ("rdb-window-dim"); std::string cfg_rdb_max_marker_count ("rdb-max-marker-count"); std::string cfg_rdb_marker_color ("rdb-marker-color"); @@ -296,6 +297,7 @@ public: options.push_back (std::pair (cfg_rdb_window_mode, "fit-marker")); options.push_back (std::pair (cfg_rdb_window_state, "")); options.push_back (std::pair (cfg_rdb_window_dim, "1.0")); + options.push_back (std::pair (cfg_rdb_tree_state, "")); options.push_back (std::pair (cfg_rdb_max_marker_count, "1000")); options.push_back (std::pair (cfg_rdb_marker_color, lay::ColorConverter ().to_string (QColor ()))); options.push_back (std::pair (cfg_rdb_marker_line_width, "-1")); diff --git a/src/layui/layui/rdbMarkerBrowserDialog.cc b/src/layui/layui/rdbMarkerBrowserDialog.cc index 9f4b3d410..56d32c2cf 100644 --- a/src/layui/layui/rdbMarkerBrowserDialog.cc +++ b/src/layui/layui/rdbMarkerBrowserDialog.cc @@ -55,6 +55,7 @@ extern std::string cfg_rdb_list_shapes; extern std::string cfg_rdb_window_state; extern std::string cfg_rdb_window_mode; extern std::string cfg_rdb_window_dim; +extern std::string cfg_rdb_tree_state; extern std::string cfg_rdb_max_marker_count; extern std::string cfg_rdb_marker_color; extern std::string cfg_rdb_marker_line_width; @@ -847,6 +848,10 @@ MarkerBrowserDialog::update_content () mp_ui->browser_frame->set_view (view (), m_cv_index); mp_ui->browser_frame->enable_updates (true); + std::string tree_state; + view ()->config_get (cfg_rdb_tree_state, tree_state); + mp_ui->browser_frame->set_tree_state (tree_state); + if (rdb) { // Note: it appears to be required to show the browser page after it has been configured. // Otherwise the header gets messed up and the configuration is reset. @@ -872,7 +877,11 @@ void MarkerBrowserDialog::deactivated () { if (lay::Dispatcher::instance ()) { - lay::Dispatcher::instance ()->config_set (cfg_rdb_window_state, lay::save_dialog_state (this).c_str ()); + lay::Dispatcher::instance ()->config_set (cfg_rdb_window_state, lay::save_dialog_state (this)); + std::string tree_state = mp_ui->browser_frame->get_tree_state (); + if (! tree_state.empty ()) { + lay::Dispatcher::instance ()->config_set (cfg_rdb_tree_state, tree_state); + } } mp_ui->browser_frame->set_rdb (0); diff --git a/src/layui/layui/rdbMarkerBrowserPage.cc b/src/layui/layui/rdbMarkerBrowserPage.cc index 7d9625053..c798cd7b7 100644 --- a/src/layui/layui/rdbMarkerBrowserPage.cc +++ b/src/layui/layui/rdbMarkerBrowserPage.cc @@ -1953,7 +1953,83 @@ MarkerBrowserPage::set_rdb (rdb::Database *database) } } -void +static std::string top_item_by_index (int i) +{ + if (i == 0) { + return std::string ("by-cell"); + } else if (i == 1) { + return std::string ("by-category"); + } else { + return std::string (); + } +} + +static int top_index_from_item (const std::string &s) +{ + if (s == "by-cell") { + return 0; + } else if (s == "by-category") { + return 1; + } else { + return -1; + } +} + +std::string +MarkerBrowserPage::get_tree_state () +{ + std::string res; + + QAbstractItemModel *tree_model = directory_tree->model (); + if (! tree_model) { + return res; + } + + int rows = tree_model->rowCount (QModelIndex ()); + for (int i = 0; i < rows; ++i) { + bool expanded = directory_tree->isExpanded (tree_model->index (i, 0, QModelIndex ())); + std::string item = top_item_by_index (i); + if (! item.empty ()) { + if (! res.empty ()) { + res += ","; + } + res += expanded ? "+" : "-"; + res += item; + } + } + + return res; +} + +void +MarkerBrowserPage::set_tree_state (const std::string &state) +{ + QAbstractItemModel *tree_model = directory_tree->model (); + if (! tree_model) { + return; + } + + tl::Extractor ex (state.c_str ()); + while (! ex.at_end ()) { + bool expanded = false; + if (ex.test ("+")) { + expanded = true; + } else { + ex.test ("-"); + } + std::string item; + if (! ex.try_read_word (item, "-_")) { + break; + } + int index = top_index_from_item (item); + if (index >= 0) { + directory_tree->setExpanded (tree_model->index (index, 0, QModelIndex ()), expanded); + } + ex.test (","); + } +} + +void MarkerBrowserPage::update_content () { diff --git a/src/layui/layui/rdbMarkerBrowserPage.h b/src/layui/layui/rdbMarkerBrowserPage.h index 7a916b1f6..0558b4348 100644 --- a/src/layui/layui/rdbMarkerBrowserPage.h +++ b/src/layui/layui/rdbMarkerBrowserPage.h @@ -161,6 +161,16 @@ public: */ void enable_updates (bool f); + /** + * @brief Gets a string with the serialized tree state + */ + std::string get_tree_state (); + + /** + * @brief Restores the tree state from the serialized string (see get_tree_state) + */ + void set_tree_state (const std::string &state); + public slots: void directory_header_clicked (int section); void directory_sorting_changed (int section, Qt::SortOrder order); From 7978a2bb1da1072f7785f4950a67a3e81d7d69ee Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jul 2024 22:24:15 +0200 Subject: [PATCH 26/29] Proposal for fixing issue #1784 (Spice throws error at midline semicolon comments) --- src/db/db/dbNetlistSpiceReader.cc | 116 ++++++++++++++++++++-- src/db/db/dbNetlistSpiceReader.h | 24 +++++ src/db/db/dbNetlistSpiceReaderDelegate.cc | 93 +---------------- src/db/unit_tests/dbNetlistReaderTests.cc | 19 ++++ testdata/algo/nreader26.cir | 14 +++ 5 files changed, 169 insertions(+), 97 deletions(-) create mode 100644 testdata/algo/nreader26.cir diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 05976791d..7e8563aa8 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -54,7 +54,7 @@ read_param_card (tl::Extractor &ex, const db::Netlist *netlist, std::mapnormalize_name (n); + return netlist->normalize_name (NetlistSpiceReader::unescape_name (n)); } class SpiceCircuitDict @@ -610,7 +610,7 @@ SpiceCircuitDict::get_line () std::string path_or_libname; ex.read_word_or_quoted (path_or_libname, allowed_name_chars); - if (! ex.at_end ()) { + if (! NetlistSpiceReader::at_eol (ex)) { std::string libname; ex.read_word_or_quoted (libname, allowed_name_chars); @@ -648,7 +648,7 @@ SpiceCircuitDict::get_line () ex.expect_end (); - } else if (ex.at_end () || ex.test ("*")) { + } else if (NetlistSpiceReader::at_eol (ex)) { // skip empty and comment lines @@ -680,7 +680,7 @@ SpiceCircuitDict::read_card () } else if (ex.test_without_case (".global")) { - while (! ex.at_end ()) { + while (! NetlistSpiceReader::at_eol (ex)) { std::string n = mp_delegate->translate_net_name (read_name (ex, mp_netlist)); if (m_global_net_names.find (n) == m_global_net_names.end ()) { m_global_nets.push_back (n); @@ -766,7 +766,7 @@ SpiceCircuitDict::read_card () void SpiceCircuitDict::read_options (tl::Extractor &ex) { - while (! ex.at_end ()) { + while (! NetlistSpiceReader::at_eol (ex)) { std::string n; ex.read_word_or_quoted (n, allowed_name_chars); @@ -780,7 +780,7 @@ SpiceCircuitDict::read_options (tl::Extractor &ex) } else { // skip until end or next space ex.skip (); - while (! ex.at_end () && ! isspace (*ex)) { + while (! NetlistSpiceReader::at_eol (ex) && ! isspace (*ex)) { ++ex; } } @@ -1345,4 +1345,106 @@ void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) } } +bool NetlistSpiceReader::at_eol (tl::Extractor &ex) +{ + // terminate at end or midline comment + return ex.at_end () || ex.test ("*") || ex.test (";"); +} + +inline static int hex_num (char c) +{ + if (c >= '0' && c <= '9') { + return (int (c - '0')); + } else if (c >= 'a' && c <= 'f') { + return (int (c - 'f') + 10); + } else { + return -1; + } +} + +std::string NetlistSpiceReader::unescape_name (const std::string &n) +{ + std::string nn; + nn.reserve (n.size ()); + + char quote = 0; + + const char *cp = n.c_str (); + while (*cp) { + + if (*cp == quote) { + + quote = 0; + ++cp; + + } else if (! quote && (*cp == '"' || *cp == '\'')) { + + quote = *cp++; + + } else if (*cp == '\\' && cp[1]) { + + if (tolower (cp[1]) == 'x') { + + cp += 2; + + char c = 0; + for (int i = 0; i < 2 && *cp; ++i) { + int n = hex_num (*cp); + if (n >= 0) { + ++cp; + c = c * 16 + char (n); + } else { + break; + } + } + + nn += c; + + } else { + ++cp; + nn += *cp++; + } + + } else { + nn += *cp++; + } + + } + + return nn; +} + +std::string NetlistSpiceReader::parse_component (tl::Extractor &ex) +{ + const char *cp = ex.skip (); + const char *cp0 = cp; + + char quote = 0; + unsigned int brackets = 0; + + while (*cp) { + if (quote) { + if (*cp == quote) { + quote = 0; + } else if (*cp == '\\' && cp[1]) { + ++cp; + } + } else if ((isspace (*cp) || *cp == '=') && ! brackets) { + break; + } else if (*cp == '"' || *cp == '\'') { + quote = *cp; + } else if (*cp == '(') { + ++brackets; + } else if (*cp == ')') { + if (brackets > 0) { + --brackets; + } + } + ++cp; + } + + ex = tl::Extractor (cp); + return std::string (cp0, cp - cp0); +} + } diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index e76efff22..b973814e2 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -29,6 +29,7 @@ #include "tlStream.h" #include "tlObject.h" #include "tlVariant.h" +#include "tlString.h" #include #include @@ -63,6 +64,29 @@ public: m_strict = s; } + /** + * @brief Returns true, if the extractor is at the end of the line + * "at_eol" is true at the line end or when a midline comment starts. + */ + static bool at_eol (tl::Extractor &ex); + + /** + * @brief Unescapes a name + * Replaces backslash sequences with the true character and removes quotes. + */ + static std::string unescape_name (const std::string &n); + + /** + * @brief Parses a netlist component (net name, expression etc.) + * Scans over the expression or net name and returns a string representing the latter. + */ + static std::string parse_component (tl::Extractor &ex); + + /** + * @brief Reads a component name + * Scans over a component name and returns the + */ + private: tl::weak_ptr mp_delegate; std::unique_ptr mp_default_delegate; diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index 15196cf28..8570cb3a8 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -32,60 +32,6 @@ namespace db // ------------------------------------------------------------------------------------------------------ -inline static int hex_num (char c) -{ - if (c >= '0' && c <= '9') { - return (int (c - '0')); - } else if (c >= 'a' && c <= 'f') { - return (int (c - 'f') + 10); - } else { - return -1; - } -} - -static std::string unescape_name (const std::string &n) -{ - std::string nn; - nn.reserve (n.size ()); - - const char *cp = n.c_str (); - while (*cp) { - - if (*cp == '\\' && cp[1]) { - - if (tolower (cp[1]) == 'x') { - - cp += 2; - - char c = 0; - for (int i = 0; i < 2 && *cp; ++i) { - int n = hex_num (*cp); - if (n >= 0) { - ++cp; - c = c * 16 + char (n); - } else { - break; - } - } - - nn += c; - - } else { - ++cp; - nn += *cp++; - } - - } else { - nn += *cp++; - } - - } - - return nn; -} - -// ------------------------------------------------------------------------------------------------------ - NetlistSpiceReaderOptions::NetlistSpiceReaderOptions () { scale = 1.0; @@ -147,7 +93,7 @@ bool NetlistSpiceReaderDelegate::wants_subcircuit (const std::string & /*circuit std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &nn) { - return unescape_name (nn); + return NetlistSpiceReader::unescape_name (nn); } void NetlistSpiceReaderDelegate::error (const std::string &msg) @@ -172,45 +118,12 @@ static db::DeviceClass *make_device_class (db::Circuit *circuit, const std::stri return cls; } -static std::string parse_component (tl::Extractor &ex) -{ - const char *cp = ex.skip (); - const char *cp0 = cp; - - char quote = 0; - unsigned int brackets = 0; - - while (*cp) { - if (quote) { - if (*cp == quote) { - quote = 0; - } else if (*cp == '\\' && cp[1]) { - ++cp; - } - } else if ((isspace (*cp) || *cp == '=') && ! brackets) { - break; - } else if (*cp == '"' || *cp == '\'') { - quote = *cp; - } else if (*cp == '(') { - ++brackets; - } else if (*cp == ')') { - if (brackets > 0) { - --brackets; - } - } - ++cp; - } - - ex = tl::Extractor (cp); - return std::string (cp0, cp - cp0); -} - void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, std::vector &strings, std::map &pv, const std::map &variables) { tl::Extractor ex (s.c_str ()); bool in_params = false; - while (! ex.at_end ()) { + while (! NetlistSpiceReader::at_eol (ex)) { if (ex.test_without_case ("params:")) { @@ -235,7 +148,7 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, ex.error (tl::to_string (tr ("Invalid syntax for parameter assignment - needs keyword followed by '='"))); } - std::string comp_name = parse_component (ex); + std::string comp_name = NetlistSpiceReader::parse_component (ex); comp_name = mp_netlist ? mp_netlist->normalize_name (comp_name) : tl::to_upper_case (comp_name); // resolve variables if string type diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index 47dfbee3e..db581d75e 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -914,6 +914,25 @@ TEST(25_dismiss_top_level) ); } +TEST(26_comments) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader26.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP (A=A,B=B);\n" + " device NMOS '1' (S=A,G=B,D=A,B=B) (L=100,W=100,AS=0,AD=0,PS=0,PD=0);\n" + " device NMOS '2' (S='\\\\A',G='NET\"Q\"',D=A,B='NET[0]') (L=100,W=100,AS=0,AD=0,PS=0,PD=0);\n" + " device NMOS 'FET[1]' (S='\\\\A',G='NET\"Q\"',D=AAA,B='NET[0]') (L=100,W=1.7,AS=0,AD=0,PS=0,PD=0);\n" + "end;\n" + ); +} + TEST(100_ExpressionParser) { std::map vars; diff --git a/testdata/algo/nreader26.cir b/testdata/algo/nreader26.cir new file mode 100644 index 000000000..9fdd16c24 --- /dev/null +++ b/testdata/algo/nreader26.cir @@ -0,0 +1,14 @@ +* Test: dismiss empty top level circuit +; a comment + +.subckt top a b +m1 a b a b nmos * a comment +m2 a 'net"q"' \\a net[0] nmos * a comment +mfet\[1\] \a\x41a "net\"q\"" "\\a" net\[0\] nmos w=1.7u ; a comment +.ends + +* this triggered generation of a top level circuit +.param p1 17 ; a comment + +.end ; a comment + From b7e2b59852ca5abbcfbdd3440ef48d7577a1d34c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 29 Jul 2024 19:54:29 +0200 Subject: [PATCH 27/29] First implementation of GDS2 coordinate overflow checks. --- .../gds2/db_plugin/dbGDS2WriterBase.cc | 327 ++++++++++++------ .../gds2/unit_tests/dbGDS2WriterTests.cc | 92 +++++ 2 files changed, 320 insertions(+), 99 deletions(-) diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc index 2b49a1491..e620be9e6 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc @@ -51,27 +51,110 @@ GDS2WriterBase::GDS2WriterBase () // .. nothing yet .. } -static int safe_scale (double sf, int value) +static int32_t safe_convert_to_int32 (int32_t value) { - double i = floor (sf * value + 0.5); - if (i < double (std::numeric_limits::min ())) { - throw tl::Exception ("Scaling failed: coordinate underflow"); - } - if (i > double (std::numeric_limits::max ())) { - throw tl::Exception ("Scaling failed: coordinate overflow"); - } - return int (i); + return value; } -inline int scale (double sf, int value) +static int32_t safe_convert_to_int32 (uint32_t value) +{ + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Coordinate overflow"))); + } + return int32_t (value); +} + +static int32_t safe_convert_to_int32 (int64_t value) +{ + if (value < std::numeric_limits::min ()) { + throw tl::Exception (tl::to_string (tr ("Coordinate underflow"))); + } + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Coordinate overflow"))); + } + return int32_t (value); +} + +static int32_t safe_convert_to_int32 (uint64_t value) +{ + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Coordinate overflow"))); + } + return int32_t (value); +} + +template +static int32_t safe_scale (double sf, T value) +{ + double i = floor (sf * value + 0.5); + if (i < double (std::numeric_limits::min ())) { + throw tl::Exception (tl::to_string (tr ("Scaling failed: coordinate underflow"))); + } + if (i > double (std::numeric_limits::max ())) { + throw tl::Exception (tl::to_string (tr ("Scaling failed: coordinate overflow"))); + } + return int32_t (i); +} + +template +inline int32_t scale (double sf, T value) { if (sf == 1.0) { - return value; + return safe_convert_to_int32 (value); } else { return safe_scale (sf, value); } } +static uint16_t safe_convert_to_uint16 (int16_t value) +{ + // we accept this as GDS is not well defined here ... + return value; +} + +static uint16_t safe_convert_to_uint16 (uint16_t value) +{ + return value; +} + +static uint16_t safe_convert_to_uint16 (int32_t value) +{ + if (value < 0) { + throw tl::Exception (tl::to_string (tr ("Short unsigned integer underflow"))); + } + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); + } + return uint16_t (value); +} + +static uint16_t safe_convert_to_uint16 (uint32_t value) +{ + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); + } + return uint16_t (value); +} + +static uint16_t safe_convert_to_uint16 (int64_t value) +{ + if (value < 0) { + throw tl::Exception (tl::to_string (tr ("Short unsigned integer underflow"))); + } + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); + } + return uint16_t (value); +} + +static uint16_t safe_convert_to_uint16 (uint64_t value) +{ + if (value > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); + } + return uint16_t (value); +} + void GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, const std::vector &cells) { @@ -117,7 +200,7 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, write_record_size (6); write_record (sPROPATTR); - write_short (short (std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s))); // = user string + write_short (safe_convert_to_uint16 (std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s))); // = user string write_string_record (sPROPVALUE, *s); @@ -156,7 +239,7 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, write_record_size (6); write_record (sPROPATTR); - write_short (short (std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s))); // = user string + write_short (safe_convert_to_uint16 (std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s))); // = user string write_string_record (sPROPVALUE, *s); @@ -266,7 +349,11 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S write_time (time_data); write_time (time_data); - write_string_record (sLIBNAME, gds2_options.libname); + try { + write_string_record (sLIBNAME, gds2_options.libname); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing LIBNAME"))); + } write_record_size (4 + 8 * 2); write_record (sUNITS); @@ -276,7 +363,11 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S // layout properties if (gds2_options.write_file_properties && layout.prop_id () != 0) { - write_properties (layout, layout.prop_id ()); + 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 @@ -291,7 +382,11 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S } if (has_context) { - write_context_cell (layout, time_data, cells); + try { + write_context_cell (layout, time_data, cells); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing context cell"))); + } } // body @@ -302,88 +397,118 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S const db::Cell &cref (layout.cell (*cell)); - // don't write ghost cells unless they are not empty (any more) - // also don't write proxy cells which are not employed - if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) { + try { - // cell header + // don't write ghost cells unless they are not empty (any more) + // also don't write proxy cells which are not employed + if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) { - write_record_size (4 + 12 * 2); - write_record (sBGNSTR); - write_time (time_data); - write_time (time_data); + // cell header - write_string_record (sSTRNAME, m_cell_name_map.cell_name (*cell)); - - // cell body - - if (gds2_options.write_cell_properties && cref.prop_id () != 0) { - write_properties (layout, cref.prop_id ()); - } - - // instances - - for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { - - // write only instances to selected cells - if (options.keep_instances () || cell_set.find (inst->cell_index ()) != cell_set.end ()) { - - progress_checkpoint (); - write_inst (sf, *inst, true /*normalize*/, resolve_skew_arrays, layout, inst->prop_id ()); + write_record_size (4 + 12 * 2); + write_record (sBGNSTR); + write_time (time_data); + write_time (time_data); + try { + write_string_record (sSTRNAME, m_cell_name_map.cell_name (*cell)); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing cell name"))); } - } + // cell body - // shapes + if (gds2_options.write_cell_properties && cref.prop_id () != 0) { + try { + write_properties (layout, cref.prop_id ()); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties"))); + } + } - for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { - - if (layout.is_valid_layer (l->first)) { + // instances - int layer = l->second.layer; - int datatype = l->second.datatype; + for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { - db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts)); - while (! shape.at_end ()) { + // write only instances to selected cells + if (options.keep_instances () || cell_set.find (inst->cell_index ()) != cell_set.end ()) { progress_checkpoint (); - - if (shape->is_text ()) { - write_text (layer, datatype, sf, dbu, *shape, layout, shape->prop_id ()); - } else if (shape->is_polygon ()) { - write_polygon (layer, datatype, sf, *shape, multi_xy, max_vertex_count, layout, shape->prop_id ()); - } else if (shape->is_edge ()) { - write_edge (layer, datatype, sf, *shape, layout, shape->prop_id ()); - } else if (shape->is_edge_pair ()) { - write_edge (layer, datatype, sf, shape->edge_pair ().first (), layout, shape->prop_id ()); - write_edge (layer, datatype, sf, shape->edge_pair ().second (), layout, shape->prop_id ()); - } else if (shape->is_path ()) { - if (no_zero_length_paths && (shape->path_length () - shape->path_extensions ().first - shape->path_extensions ().second) == 0) { - // eliminate the zero-width path - db::Polygon poly; - shape->polygon (poly); - write_polygon (layer, datatype, sf, poly, multi_xy, max_vertex_count, layout, shape->prop_id (), false); - } else { - write_path (layer, datatype, sf, *shape, multi_xy, layout, shape->prop_id ()); - } - } else if (shape->is_box ()) { - write_box (layer, datatype, sf, *shape, layout, shape->prop_id ()); + try { + write_inst (sf, *inst, true /*normalize*/, resolve_skew_arrays, layout, inst->prop_id ()); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing instances"))); } - ++shape; - } } + // shapes + + for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { + + if (layout.is_valid_layer (l->first) && l->second.layer >= 0 && l->second.datatype >= 0) { + + int layer = l->second.layer; + if (layer > std::numeric_limits::max ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write layer numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); + } + int datatype = l->second.datatype; + if (datatype > std::numeric_limits::max ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write datatype numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); + } + + try { + + db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts)); + while (! shape.at_end ()) { + + progress_checkpoint (); + + if (shape->is_text ()) { + write_text (layer, datatype, sf, dbu, *shape, layout, shape->prop_id ()); + } else if (shape->is_polygon ()) { + write_polygon (layer, datatype, sf, *shape, multi_xy, max_vertex_count, layout, shape->prop_id ()); + } else if (shape->is_edge ()) { + write_edge (layer, datatype, sf, *shape, layout, shape->prop_id ()); + } else if (shape->is_edge_pair ()) { + write_edge (layer, datatype, sf, shape->edge_pair ().first (), layout, shape->prop_id ()); + write_edge (layer, datatype, sf, shape->edge_pair ().second (), layout, shape->prop_id ()); + } else if (shape->is_path ()) { + if (no_zero_length_paths && (shape->path_length () - shape->path_extensions ().first - shape->path_extensions ().second) == 0) { + // eliminate the zero-width path + db::Polygon poly; + shape->polygon (poly); + write_polygon (layer, datatype, sf, poly, multi_xy, max_vertex_count, layout, shape->prop_id (), false); + } else { + write_path (layer, datatype, sf, *shape, multi_xy, layout, shape->prop_id ()); + } + } else if (shape->is_box ()) { + write_box (layer, datatype, sf, *shape, layout, shape->prop_id ()); + } + + ++shape; + + } + + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing layer %d/%d")), layer, datatype)); + } + + } + + } + + // end of cell + + write_record_size (4); + write_record (sENDSTR); + } - // end of cell - - write_record_size (4); - write_record (sENDSTR); - + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing cell '%s'")), layout.cell_name (*cell))); } } @@ -509,8 +634,8 @@ GDS2WriterBase::write_inst (double sf, const db::Instance &instance, bool normal if (is_reg) { write_record_size (4 + 2 * 2); write_record (sCOLROW); - if (amax > 32767 || bmax > 32767) { - throw tl::Exception (tl::to_string (tr ("Cannot write array references with more than 32767 columns or rows to GDS2 streams"))); + if (amax > std::numeric_limits::max () || bmax > std::numeric_limits::max ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write array references with more than %d columns or rows to GDS2 streams")), int (std::numeric_limits::max ()))); } write_short (std::max ((unsigned long) 1, bmax)); write_short (std::max ((unsigned long) 1, amax)); @@ -522,10 +647,10 @@ GDS2WriterBase::write_inst (double sf, const db::Instance &instance, bool normal write_int (scale (sf, t.disp ().y ())); if (is_reg) { - write_int (scale (sf, t.disp ().x () + b.x () * bmax)); - write_int (scale (sf, t.disp ().y () + b.y () * bmax)); - write_int (scale (sf, t.disp ().x () + a.x () * amax)); - write_int (scale (sf, t.disp ().y () + a.y () * amax)); + write_int (scale (sf, t.disp ().x () + b.x () * (long) bmax)); + write_int (scale (sf, t.disp ().y () + b.y () * (long) bmax)); + write_int (scale (sf, t.disp ().x () + a.x () * (long) amax)); + write_int (scale (sf, t.disp ().y () + a.y () * (long) amax)); } finish (layout, prop_id); @@ -548,11 +673,11 @@ GDS2WriterBase::write_box (int layer, int datatype, double sf, const db::Shape & write_record_size (4 + 2); write_record (sLAYER); - write_short (layer); + write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); - write_short (datatype); + write_short (safe_convert_to_uint16 (datatype)); write_record_size (4 + 5 * 2 * 4); write_record (sXY); @@ -582,11 +707,11 @@ GDS2WriterBase::write_path (int layer, int datatype, double sf, const db::Shape write_record_size (4 + 2); write_record (sLAYER); - write_short (layer); + write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); - write_short (datatype); + write_short (safe_convert_to_uint16 (datatype)); short type = 0; db::Coord w = path.width (); @@ -662,11 +787,11 @@ GDS2WriterBase::write_edge (int layer, int datatype, double sf, const db::Edge & write_record_size (4 + 2); write_record (sLAYER); - write_short (layer); + write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); - write_short (datatype); + write_short (safe_convert_to_uint16 (datatype)); write_record_size (4 + 2); write_record (sPATHTYPE); @@ -696,11 +821,11 @@ GDS2WriterBase::write_text (int layer, int datatype, double sf, double dbu, cons write_record_size (4 + 2); write_record (sLAYER); - write_short (layer); + write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sTEXTTYPE); - write_short (datatype); + write_short (safe_convert_to_uint16 (datatype)); if (shape.text_halign () != db::NoHAlign || shape.text_valign () != db::NoVAlign || shape.text_font () != db::NoFont) { short ha = short (shape.text_halign () == db::NoHAlign ? db::HAlignLeft : shape.text_halign ()); @@ -781,11 +906,11 @@ GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Pol write_record_size (4 + 2); write_record (sLAYER); - write_short (layer); + write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); - write_short (datatype); + write_short (safe_convert_to_uint16 (datatype)); size_t n = polygon.vertices (); @@ -856,11 +981,11 @@ GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Sha write_record_size (4 + 2); write_record (sLAYER); - write_short (layer); + write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); - write_short (datatype); + write_short (safe_convert_to_uint16 (datatype)); db::Shape::point_iterator e (shape.begin_hull ()); while (n > 0) { @@ -911,11 +1036,11 @@ GDS2WriterBase::write_properties (const db::Layout &layout, db::properties_id_ty attr = name.to_long (); } - if (attr >= 0 && attr < 65535) { + if (attr >= 0 && attr <= std::numeric_limits::max ()) { write_record_size (6); write_record (sPROPATTR); - write_short (attr); + write_short ((int16_t) attr); write_string_record (sPROPVALUE, p->second.to_string ()); @@ -938,7 +1063,11 @@ GDS2WriterBase::finish (const db::Layout &layout, db::properties_id_type prop_id void GDS2WriterBase::write_string_record (short record, const std::string &t) { - write_record_size (4 + (int16_t (t.size () + 1) / 2) * 2); + size_t rs = 4 + ((t.size () + 1) / 2) * 2; + if (rs > std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("String max. length overflow"))); + } + write_record_size (uint16_t (rs)); write_record (record); write_string (t); } diff --git a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc index a840ef810..af6ed3fae 100644 --- a/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc +++ b/src/plugins/streamers/gds2/unit_tests/dbGDS2WriterTests.cc @@ -1312,6 +1312,96 @@ TEST(130a) EXPECT_EQ (layout_read.meta_info ("b").value.to_string (), "nil"); } +// Limits +static std::string run_test_with_error (double sf, db::Layout &layout) +{ + try { + + tl::OutputMemoryStream buffer; + tl::OutputStream stream (buffer); + + db::SaveLayoutOptions options; + options.set_format ("GDS2"); + options.set_scale_factor (sf); + + db::Writer writer (options); + writer.write (layout, stream); + + return std::string (); + + } catch (tl::Exception &ex) { + return ex.msg (); + } +} + +static std::string huge_string () +{ + std::string n; + for (unsigned int i = 0; i < 100000; ++i) { + n += "A"; + } + return n; +} + +// Exceeding limits +TEST(140) +{ + db::Layout layout; + db::cell_index_type top_index = layout.add_cell ("TOP"); + db::Cell &top = layout.cell (top_index); + unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0)); + top.shapes (l1).insert (db::Text (huge_string (), db::Trans ())); + + EXPECT_EQ (run_test_with_error (1.0, layout), "String max. length overflow, writing layer 1/0, writing cell 'TOP'"); +} + +TEST(141) +{ + db::Layout layout; + db::cell_index_type top_index = layout.add_cell ("TOP"); + db::Cell &top = layout.cell (top_index); + unsigned int l1 = layout.insert_layer (db::LayerProperties (100000, 0)); + top.shapes (l1).insert (db::Box (0, 0, 100, 200)); + + EXPECT_EQ (run_test_with_error (1.0, layout), "Cannot write layer numbers larger than 65535 to GDS2 streams, writing cell 'TOP'"); +} + +TEST(142) +{ + db::Layout layout; + db::cell_index_type top_index = layout.add_cell ("TOP"); + db::Cell &top = layout.cell (top_index); + db::cell_index_type child_index = layout.add_cell ("CHILD"); + db::Cell &child = layout.cell (child_index); + unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0)); + child.shapes (l1).insert (db::Box (0, 0, 100, 200)); + + top.insert (db::CellInstArray (child_index, db::Trans (), db::Vector (100000000, 0), db::Vector (0, 100000000), 10, 10)); + EXPECT_EQ (run_test_with_error (1.0, layout), ""); // no error + + top.clear_insts (); + + top.insert (db::CellInstArray (child_index, db::Trans (), db::Vector (100000000, 0), db::Vector (0, 100000000), 100, 100)); + EXPECT_EQ (run_test_with_error (1.0, layout), "Coordinate overflow, writing instances, writing cell 'TOP'"); + + top.clear_insts (); + + top.insert (db::CellInstArray (child_index, db::Trans (), db::Vector (100, 0), db::Vector (0, 100), 100000, 100)); + EXPECT_EQ (run_test_with_error (1.0, layout), "Cannot write array references with more than 32767 columns or rows to GDS2 streams, writing instances, writing cell 'TOP'"); +} + +TEST(143) +{ + db::Layout layout; + db::cell_index_type top_index = layout.add_cell ("TOP"); + db::Cell &top = layout.cell (top_index); + unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0)); + top.shapes (l1).insert (db::Box (-2000000000, 0, 0, 200000000)); + + EXPECT_EQ (run_test_with_error (1.0, layout), ""); + EXPECT_EQ (run_test_with_error (23.0, layout), "Scaling failed: coordinate underflow, writing layer 1/0, writing cell 'TOP'"); +} + // Extreme fracturing by max. points TEST(166) { @@ -1320,3 +1410,5 @@ TEST(166) run_test (_this, "t166.oas.gz", "t166_au.gds.gz", false, opt); } + + From 277ab2c33538f798e7772a937db00076f2454eb3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 30 Jul 2024 00:13:47 +0200 Subject: [PATCH 28/29] Refactoring of GDS2 writer - split large functions into smaller ones --- .../gds2/db_plugin/dbGDS2WriterBase.cc | 278 ++++++++++-------- .../gds2/db_plugin/dbGDS2WriterBase.h | 11 + 2 files changed, 164 insertions(+), 125 deletions(-) diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc index e679f7689..710262fe3 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc @@ -44,12 +44,7 @@ namespace db { // ------------------------------------------------------------------ -// GDS2WriterBase implementation - -GDS2WriterBase::GDS2WriterBase () -{ - // .. nothing yet .. -} +// Limit checking conversion functions static int32_t safe_convert_to_int32 (int32_t value) { @@ -155,6 +150,16 @@ static uint16_t safe_convert_to_uint16 (uint64_t value) return uint16_t (value); } +// ------------------------------------------------------------------ +// GDS2WriterBase implementation + +GDS2WriterBase::GDS2WriterBase () + : m_dbu (0.0), m_resolve_skew_arrays (false), m_multi_xy (false), m_no_zero_length_paths (false), + m_max_vertex_count (0), m_write_cell_properties (false), m_keep_instances (false) +{ + // .. nothing yet .. +} + void GDS2WriterBase::write_context_string (size_t n, const std::string &s) { @@ -294,13 +299,135 @@ GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, write_record (sENDSTR); } +void +GDS2WriterBase::write_shape (const db::Layout &layout, int layer, int datatype, const db::Shape &shape, double sf) +{ + if (shape.is_text ()) { + + write_text (layer, datatype, sf, m_dbu, shape, layout, shape.prop_id ()); + + } else if (shape.is_polygon ()) { + + write_polygon (layer, datatype, sf, shape, m_multi_xy, m_max_vertex_count, layout, shape.prop_id ()); + + } else if (shape.is_edge ()) { + + write_edge (layer, datatype, sf, shape, layout, shape.prop_id ()); + + } else if (shape.is_edge_pair ()) { + + write_edge (layer, datatype, sf, shape.edge_pair ().first (), layout, shape.prop_id ()); + write_edge (layer, datatype, sf, shape.edge_pair ().second (), layout, shape.prop_id ()); + + } else if (shape.is_path ()) { + + if (m_no_zero_length_paths && (shape.path_length () - shape.path_extensions ().first - shape.path_extensions ().second) == 0) { + // eliminate the zero-width path + db::Polygon poly; + shape.polygon (poly); + write_polygon (layer, datatype, sf, poly, m_multi_xy, m_max_vertex_count, layout, shape.prop_id (), false); + } else { + write_path (layer, datatype, sf, shape, m_multi_xy, layout, shape.prop_id ()); + } + + } else if (shape.is_box ()) { + + write_box (layer, datatype, sf, shape, layout, shape.prop_id ()); + + } +} + +void +GDS2WriterBase::write_cell (db::Layout &layout, const db::Cell &cref, const std::vector > &layers, const std::set &cell_set, double sf, short *time_data) +{ + // cell header + + write_record_size (4 + 12 * 2); + write_record (sBGNSTR); + write_time (time_data); + write_time (time_data); + + try { + write_string_record (sSTRNAME, m_cell_name_map.cell_name (cref.cell_index ())); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing cell name"))); + } + + // cell body + + if (m_write_cell_properties && cref.prop_id () != 0) { + try { + write_properties (layout, cref.prop_id ()); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties"))); + } + } + + // instances + + for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { + + // write only instances to selected cells + if (m_keep_instances || cell_set.find (inst->cell_index ()) != cell_set.end ()) { + + progress_checkpoint (); + try { + write_inst (sf, *inst, true /*normalize*/, m_resolve_skew_arrays, layout, inst->prop_id ()); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::to_string (tr (", writing instances"))); + } + + } + + } + + // shapes + + for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { + + if (layout.is_valid_layer (l->first) && l->second.layer >= 0 && l->second.datatype >= 0) { + + int layer = l->second.layer; + if (layer > std::numeric_limits::max ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write layer numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); + } + int datatype = l->second.datatype; + if (datatype > std::numeric_limits::max ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write datatype numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); + } + + db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts)); + while (! shape.at_end ()) { + + progress_checkpoint (); + + try { + write_shape (layout, layer, datatype, *shape, sf); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing layer %d/%d")), layer, datatype)); + } + + ++shape; + + } + + } + + } + + // end of cell + + write_record_size (4); + write_record (sENDSTR); +} + void GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayoutOptions &options) { set_stream (stream); - double dbu = (options.dbu () == 0.0) ? layout.dbu () : options.dbu (); - double sf = options.scale_factor () * (layout.dbu () / dbu); + m_dbu = (options.dbu () == 0.0) ? layout.dbu () : options.dbu (); + double sf = options.scale_factor () * (layout.dbu () / m_dbu); if (fabs (sf - 1.0) < 1e-9) { // to avoid rounding problems, set to 1.0 exactly if possible. sf = 1.0; @@ -308,8 +435,8 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S db::GDS2WriterOptions gds2_options = options.get_options (); - layout.add_meta_info ("dbuu", MetaInfo (tl::to_string (tr ("Database unit in user units")), tl::to_string (dbu / std::max (1e-9, gds2_options.user_units)))); - layout.add_meta_info ("dbum", MetaInfo (tl::to_string (tr ("Database unit in meter")), tl::to_string (dbu * 1e-6))); + layout.add_meta_info ("dbuu", MetaInfo (tl::to_string (tr ("Database unit in user units")), tl::to_string (m_dbu / std::max (1e-9, gds2_options.user_units)))); + layout.add_meta_info ("dbum", MetaInfo (tl::to_string (tr ("Database unit in meter")), tl::to_string (m_dbu * 1e-6))); layout.add_meta_info ("libname", MetaInfo (tl::to_string (tr ("Library name")), gds2_options.libname)); std::vector > layers; @@ -348,11 +475,14 @@ 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)); - bool multi_xy = gds2_options.multi_xy_records; + 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); + m_no_zero_length_paths = gds2_options.no_zero_length_paths; + m_resolve_skew_arrays = gds2_options.resolve_skew_arrays; + m_write_cell_properties = gds2_options.write_cell_properties; + size_t max_cellname_length = std::max (gds2_options.max_cellname_length, (unsigned int)8); - size_t max_vertex_count = std::max (gds2_options.max_vertex_count, (unsigned int)4); - bool no_zero_length_paths = gds2_options.no_zero_length_paths; - bool resolve_skew_arrays = gds2_options.resolve_skew_arrays; m_cell_name_map = db::WriterCellNameMap (max_cellname_length); m_cell_name_map.replacement ('$'); @@ -393,8 +523,8 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S write_record_size (4 + 8 * 2); write_record (sUNITS); - write_double (dbu / std::max (1e-9, gds2_options.user_units)); - write_double (dbu * 1e-6); + write_double (m_dbu / std::max (1e-9, gds2_options.user_units)); + write_double (m_dbu * 1e-6); // layout properties @@ -433,118 +563,16 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S const db::Cell &cref (layout.cell (*cell)); - try { - - // don't write ghost cells unless they are not empty (any more) - // also don't write proxy cells which are not employed - if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) { - - // cell header - - write_record_size (4 + 12 * 2); - write_record (sBGNSTR); - write_time (time_data); - write_time (time_data); - - try { - write_string_record (sSTRNAME, m_cell_name_map.cell_name (*cell)); - } catch (tl::Exception &ex) { - throw tl::Exception (ex.msg () + tl::to_string (tr (", writing cell name"))); - } - - // cell body - - if (gds2_options.write_cell_properties && cref.prop_id () != 0) { - try { - write_properties (layout, cref.prop_id ()); - } catch (tl::Exception &ex) { - throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties"))); - } - } - - // instances - - for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { - - // write only instances to selected cells - if (options.keep_instances () || cell_set.find (inst->cell_index ()) != cell_set.end ()) { - - progress_checkpoint (); - try { - write_inst (sf, *inst, true /*normalize*/, resolve_skew_arrays, layout, inst->prop_id ()); - } catch (tl::Exception &ex) { - throw tl::Exception (ex.msg () + tl::to_string (tr (", writing instances"))); - } - - } - - } - - // shapes - - for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { - - if (layout.is_valid_layer (l->first) && l->second.layer >= 0 && l->second.datatype >= 0) { - - int layer = l->second.layer; - if (layer > std::numeric_limits::max ()) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write layer numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); - } - int datatype = l->second.datatype; - if (datatype > std::numeric_limits::max ()) { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write datatype numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); - } - - try { - - db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts)); - while (! shape.at_end ()) { - - progress_checkpoint (); - - if (shape->is_text ()) { - write_text (layer, datatype, sf, dbu, *shape, layout, shape->prop_id ()); - } else if (shape->is_polygon ()) { - write_polygon (layer, datatype, sf, *shape, multi_xy, max_vertex_count, layout, shape->prop_id ()); - } else if (shape->is_edge ()) { - write_edge (layer, datatype, sf, *shape, layout, shape->prop_id ()); - } else if (shape->is_edge_pair ()) { - write_edge (layer, datatype, sf, shape->edge_pair ().first (), layout, shape->prop_id ()); - write_edge (layer, datatype, sf, shape->edge_pair ().second (), layout, shape->prop_id ()); - } else if (shape->is_path ()) { - if (no_zero_length_paths && (shape->path_length () - shape->path_extensions ().first - shape->path_extensions ().second) == 0) { - // eliminate the zero-width path - db::Polygon poly; - shape->polygon (poly); - write_polygon (layer, datatype, sf, poly, multi_xy, max_vertex_count, layout, shape->prop_id (), false); - } else { - write_path (layer, datatype, sf, *shape, multi_xy, layout, shape->prop_id ()); - } - } else if (shape->is_box ()) { - write_box (layer, datatype, sf, *shape, layout, shape->prop_id ()); - } - - ++shape; - - } - - } catch (tl::Exception &ex) { - throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing layer %d/%d")), layer, datatype)); - } - - } - - } - - // end of cell - - write_record_size (4); - write_record (sENDSTR); + // don't write ghost cells unless they are not empty (any more) + // also don't write proxy cells which are not employed + if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) { + try { + write_cell (layout, cref, layers, cell_set, sf, time_data); + } catch (tl::Exception &ex) { + throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing cell '%s'")), layout.cell_name (*cell))); } - } catch (tl::Exception &ex) { - throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing cell '%s'")), layout.cell_name (*cell))); } } diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h index 0d058e7fe..de1e9fc4b 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.h @@ -39,6 +39,7 @@ namespace db class Layout; class SaveLayoutOptions; +class GDS2WriterOptions; /** * @brief A GDS2 writer abstraction @@ -166,10 +167,20 @@ protected: private: db::WriterCellNameMap m_cell_name_map; + double m_dbu; + bool m_resolve_skew_arrays; + bool m_multi_xy; + bool m_no_zero_length_paths; + size_t m_max_vertex_count; + bool m_write_cell_properties; + bool m_keep_instances; 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_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); }; } // namespace db From 7ddc86414dc7e718c06880e32b3983ff73d696f5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 30 Jul 2024 07:13:35 +0200 Subject: [PATCH 29/29] Trying to fix Windows builds --- src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc index 710262fe3..ef05cd65c 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc @@ -711,10 +711,10 @@ GDS2WriterBase::write_inst (double sf, const db::Instance &instance, bool normal write_int (scale (sf, t.disp ().y ())); if (is_reg) { - write_int (scale (sf, t.disp ().x () + b.x () * (long) bmax)); - write_int (scale (sf, t.disp ().y () + b.y () * (long) bmax)); - write_int (scale (sf, t.disp ().x () + a.x () * (long) amax)); - write_int (scale (sf, t.disp ().y () + a.y () * (long) amax)); + write_int (scale (sf, t.disp ().x () + b.x () * (int64_t) bmax)); + write_int (scale (sf, t.disp ().y () + b.y () * (int64_t) bmax)); + write_int (scale (sf, t.disp ().x () + a.x () * (int64_t) amax)); + write_int (scale (sf, t.disp ().y () + a.y () * (int64_t) amax)); } finish (layout, prop_id);