From c831ed15f8ee8dd8946783e288d230db30167e92 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 15 Jul 2023 00:22:17 +0200 Subject: [PATCH] Enhance the selection behavior of partial edit mode: allow selection of edge ends if edges overlap, graphical indicator for selected partial --- src/edt/edt/edtPartialService.cc | 242 ++++++++++-------- src/laybasic/laybasic/layEditorServiceBase.cc | 19 ++ src/laybasic/laybasic/layEditorServiceBase.h | 13 +- src/laybasic/laybasic/layFinder.cc | 69 +++-- src/laybasic/laybasic/layFinder.h | 9 +- 5 files changed, 220 insertions(+), 132 deletions(-) diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index 649be7f3f..765c6d2a7 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -774,7 +774,6 @@ PartialShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, co checkpoint (); - // in point mode just store that found that has the least "distance" m_founds.push_back (founds_vector_type::value_type ()); lay::ObjectInstPath &inst_path = m_founds.back ().first; @@ -786,7 +785,8 @@ PartialShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, co inst_path.set_layer (*l); inst_path.set_shape (*shape); - // in point mode, test the edges and use a "closest" criterion + // in box mode, select the edges depending on whether an endpoint is inside the + // box or not if (shape->is_polygon ()) { for (unsigned int c = 0; c < shape->holes () + 1; ++c) { @@ -897,155 +897,164 @@ PartialShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, co const db::Shapes &shapes = cell.shapes (*l); std::vector edge_sel; - db::ShapeIterator shape = shapes.begin_touching (scan_box, flags (), prop_sel (), inv_prop_sel ()); - while (! shape.at_end ()) { + // two passes - one with points, second with edges - bool match = false; - double d = std::numeric_limits::max (); + bool any = false; + for (int pass = 0; pass < 2 && ! any; ++pass) { - edge_sel.clear (); + db::ShapeIterator shape = shapes.begin_touching (scan_box, flags (), prop_sel (), inv_prop_sel ()); + while (! shape.at_end ()) { - checkpoint (); + bool match = false; + double d = std::numeric_limits::max (); - // in point mode, test the edges and use a "closest" criterion - if (shape->is_polygon ()) { + edge_sel.clear (); - for (unsigned int c = 0; c < shape->holes () + 1; ++c) { + checkpoint (); + + // in point mode, test the edges and use a "closest" criterion + if (shape->is_polygon ()) { + + for (unsigned int c = 0; c < shape->holes () + 1; ++c) { + + unsigned int n = 0; + db::Shape::polygon_edge_iterator ee; + for (db::Shape::polygon_edge_iterator e = shape->begin_edge (c); ! e.at_end (); e = ee, ++n) { + + ee = e; + ++ee; + unsigned int nn = ee.at_end () ? 0 : n + 1; + + unsigned int r = test_edge (t, *e, pass == 0, d, match); + if (r) { + edge_sel.clear (); + if ((r & 1) != 0) { + edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, c)); + } + if ((r & 2) != 0) { + edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, c)); + } + if (r == 3) { + edge_sel.push_back (EdgeWithIndex (*e, n, nn, c)); + } + } + + } + + } + + } else if (shape->is_path ()) { + + // test the "spine" + db::Shape::point_iterator pt = shape->begin_point (); + if (pt != shape->end_point ()) { + db::Point p (*pt); + ++pt; + unsigned int n = 0; + for (; pt != shape->end_point (); ++pt, ++n) { + unsigned int r = test_edge (t, db::Edge (p, *pt), pass == 0, d, match); + if (r) { + edge_sel.clear (); + if ((r & 1) != 0) { + edge_sel.push_back (EdgeWithIndex (db::Edge (p, p), n, n, 0)); + } + if ((r & 2) != 0) { + edge_sel.push_back (EdgeWithIndex (db::Edge (*pt, *pt), n + 1, n + 1, 0)); + } + if (r == 3) { + edge_sel.push_back (EdgeWithIndex (db::Edge (p, *pt), n, n + 1, 0)); + } + } + p = *pt; + } + } + + } else if (shape->is_box ()) { + + const db::Box &box = shape->box (); + + // convert to polygon and test those edges + db::Polygon poly (box); unsigned int n = 0; db::Shape::polygon_edge_iterator ee; - for (db::Shape::polygon_edge_iterator e = shape->begin_edge (c); ! e.at_end (); e = ee, ++n) { + for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) { ee = e; ++ee; unsigned int nn = ee.at_end () ? 0 : n + 1; - unsigned int r = test_edge (t, *e, d, match); + unsigned int r = test_edge (t, *e, pass == 0, d, match); if (r) { edge_sel.clear (); - if ((r & 1) == 1) { - edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, c)); + if ((r & 1) != 0) { + edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, 0)); } - if ((r & 2) == 2) { - edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, c)); + if ((r & 2) != 0) { + edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, 0)); } if (r == 3) { - edge_sel.push_back (EdgeWithIndex (*e, n, nn, c)); + edge_sel.push_back (EdgeWithIndex (*e, n, nn, 0)); } } } - } + } else if (shape->is_text ()) { - } else if (shape->is_path ()) { + db::Point tp (shape->text_trans () * db::Point ()); - // test the "spine" - db::Shape::point_iterator pt = shape->begin_point (); - if (pt != shape->end_point ()) { - db::Point p (*pt); - ++pt; - unsigned int n = 0; - for (; pt != shape->end_point (); ++pt, ++n) { - unsigned int r = test_edge (t, db::Edge (p, *pt), d, match); - if (r) { + if (text_info ()) { + + db::CplxTrans t_dbu = db::CplxTrans (layout ().dbu ()) * t; + db::Text text; + shape->text (text); + db::Box tb (t_dbu.inverted () * text_info ()->bbox (t_dbu * text, vp)); + if (tb.contains (hit_box.center ())) { + d = tp.distance (hit_box.center ()); edge_sel.clear (); - if ((r & 1) == 1) { - edge_sel.push_back (EdgeWithIndex (db::Edge (p, p), n, n, 0)); - } - if ((r & 2) == 2) { - edge_sel.push_back (EdgeWithIndex (db::Edge (*pt, *pt), n + 1, n + 1, 0)); - } - if (r == 3) { - edge_sel.push_back (EdgeWithIndex (db::Edge (p, *pt), n, n + 1, 0)); - } + edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); + match = true; } - p = *pt; - } - } - } else if (shape->is_box ()) { + } else { - const db::Box &box = shape->box (); - - // convert to polygon and test those edges - db::Polygon poly (box); - - unsigned int n = 0; - db::Shape::polygon_edge_iterator ee; - for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) { - - ee = e; - ++ee; - unsigned int nn = ee.at_end () ? 0 : n + 1; - - unsigned int r = test_edge (t, *e, d, match); - if (r) { - edge_sel.clear (); - if ((r & 1) == 1) { - edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, 0)); - } - if ((r & 2) == 2) { - edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, 0)); - } - if (r == 3) { - edge_sel.push_back (EdgeWithIndex (*e, n, nn, 0)); + if (hit_box.contains (tp)) { + d = tp.distance (hit_box.center ()); + edge_sel.clear (); + edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); + match = true; } + } } - } else if (shape->is_text ()) { + if (match && closer (d)) { - db::Point tp (shape->text_trans () * db::Point ()); - - if (text_info ()) { - - db::CplxTrans t_dbu = db::CplxTrans (layout ().dbu ()) * t; - db::Text text; - shape->text (text); - db::Box tb (t_dbu.inverted () * text_info ()->bbox (t_dbu * text, vp)); - if (tb.contains (hit_box.center ())) { - d = tp.distance (hit_box.center ()); - edge_sel.clear (); - edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); - match = true; + // in point mode just store that found that has the least "distance" + if (m_founds.empty ()) { + m_founds.push_back (founds_vector_type::value_type ()); } - } else { + lay::ObjectInstPath &inst_path = m_founds.back ().first; - if (hit_box.contains (tp)) { - d = tp.distance (hit_box.center ()); - edge_sel.clear (); - edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); - match = true; - } + inst_path.set_cv_index (cv_index ()); + inst_path.set_topcell (topcell ()); + inst_path.assign_path (path ().begin (), path ().end ()); + inst_path.set_layer (*l); + inst_path.set_shape (*shape); + + m_founds.back ().second = edge_sel; + + any = true; } + ++shape; + } - if (match && closer (d)) { - - // in point mode just store that found that has the least "distance" - if (m_founds.empty ()) { - m_founds.push_back (founds_vector_type::value_type ()); - } - - lay::ObjectInstPath &inst_path = m_founds.back ().first; - - inst_path.set_cv_index (cv_index ()); - inst_path.set_topcell (topcell ()); - inst_path.assign_path (path ().begin (), path ().end ()); - inst_path.set_layer (*l); - inst_path.set_shape (*shape); - - m_founds.back ().second = edge_sel; - - } - - ++shape; - } } @@ -1153,6 +1162,7 @@ PartialService::timeout () m_hover = true; mp_view->clear_transient_selection (); + clear_mouse_cursors (); // compute search box double l = catch_distance (); @@ -2413,6 +2423,10 @@ PartialService::enter_edge (const EdgeWithIndex &e, size_t &nmarker, partial_obj db::DEdge ee = db::DEdge (db::DPoint (ep2) + ((db::DPoint (ep1) - db::DPoint (ep2)) * 0.25), db::DPoint (ep2)); marker->set (ee, db::DCplxTrans (gt), tv); + if (transient && sel->second.size () == 1) { + add_mouse_cursor (ep2, sel->first.cv_index (), gt, tv, true); + } + } if (p1_sel && !p12_sel) { @@ -2423,12 +2437,22 @@ PartialService::enter_edge (const EdgeWithIndex &e, size_t &nmarker, partial_obj db::DEdge ee = db::DEdge (db::DPoint (ep1), db::DPoint (ep1) + ((db::DPoint (ep2) - db::DPoint (ep1)) * 0.25)); marker->set (ee, db::DCplxTrans (gt), tv); - } + if (transient && sel->second.size () == 1) { + add_mouse_cursor (ep1, sel->first.cv_index (), gt, tv, true); + } + + } if (p12_sel) { + lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_vertex_size (0); marker->set (enew, gt, tv); + + if (transient) { + add_edge_marker (enew, sel->first.cv_index (), gt, tv, true); + } + } } diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index e3539d6e3..ea44746d3 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -210,6 +210,7 @@ EditorServiceBase::EditorServiceBase (LayoutViewBase *view) : lay::ViewService (view->canvas ()), lay::Editable (view), lay::Plugin (view), + mp_view (view), m_cursor_enabled (true), m_has_tracking_position (false) { @@ -229,12 +230,30 @@ EditorServiceBase::add_mouse_cursor (const db::DPoint &pt, bool emphasize) m_mouse_cursor_markers.push_back (new MouseCursorViewObject (this, ui (), pt, emphasize)); } +void +EditorServiceBase::add_mouse_cursor (const db::Point &pt, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize) +{ + double dbu = mp_view->cellview (cv_index)->layout ().dbu (); + for (auto t = tv.begin (); t != tv.end (); ++t) { + add_mouse_cursor (*t * db::CplxTrans (dbu) * gt * pt, emphasize); + } +} + void EditorServiceBase::add_edge_marker (const db::DEdge &e, bool emphasize) { m_mouse_cursor_markers.push_back (new EdgeMarkerViewObject (this, ui (), e, emphasize)); } +void +EditorServiceBase::add_edge_marker (const db::Edge &e, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize) +{ + double dbu = mp_view->cellview (cv_index)->layout ().dbu (); + for (auto t = tv.begin (); t != tv.end (); ++t) { + add_edge_marker (*t * db::CplxTrans (dbu) * gt * e, emphasize); + } +} + void EditorServiceBase::clear_mouse_cursors () { diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 68cc0290d..36af9f8ed 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -74,10 +74,20 @@ public: */ void add_mouse_cursor (const db::DPoint &pt, bool emphasize = false); + /** + * @brief Adds a mouse cursor to the given point in layout space + */ + void add_mouse_cursor (const db::Point &pt, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize = false); + /** * @brief Adds an edge marker for the given edge */ - void add_edge_marker (const db::DEdge &e, bool emphasize); + void add_edge_marker (const db::DEdge &e, bool emphasize = false); + + /** + * @brief Adds an edge marker for the given edge in layout space + */ + void add_edge_marker (const db::Edge &e, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize = false); /** * @brief Resets the mouse cursor @@ -132,6 +142,7 @@ protected: private: // The marker representing the mouse cursor + lay::LayoutViewBase *mp_view; std::vector m_mouse_cursor_markers; tl::Color m_cursor_color; bool m_cursor_enabled; diff --git a/src/laybasic/laybasic/layFinder.cc b/src/laybasic/laybasic/layFinder.cc index 168efce12..df2cdcc36 100644 --- a/src/laybasic/laybasic/layFinder.cc +++ b/src/laybasic/laybasic/layFinder.cc @@ -123,47 +123,74 @@ Finder::start (lay::LayoutViewBase *view, unsigned int cv_index, const std::vect } } +void +Finder::test_edge (const db::ICplxTrans &trans, const db::Edge &edge, double &distance, bool &match) +{ + if (test_edge (trans, edge, true, distance, match) == 0) { + test_edge (trans, edge, false, distance, match); + } +} + unsigned int -Finder::test_edge (const db::ICplxTrans &trans, const db::Edge &edg, double &distance, bool &match) +Finder::test_edge (const db::ICplxTrans &trans, const db::Edge &edg, bool points, double &distance, bool &match) { db::Point p1 = trans * edg.p1 (); db::Point p2 = trans * edg.p2 (); unsigned int ret = 0; - // we hit the region with the edge end points - take the closest vertex - if (m_region.contains (p1) || m_region.contains (p2)) { + if (points) { - double d1 = p1.double_distance (m_region.center ()); - double d2 = p2.double_distance (m_region.center ()); + // we hit the region with the edge end points - take the closest vertex + if (m_region.contains (p1) || m_region.contains (p2)) { + + double d1 = p1.double_distance (m_region.center ()); + double d2 = p2.double_distance (m_region.center ()); + if (d1 < d2) { + ret = 1; + } else { + ret = 2; + } + + double d = std::min (d1, d2); + // add a penalty of 1 DBU for being on the wrong + // side of the edge - this favors the right edge + // in case of butting corners + if (ret == 1) { + if (db::sprod_sign (m_region.center () - p1, p2 - p1) < 0) { + d += trans.ctrans (1); + } + } else { + if (db::sprod_sign (m_region.center () - p2, p1 - p2) < 0) { + d += trans.ctrans (1); + } + } + + if (! match || d < distance) { + distance = d; + } + + match = true; - double d = std::min (d1, d2); - if (! match || d < distance) { - distance = d; } - if (d1 < d2) { - ret = 1; - } else { - ret = 2; - } - - match = true; - - } + } else { - // if the edge cuts through the active region: test the - // edge as a whole - if (ret == 0) { + // if the edge cuts through the active region: test the + // edge as a whole db::Edge edg_trans (p1, p2); if (edg_trans.clipped (m_region).first) { + double d = edg_trans.distance_abs (m_region.center ()); if (! match || d < distance) { distance = d; - ret = 3; } + + ret = 3; match = true; + } + } return ret; diff --git a/src/laybasic/laybasic/layFinder.h b/src/laybasic/laybasic/layFinder.h index 065e69741..6ef4bc00b 100644 --- a/src/laybasic/laybasic/layFinder.h +++ b/src/laybasic/laybasic/layFinder.h @@ -176,11 +176,18 @@ protected: * * "trans" is the transformation to be applied to the edge before the test. * + * If "points" is true, only points are tested, otherwise edges are tested. + * * This method returns a mask indicating which point of the edge was matching. * Bit 0 of this mask indicates the first point is matching, bit 1 indicates the * second point is matching. */ - unsigned int test_edge (const db::ICplxTrans &trans, const db::Edge &edge, double &distance, bool &match); + unsigned int test_edge (const db::ICplxTrans &trans, const db::Edge &edge, bool points, double &distance, bool &match); + + /** + * @brief Tests an edge in point mode and edge mode (later) + */ + void test_edge (const db::ICplxTrans &trans, const db::Edge &edge, double &distance, bool &match); private: void do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, const db::ICplxTrans &t);