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 ();
// 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 <EdgeWithIndex> 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<double>::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<double>::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);
}
}
}

View File

@ -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 &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
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 &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
EditorServiceBase::clear_mouse_cursors ()
{

View File

@ -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 &gt, const std::vector<db::DCplxTrans> &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 &gt, const std::vector<db::DCplxTrans> &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<lay::ViewObject *> m_mouse_cursor_markers;
tl::Color m_cursor_color;
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
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;

View File

@ -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);