Enhance the selection behavior of partial edit mode: allow selection of edge ends if edges overlap, graphical indicator for selected partial

This commit is contained in:
Matthias Koefferlein 2023-07-15 00:22:17 +02:00
parent 67436d81a5
commit c831ed15f8
5 changed files with 220 additions and 132 deletions

View File

@ -774,7 +774,6 @@ PartialShapeFinder::visit_cell (const db::Cell &cell, const db::Box &hit_box, co
checkpoint (); checkpoint ();
// in point mode just store that found that has the least "distance"
m_founds.push_back (founds_vector_type::value_type ()); m_founds.push_back (founds_vector_type::value_type ());
lay::ObjectInstPath &inst_path = m_founds.back ().first; 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_layer (*l);
inst_path.set_shape (*shape); 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 ()) { if (shape->is_polygon ()) {
for (unsigned int c = 0; c < shape->holes () + 1; ++c) { 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); const db::Shapes &shapes = cell.shapes (*l);
std::vector <EdgeWithIndex> edge_sel; std::vector <EdgeWithIndex> edge_sel;
db::ShapeIterator shape = shapes.begin_touching (scan_box, flags (), prop_sel (), inv_prop_sel ()); // two passes - one with points, second with edges
while (! shape.at_end ()) {
bool match = false; bool any = false;
double d = std::numeric_limits<double>::max (); 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<double>::max ();
// in point mode, test the edges and use a "closest" criterion edge_sel.clear ();
if (shape->is_polygon ()) {
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; unsigned int n = 0;
db::Shape::polygon_edge_iterator ee; 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 = e;
++ee; ++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1; 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) { if (r) {
edge_sel.clear (); edge_sel.clear ();
if ((r & 1) == 1) { if ((r & 1) != 0) {
edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, c)); edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, 0));
} }
if ((r & 2) == 2) { if ((r & 2) != 0) {
edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, c)); edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, 0));
} }
if (r == 3) { 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" if (text_info ()) {
db::Shape::point_iterator pt = shape->begin_point ();
if (pt != shape->end_point ()) { db::CplxTrans t_dbu = db::CplxTrans (layout ().dbu ()) * t;
db::Point p (*pt); db::Text text;
++pt; shape->text (text);
unsigned int n = 0; db::Box tb (t_dbu.inverted () * text_info ()->bbox (t_dbu * text, vp));
for (; pt != shape->end_point (); ++pt, ++n) { if (tb.contains (hit_box.center ())) {
unsigned int r = test_edge (t, db::Edge (p, *pt), d, match); d = tp.distance (hit_box.center ());
if (r) {
edge_sel.clear (); edge_sel.clear ();
if ((r & 1) == 1) { edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0));
edge_sel.push_back (EdgeWithIndex (db::Edge (p, p), n, n, 0)); match = true;
}
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));
}
} }
p = *pt;
}
}
} else if (shape->is_box ()) { } else {
const db::Box &box = shape->box (); if (hit_box.contains (tp)) {
d = tp.distance (hit_box.center ());
// convert to polygon and test those edges edge_sel.clear ();
db::Polygon poly (box); edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0));
match = true;
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));
} }
} }
} }
} else if (shape->is_text ()) { if (match && closer (d)) {
db::Point tp (shape->text_trans () * db::Point ()); // in point mode just store that found that has the least "distance"
if (m_founds.empty ()) {
if (text_info ()) { m_founds.push_back (founds_vector_type::value_type ());
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;
} }
} else { lay::ObjectInstPath &inst_path = m_founds.back ().first;
if (hit_box.contains (tp)) { inst_path.set_cv_index (cv_index ());
d = tp.distance (hit_box.center ()); inst_path.set_topcell (topcell ());
edge_sel.clear (); inst_path.assign_path (path ().begin (), path ().end ());
edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); inst_path.set_layer (*l);
match = true; 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; m_hover = true;
mp_view->clear_transient_selection (); mp_view->clear_transient_selection ();
clear_mouse_cursors ();
// compute search box // compute search box
double l = catch_distance (); 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)); 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); 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) { 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)); 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); 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) { if (p12_sel) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_vertex_size (0); marker->set_vertex_size (0);
marker->set (enew, gt, tv); marker->set (enew, gt, tv);
if (transient) {
add_edge_marker (enew, sel->first.cv_index (), gt, tv, true);
}
} }
} }

View File

@ -210,6 +210,7 @@ EditorServiceBase::EditorServiceBase (LayoutViewBase *view)
: lay::ViewService (view->canvas ()), : lay::ViewService (view->canvas ()),
lay::Editable (view), lay::Editable (view),
lay::Plugin (view), lay::Plugin (view),
mp_view (view),
m_cursor_enabled (true), m_cursor_enabled (true),
m_has_tracking_position (false) 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)); 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 &gt, const std::vector<db::DCplxTrans> &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 void
EditorServiceBase::add_edge_marker (const db::DEdge &e, bool emphasize) EditorServiceBase::add_edge_marker (const db::DEdge &e, bool emphasize)
{ {
m_mouse_cursor_markers.push_back (new EdgeMarkerViewObject (this, ui (), e, 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 &gt, const std::vector<db::DCplxTrans> &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 void
EditorServiceBase::clear_mouse_cursors () EditorServiceBase::clear_mouse_cursors ()
{ {

View File

@ -74,10 +74,20 @@ public:
*/ */
void add_mouse_cursor (const db::DPoint &pt, bool emphasize = false); 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 &gt, const std::vector<db::DCplxTrans> &tv, bool emphasize = false);
/** /**
* @brief Adds an edge marker for the given edge * @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 &gt, const std::vector<db::DCplxTrans> &tv, bool emphasize = false);
/** /**
* @brief Resets the mouse cursor * @brief Resets the mouse cursor
@ -132,6 +142,7 @@ protected:
private: private:
// The marker representing the mouse cursor // The marker representing the mouse cursor
lay::LayoutViewBase *mp_view;
std::vector<lay::ViewObject *> m_mouse_cursor_markers; std::vector<lay::ViewObject *> m_mouse_cursor_markers;
tl::Color m_cursor_color; tl::Color m_cursor_color;
bool m_cursor_enabled; bool m_cursor_enabled;

View File

@ -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 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 p1 = trans * edg.p1 ();
db::Point p2 = trans * edg.p2 (); db::Point p2 = trans * edg.p2 ();
unsigned int ret = 0; unsigned int ret = 0;
// we hit the region with the edge end points - take the closest vertex if (points) {
if (m_region.contains (p1) || m_region.contains (p2)) {
double d1 = p1.double_distance (m_region.center ()); // we hit the region with the edge end points - take the closest vertex
double d2 = p2.double_distance (m_region.center ()); 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) { } else {
ret = 1;
} else {
ret = 2;
}
match = true;
}
// if the edge cuts through the active region: test the // if the edge cuts through the active region: test the
// edge as a whole // edge as a whole
if (ret == 0) {
db::Edge edg_trans (p1, p2); db::Edge edg_trans (p1, p2);
if (edg_trans.clipped (m_region).first) { if (edg_trans.clipped (m_region).first) {
double d = edg_trans.distance_abs (m_region.center ()); double d = edg_trans.distance_abs (m_region.center ());
if (! match || d < distance) { if (! match || d < distance) {
distance = d; distance = d;
ret = 3;
} }
ret = 3;
match = true; match = true;
} }
} }
return ret; return ret;

View File

@ -176,11 +176,18 @@ protected:
* *
* "trans" is the transformation to be applied to the edge before the test. * "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. * 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 * Bit 0 of this mask indicates the first point is matching, bit 1 indicates the
* second point is matching. * 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: private:
void do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, const db::ICplxTrans &t); void do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, const db::ICplxTrans &t);