From 3cf6ef2cec342887ecf3263bca1d3a7ea2fdf5c4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Jul 2024 23:28:19 +0200 Subject: [PATCH 01/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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 071326ffee7f53869f7ecde97787615ed33d443b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 14:18:42 +0200 Subject: [PATCH 18/21] 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 19/21] 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 20/21] 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 21/21] 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 ()));