/* KLayout Layout Viewer Copyright (C) 2006-2017 Matthias Koefferlein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "dbVector.h" #include "layLayoutView.h" #include "laySnap.h" #include "layFinder.h" #include "tlProgress.h" #include "edtPartialService.h" #include "edtService.h" #include "edtConfig.h" #include "edtDialogs.h" #include "edtEditorOptionsPages.h" #include namespace tl { class Progress; } namespace edt { // max. number of tries in single-click selection before giving up static int point_sel_tests = 10000; // TODO: is this a reasonable value? // ------------------------------------------------------------- /** * @brief A move constraint * * A move constraint describes the degrees of freedom for a single point. * Such a constraint can be: fixed (no freedom), unconstrained (point can move * both in x and y direction) and freedom along an axis. */ class Constraint { public: typedef enum { Free, Fixed, OneDim } constraint_mode; /** * @brief Construct an unconstrained constraint */ Constraint () : m_mode (Free) { } /** * @brief Construct an "fixed" constraint */ Constraint (int) : m_mode (Fixed) { } /** * @brief Construct an "1-dimensional" constraint * * @param axis Gives the direction in which the point can move, if 0 same as fixed constraint */ Constraint (db::Vector axis) : m_axis (axis) { if (axis == db::Vector ()) { m_mode = Fixed; } else { m_mode = OneDim; } } /** * @brief Type accessor */ constraint_mode mode () const { return m_mode; } /** * @brief Add constraints * * Merging of constraints means to allow movement additionally in the same directions * than given by the second constraint. */ Constraint &operator+= (const Constraint &b) { if (m_mode == Fixed || b.mode () == Free) { *this = b; } else if (m_mode == Free || b.mode () == Fixed) { // nothing to do. } else { // must both be OneDim here. if (db::vprod_sign (m_axis, b.m_axis) != 0) { m_mode = Free; } } return *this; } /** * @brief Unite constraints * * Additionally impose a constraint on this movement. */ Constraint &operator*= (const Constraint &b) { if (m_mode == Free || b.mode () == Fixed) { *this = b; } else if (m_mode == Fixed || b.mode () == Free) { // nothing to do. } else { // must both be OneDim here. if (db::vprod_sign (m_axis, b.m_axis) != 0) { m_mode = Fixed; } } return *this; } /** * @brief Move a point by the given vector, given the imposed constraints * * The movement is performed "as far as possible", i.e. projecting the axis to the * move vector, not vice versa. * * @param p The point to move * @param v The vector by which to move (desired direction) * @return first: true, if the move was successful, second: the resulting point */ std::pair move (const db::Point &p, const db::DVector &v) { if (v == db::DVector ()) { return std::make_pair (true, p); } else if (m_mode == Free) { return std::make_pair (true, p + db::Vector (v)); } else if (m_mode == Fixed) { return std::make_pair (true, p); } else { double proj = db::sprod (db::DVector (m_axis), v); #if 0 // HINT: disallowing movements when constraint axis and edge form a small angle // leads to strange results when moving rounded corner segments as a whole for example. // Therefore the creation of artefacts in this case is not recommended // There is other code that takes care of the case that edges get very long // beyond a certain threshold. // do not allow movement for small angles to avoid creation of nasty spikes if (fabs (proj) < m_axis.double_length () * v.double_length () * 0.1) { return std::make_pair (false, p); } else { // check for overflow and return false if that happens db::DPoint dp = db::DPoint (p) + db::DVector (m_axis) * (v.sq_double_length () / proj); if (dp.x () <= double (std::numeric_limits::min ()) || dp.x () >= double (std::numeric_limits::max ()) || dp.y () <= double (std::numeric_limits::min ()) || dp.y () >= double (std::numeric_limits::max ())) { return std::make_pair (false, p); } else { return std::make_pair (true, db::Point::from_double (dp)); } } #else // check for overflow and return false if that happens db::DPoint dp = db::DPoint (p) + db::DVector (m_axis) * (v.sq_double_length () / proj); if (dp.x () <= double (std::numeric_limits::min ()) || dp.x () >= double (std::numeric_limits::max ()) || dp.y () <= double (std::numeric_limits::min ()) || dp.y () >= double (std::numeric_limits::max ())) { return std::make_pair (false, p); } else { return std::make_pair (true, db::Point (dp)); } #endif } } /** * @brief Transform by a given transformation */ template Constraint &transform (const T &t) { m_axis.transform (t); return *this; } /** * @brief Return the transformed version */ template Constraint transformed (const T &t) const { Constraint c (*this); return c.transform (t); } private: constraint_mode m_mode; db::Vector m_axis; }; // ------------------------------------------------------------- // Some utilities static bool insert_point_path (const db::Path &p, const std::set &sel, db::Point &ins, db::Path &new_path) { new_path.width (p.width ()); new_path.round (p.round ()); new_path.extensions (p.bgn_ext (), p.end_ext ()); std::vector ctr; ctr.reserve (p.points () + 1); bool found = false; unsigned int n = 0; for (db::Path::iterator pt = p.begin (); pt != p.end (); ++n) { db::Point p1 = *pt; ++pt; if (pt != p.end ()) { db::Point p2 = *pt; ctr.push_back (p1); if (! found && sel.find (EdgeWithIndex (db::Edge (p1, p2), n, n + 1, 0)) != sel.end ()) { // project the point onto the edge std::pair projected = db::Edge (p1, p2).projected (ins); if (projected.first) { ins = projected.second; ctr.push_back (ins); found = true; } } } else { ctr.push_back (p1); } } if (found) { new_path.assign (ctr.begin (), ctr.end ()); } return found; } static void assign_path_compressed (db::Path &p, std::vector &ctr) { // compress contour (remove redundant points) // and assign to path std::vector::iterator wp = ctr.begin (); std::vector::iterator wp0 = wp; std::vector::const_iterator rp = ctr.begin (); db::Point pm1 = *rp; if (wp != ctr.end ()) { ++wp; ++rp; while (rp != ctr.end ()) { db::Point p0 = *rp; ++rp; if (rp != ctr.end ()) { db::Point pp1 = *rp; if (! (db::vprod_sign (pp1 - p0, p0 - pm1) == 0 && db::sprod_sign (pp1 - p0, p0 - pm1) >= 0)) { *wp++ = p0; pm1 = p0; } } else { *wp++ = p0; } } } p.assign (wp0, wp); } static db::Path del_points_path (const db::Path &p, const std::set &sel) { db::Path new_path; new_path.width (p.width ()); new_path.round (p.round ()); new_path.extensions (p.bgn_ext (), p.end_ext ()); std::vector ctr; ctr.reserve (p.points ()); unsigned int n = 0; for (db::Path::iterator pt = p.begin (); pt != p.end (); ++pt, ++n) { db::Point p1 = *pt; if (sel.find (EdgeWithIndex (db::Edge (p1, p1), n, n, 0)) == sel.end ()) { ctr.push_back (p1); } } assign_path_compressed (new_path, ctr); return new_path; } static void modify_path (db::Path &p, const std::map &new_points, const std::map &new_edges, bool compress = false) { std::vector ctr; ctr.reserve (p.points ()); std::map ::const_iterator np; std::map ::const_iterator ne; unsigned int n = 0; for (db::Path::iterator pt = p.begin (); pt != p.end (); ++n) { db::Point p1 = *pt; db::Point p1org = p1; np = new_points.find (PointWithIndex (p1, n, 0)); if (np != new_points.end ()) { p1 = np->second; } ++pt; if (pt != p.end ()) { db::Point p2 = *pt; db::Point p2org = p2; np = new_points.find (PointWithIndex (p2, n + 1, 0)); if (np != new_points.end ()) { p2 = np->second; } ne = new_edges.find (EdgeWithIndex (db::Edge (p1org, p2org), n, n + 1, 0)); ctr.push_back (p1); if (ne != new_edges.end () && ne->second.p1 () != p1) { ctr.push_back (ne->second.p1 ()); } if (ne != new_edges.end () && ne->second.p2 () != p2) { ctr.push_back (ne->second.p2 ()); } } else { ctr.push_back (p1); } } if (compress) { assign_path_compressed (p, ctr); } else { p.assign (ctr.begin (), ctr.end ()); } } bool insert_point_poly (const db::Polygon &p, const std::set &sel, db::Point &ins, db::Polygon &new_poly) { for (unsigned int c = 0; c < p.holes () + 1; ++c) { bool found = false; std::vector ctr; size_t points = p.contour (c).size (); ctr.reserve (points + 1); unsigned int n = 0; db::Shape::polygon_edge_iterator ee; for (db::Shape::polygon_edge_iterator e = p.begin_edge (c); ! e.at_end (); e = ee, ++n) { ee = e; ++ee; unsigned int nn = ee.at_end () ? 0 : n + 1; ctr.push_back ((*e).p1 ()); if (! found && sel.find (EdgeWithIndex (*e, n, nn, c)) != sel.end ()) { // project the point onto the edge std::pair projected = (*e).projected (ins); if (projected.first) { ins = projected.second; ctr.push_back (ins); found = true; } } } if (found) { new_poly = p; if (c == 0) { new_poly.assign_hull (ctr.begin (), ctr.end (), false /*don't compress*/); } else { new_poly.assign_hole (c - 1, ctr.begin (), ctr.end (), false /*don't compress*/); } return true; } } return false; } static db::Polygon del_points_poly (const db::Polygon &p, const std::set &sel) { db::Polygon new_poly = p; for (unsigned int c = 0; c < p.holes () + 1; ++c) { std::vector ctr; size_t points = p.contour (c).size (); ctr.reserve (points); unsigned int n = 0; for (db::Shape::polygon_edge_iterator e = p.begin_edge (c); ! e.at_end (); ++e, ++n) { db::Point p1 = (*e).p1 (); if (sel.find (EdgeWithIndex (db::Edge (p1, p1), n, n, c)) == sel.end ()) { ctr.push_back (p1); } } if (c == 0) { new_poly.assign_hull (ctr.begin (), ctr.end (), true /*compress*/); } else { new_poly.assign_hole (c - 1, ctr.begin (), ctr.end (), true /*compress*/); } } return new_poly; } static void modify_polygon (db::Polygon &p, const std::map &new_points, const std::map &new_edges, bool compress = false) { for (unsigned int c = 0; c < p.holes () + 1; ++c) { std::vector ctr; size_t points = p.contour (c).size (); ctr.reserve (points); std::map ::const_iterator np; std::map ::const_iterator ne; unsigned int n = 0; db::Shape::polygon_edge_iterator ee; for (db::Shape::polygon_edge_iterator e = p.begin_edge (c); ! e.at_end (); e = ee, ++n) { ee = e; ++ee; unsigned int nn = ee.at_end () ? 0 : n + 1; db::Point p1 = (*e).p1 (); np = new_points.find (PointWithIndex (p1, n, c)); if (np != new_points.end ()) { p1 = np->second; } db::Point p2 = (*e).p2 (); np = new_points.find (PointWithIndex (p2, nn, c)); if (np != new_points.end ()) { p2 = np->second; } ne = new_edges.find (EdgeWithIndex (*e, n, nn, c)); ctr.push_back (p1); if (ne != new_edges.end () && ne->second.p1 () != p1) { ctr.push_back (ne->second.p1 ()); } if (ne != new_edges.end () && ne->second.p2 () != p2) { ctr.push_back (ne->second.p2 ()); } } if (c == 0) { p.assign_hull (ctr.begin (), ctr.end (), compress); } else { p.assign_hole (c - 1, ctr.begin (), ctr.end (), compress); } } } static void constrain (std::map &constr, const EdgeWithIndex &edge) { constr.insert (std::make_pair (edge.pi1 (), Constraint ())).first->second *= Constraint (edge.d ()); constr.insert (std::make_pair (edge.pi2 (), Constraint ())).first->second *= Constraint (edge.d ()); } static void create_shift_sets (const db::Shape &shape, const std::set &sel, std::map &new_points, std::map &new_edges, db::Vector mv) { // Set up a map of new edges and new points for (std::set ::const_iterator e = sel.begin (); e != sel.end (); ++e) { if (e->p1 () != e->p2 ()) { new_edges.insert (std::make_pair (*e, db::Edge (*e))); } else { new_points.insert (std::make_pair (PointWithIndex (e->p1 (), e->n, e->c), e->p1 ())); } } // new_points should only contain the selected points, not the start and end points of selected edges for (std::set ::const_iterator e = sel.begin (); e != sel.end (); ++e) { if (e->p1 () != e->p2 ()) { new_points.erase (e->pi1 ()); new_points.erase (e->pi2 ()); } } std::map point_constr; 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; if ((*e).p1 () != (*e).p2 () && sel.find (EdgeWithIndex (*e, n, nn, c)) == sel.end ()) { constrain (point_constr, EdgeWithIndex (*e, n, nn, c)); } } } } else if (shape.is_path ()) { if (shape.begin_point () != shape.end_point ()) { db::Shape::point_iterator pt = shape.begin_point (); db::Point p1 = *pt; unsigned int n = 0; while (true) { ++pt; if (pt == shape.end_point ()) { break; } EdgeWithIndex e (db::Edge (p1, *pt), n, n + 1, 0); if (e.p1 () != e.p2 () && sel.find (e) == sel.end ()) { constrain (point_constr, e); } p1 = *pt; ++n; } } } else if (shape.is_box ()) { // convert to polygon and test those edges db::Polygon poly (shape.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; EdgeWithIndex ewi (*e, n, nn, 0); if ((*e).p1 () != (*e).p2 () && sel.find (ewi) == sel.end ()) { // add some moveable edges to impose manhattan constraints if (new_points.find (ewi.pi1 ()) != new_points.end () || new_points.find (ewi.pi2 ()) != new_points.end ()) { new_edges.insert (std::make_pair (ewi, db::Edge ((*e)))); } else { constrain (point_constr, ewi); } } } } // Simply move the points for (std::map ::iterator np = new_points.begin (); np != new_points.end (); ++np) { np->second += mv; } // The edges are treated somewhat more elaborate: for (std::map ::iterator ne = new_edges.begin (); ne != new_edges.end (); ++ne) { std::map ::iterator c1, c2; // compute normal of move vector db::DVector nmv; if (ne->first.d () != db::Vector ()) { nmv = db::DVector (mv) - db::DVector (ne->first.d ()) * (double (db::sprod (mv, ne->first.d ())) / ne->first.d ().sq_double_length ()); } db::Point p1 = ne->second.p1 (), p2 = ne->second.p2 (); db::Point p1e = p1, p2e = p2; c1 = point_constr.find (ne->first.pi1 ()); if (c1 != point_constr.end ()) { std::pair pm = c1->second.move (p1, nmv); if (pm.first) { p1e = p1 = pm.second; } else { // if the movement was not possible, create a new "detached" edge p1e = p1 + db::Vector (nmv); } } else { p1 += mv; p1e = p1; } c2 = point_constr.find (ne->first.pi2 ()); if (c2 != point_constr.end ()) { std::pair pm = c2->second.move (p2, nmv); if (pm.first) { p2e = p2 = pm.second; } else { // if the movement was not possible, create a new "detached" edge p2e = p2 + db::Vector (nmv); } } else { p2 += mv; p2e = p2; } // if the moved edge is // 1. result of two constraints // ( commented out: 2. inverted (the direction has changed) or the length grows 4x larger than the move distance ) // then create a "detached edge" as well db::Vector ve (p2e - p1e); db::Vector vo (ne->second.p2 () - ne->second.p1 ()); if (c1 != point_constr.end () && c2 != point_constr.end () && (/* db::sprod_sign (ve, vo) <= 0 || */ (ve - vo).double_length () > 4.0 * nmv.double_length ())) { #if 0 // first try to detach just one (the one with the largest movement) if (p2e.sq_double_distance (ne->second.p2 ()) > p1e.sq_double_distance (ne->second.p1 ())) { p2 = ne->second.p2 (); p2e = p2 + db::Vector (nmv); } else { p1 = ne->second.p1 (); p1e = p1 + db::Vector (nmv); } // if that still is not sufficient to avoid inversion, do both if (db::sprod_sign (db::Vector (p2e - p1e), db::Vector (ne->second.p2 () - ne->second.p1 ())) <= 0) { p2 = ne->second.p2 (); p1 = ne->second.p1 (); p2e = p2 + db::Vector (nmv); p1e = p1 + db::Vector (nmv); } #else // this approach is more simple: just create the detached edge .. p2 = ne->second.p2 (); p1 = ne->second.p1 (); p2e = p2 + db::Vector (nmv); p1e = p1 + db::Vector (nmv); #endif } ne->second = db::Edge (p1e, p2e); // insert the end points into the point list in order to find them by looking up a point alone new_points.insert (std::make_pair (ne->first.pi1 (), db::Point ())).first->second = p1; new_points.insert (std::make_pair (ne->first.pi2 (), db::Point ())).first->second = p2; } } // ------------------------------------------------------------- // PartialShapeFinder declaration /** * @brief Partial shape finder utility class * * This class specializes the finder to finding vertices or edges of shapes. */ class PartialShapeFinder : public lay::ShapeFinder { public: typedef std::vector > > founds_vector_type; typedef founds_vector_type::const_iterator iterator; PartialShapeFinder (bool point_mode, bool top_level_sel, db::ShapeIterator::flags_type flags); iterator begin () const { return m_founds.begin (); } iterator end () const { return m_founds.end (); } private: virtual void visit_cell (const db::Cell &cell, const db::Box &search_box, const db::ICplxTrans &t, int /*level*/); founds_vector_type m_founds; }; // ------------------------------------------------------------- // PartialShapeFinder implementation PartialShapeFinder::PartialShapeFinder (bool point_mode, bool top_level_sel, db::ShapeIterator::flags_type flags) : lay::ShapeFinder (point_mode, top_level_sel, flags) { set_test_count (point_sel_tests); } void PartialShapeFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const db::ICplxTrans &t, int /*level*/) { if (! point_mode ()) { for (std::vector::const_iterator l = layers ().begin (); l != layers ().end (); ++l) { if (layers ().size () == 1 || (layers ().size () > 1 && cell.bbox ((unsigned int) *l).touches (search_box))) { checkpoint (); const db::Shapes &shapes = cell.shapes (*l); db::ShapeIterator shape = shapes.begin_touching (search_box, flags (), prop_sel (), inv_prop_sel ()); while (! shape.at_end ()) { 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; std::vector &edges = m_founds.back ().second; 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); // 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; if (search_box.contains ((*e).p1 ())) { edges.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, c)); if (search_box.contains ((*e).p2 ())) { edges.push_back (EdgeWithIndex (*e, n, nn, c)); } } } } } else if (shape->is_path ()) { bool pl_set = false; db::Point pl; unsigned int n = 0; for (db::Shape::point_iterator pt = shape->begin_point (); pt != shape->end_point (); ++pt, ++n) { if (search_box.contains (*pt)) { edges.push_back (EdgeWithIndex (db::Edge (*pt, *pt), n, n, 0)); if (pl_set && search_box.contains (pl)) { edges.push_back (EdgeWithIndex (db::Edge (pl, *pt), n - 1, n, 0)); } } pl = *pt; pl_set = true; } } 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 = poly.begin_edge (); ! e.at_end (); e = ee, ++n) { ee = e; ++ee; unsigned int nn = ee.at_end () ? 0 : n + 1; if (search_box.contains ((*e).p1 ())) { edges.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, 0)); if (search_box.contains ((*e).p2 ())) { edges.push_back (EdgeWithIndex (*e, n, nn, 0)); } } } } else if (shape->is_text ()) { db::Point tp (shape->text_trans () * db::Point ()); if (search_box.contains (tp)) { edges.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); } } // do not select shapes that do not have at least one edge selected if (edges.empty ()) { m_founds.pop_back (); } ++shape; } } } } else { for (std::vector::const_iterator l = layers ().begin (); l != layers ().end (); ++l) { if (layers ().size () == 1 || (layers ().size () > 1 && cell.bbox ((unsigned int) *l).touches (search_box))) { checkpoint (); const db::Shapes &shapes = cell.shapes (*l); std::vector edge_sel; db::ShapeIterator shape = shapes.begin_touching (search_box, flags (), prop_sel (), inv_prop_sel ()); while (! shape.at_end ()) { bool match = false; double d = std::numeric_limits::max (); edge_sel.clear (); 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, 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 & 2) == 2) { 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), d, match); if (r) { 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)); } } 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 = 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 ()) { db::Point tp (shape->text_trans () * db::Point ()); if (search_box.contains (tp)) { d = tp.distance (search_box.center ()); edge_sel.clear (); edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0)); match = true; } } 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; } } } } } // ----------------------------------------------------------------------------- // Main Service implementation PartialService::PartialService (db::Manager *manager, lay::LayoutView *view, lay::PluginRoot *root) : QObject (), lay::ViewService (view->view_object_widget ()), lay::Editable (view), lay::Plugin (view), db::Object (manager), mp_view (view), mp_root (root), m_dragging (false), m_keep_selection (true), mp_box (0), m_color (0), m_buttons (0), m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), m_alt_ac (lay::AC_Global), m_snap_to_objects (true), m_top_level_sel (false), m_hover (false), m_hover_wait (false) { m_timer.setInterval (100 /*hover time*/); m_timer.setSingleShot (true); connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ())); } PartialService::~PartialService () { resize_markers (0, true); resize_markers (0, false); resize_inst_markers (0, true); resize_inst_markers (0, false); if (mp_box) { delete mp_box; mp_box = 0; } } lay::angle_constraint_type PartialService::connect_ac () const { // m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint return m_alt_ac != lay::AC_Global ? m_alt_ac : m_connect_ac; } lay::angle_constraint_type PartialService::move_ac () const { // m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint return m_alt_ac != lay::AC_Global ? m_alt_ac : m_move_ac; } void PartialService::deactivated () { // clear selection when this mode is left partial_select (db::DBox (), lay::Editable::Reset); clear_partial_transient_selection (); } void PartialService::activated () { // ... } void PartialService::hover_reset () { if (m_hover_wait) { m_timer.stop (); m_hover_wait = false; } if (m_hover) { clear_partial_transient_selection (); m_hover = false; } } void PartialService::timeout () { m_hover_wait = false; m_hover = true; mp_view->clear_transient_selection (); // compute search box double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag (); db::DBox search_box = db::DBox (m_hover_point, m_hover_point).enlarged (db::DVector (l, l)); PartialShapeFinder finder (true, m_top_level_sel, db::ShapeIterator::All); finder.find (view (), search_box); size_t n_marker = 0; size_t n_inst_marker = 0; if (finder.begin () != finder.end ()) { partial_objects transient_selection; transient_selection.insert (std::make_pair (finder.begin ()->first, std::set (finder.begin ()->second.begin (), finder.begin ()->second.end ()))); partial_objects::const_iterator r = transient_selection.begin (); // build the transformation variants cache TransformationVariants tv (view ()); const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); // compute the global transformation including context and explicit transformation db::ICplxTrans gt = (cv.context_trans () * r->first.trans ()); if (! r->first.is_cell_inst ()) { const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); if (tv_list && !tv_list->empty ()) { // dummy shift set std::map new_edges; std::map new_points; // create the markers to represent vertices and edges enter_vertices (n_marker, r, new_points, new_edges, gt, *tv_list, true); if (r->first.shape ().is_polygon ()) { for (unsigned int c = 0; c < r->first.shape ().holes () + 1; ++c) { unsigned int n = 0; db::Shape::polygon_edge_iterator ee; for (db::Shape::polygon_edge_iterator e = r->first.shape ().begin_edge (c); ! e.at_end (); e = ee, ++n) { ee = e; ++ee; unsigned int nn = ee.at_end () ? 0 : n + 1; enter_edge (EdgeWithIndex (*e, n, nn, c), n_marker, r, new_points, new_edges, gt, *tv_list, true); } } } else if (r->first.shape ().is_path ()) { if (r->first.shape ().begin_point () != r->first.shape ().end_point ()) { db::Shape::point_iterator pt = r->first.shape ().begin_point (); db::Point p1 = *pt; unsigned int n = 0; while (true) { ++pt; if (pt == r->first.shape ().end_point ()) { break; } enter_edge (EdgeWithIndex (db::Edge (p1, *pt), n, n + 1, 0), n_marker, r, new_points, new_edges, gt, *tv_list, true); p1 = *pt; ++n; } } } else if (r->first.shape ().is_box ()) { // convert to polygon and test those edges db::Polygon poly (r->first.shape ().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; enter_edge (EdgeWithIndex (*e, n, nn, 0), n_marker, r, new_points, new_edges, gt, *tv_list, true); } } else if (r->first.shape ().is_text ()) { db::Point tp (r->first.shape ().text_trans () * db::Point ()); enter_edge (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0), n_marker, r, new_points, new_edges, gt, *tv_list, true); } } } else { const std::vector *tv_list = tv.per_cv (r->first.cv_index ()); if (tv_list && ! tv_list->empty ()) { lay::InstanceMarker *marker = new_inst_marker (n_inst_marker, r->first.cv_index (), true); marker->set (r->first.back ().inst_ptr, gt, *tv_list); } } } // delete superfluous markers resize_markers (n_marker, true); resize_inst_markers (n_inst_marker, true); } void PartialService::clear_partial_transient_selection () { mp_view->clear_transient_selection (); resize_markers (0, true); resize_inst_markers (0, true); } void PartialService::set_colors (QColor /*background*/, QColor color) { m_color = color.rgb (); if (mp_box) { mp_box->set_color (m_color); } } void PartialService::menu_activated (const std::string & /*symbol*/) { // ... } bool PartialService::configure (const std::string &name, const std::string &value) { edt::EditGridConverter egc; edt::ACConverter acc; if (name == cfg_edit_global_grid) { egc.from_string (value, m_global_grid); } else if (name == cfg_edit_grid) { egc.from_string (value, m_edit_grid); return true; // taken } else if (name == cfg_edit_snap_to_objects) { tl::from_string (value, m_snap_to_objects); return true; // taken } else if (name == cfg_edit_move_angle_mode) { acc.from_string (value, m_move_ac); return true; // taken } else if (name == cfg_edit_connect_angle_mode) { acc.from_string (value, m_connect_ac); return true; // taken } else if (name == cfg_edit_top_level_selection) { tl::from_string (value, m_top_level_sel); } return false; // not taken } void PartialService::config_finalize () { // ... } db::DPoint PartialService::snap (const db::DPoint &p) const { // snap according to the grid if (m_edit_grid == db::DVector ()) { return lay::snap_xy (p, m_global_grid); } else if (m_edit_grid.x () >= 1e-6) { return lay::snap_xy (p, m_edit_grid); } else { return p; } } db::DVector PartialService::snap (const db::DVector &v) const { // snap according to the grid if (m_edit_grid == db::DVector ()) { return lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint (); } else if (m_edit_grid.x () >= 1e-6) { return lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint (); } else { return v; } } const int sr_pixels = 8; // TODO: make variable db::DPoint PartialService::snap2 (const db::DPoint &p) const { double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (sr_pixels); return lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range).second; } void PartialService::transform (const db::DCplxTrans &tr) { // ignore this function is non-editable mode if (! view ()->is_editable ()) { return; } // just allow displacements db::DTrans move_trans (tr.disp ()); transform_selection (move_trans); selection_to_view (); } void PartialService::transform_selection (const db::DTrans &move_trans) { // build the transformation variants cache TransformationVariants tv (view ()); // since a shape reference may become invalid while moving it and // because it creates ambiguities, we treat each shape separately: // collect the valid selected items in a selection-per-shape map. std::map > sel_per_shape; for (partial_objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r) { if (! r->first.is_cell_inst ()) { const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); if (tv_list && ! tv_list->empty ()) { sel_per_shape.insert (std::make_pair (r->first.shape (), std::vector ())).first->second.push_back (r); } } } for (std::map >::iterator sps = sel_per_shape.begin (); sps != sel_per_shape.end (); ++sps) { db::Shape shape = sps->first; for (std::vector::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) { partial_objects::iterator r = *rr; const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. db::ICplxTrans gt (cv.context_trans () * r->first.trans ()); const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * gt; db::Vector move_vector = db::Vector ((tt.inverted () * db::DCplxTrans (move_trans) * tt).disp ()); std::map new_edges; std::map new_points; create_shift_sets (shape, r->second, new_points, new_edges, move_vector); // modify the shapes and insert db::Shapes &shapes = cv->layout ().cell (r->first.cell_index ()).shapes (r->first.layer ()); if (shape.is_polygon ()) { db::Polygon poly; shape.polygon (poly); // warning: poly is modified: modify_polygon (poly, new_points, new_edges, true /*compress*/); shape = shapes.replace (shape, poly); } else if (shape.is_path ()) { db::Path path; shape.path (path); // warning: path is modified: modify_path (path, new_points, new_edges, true /*compress*/); shape = shapes.replace (shape, path); } else if (shape.is_box ()) { db::Polygon poly; shape.polygon (poly); // warning: poly is modified: modify_polygon (poly, new_points, new_edges, true /*compress*/); shape = shapes.replace (shape, poly.box ()); } else if (shape.is_text ()) { db::Text t; shape.text (t); db::Point tp (shape.text_trans () * db::Point ()); std::map ::const_iterator np = new_points.find (PointWithIndex (tp, 0, 0)); if (np != new_points.end ()) { t.transform (db::Trans (np->second - tp)); shape = shapes.replace (shape, t); } } // transform the selection std::set new_sel; new_sel.swap (r->second); for (std::set ::const_iterator s = new_sel.begin (); s != new_sel.end () && m_keep_selection; ++s) { if (s->p1 () == s->p2 ()) { std::map ::const_iterator np = new_points.find (s->pi1 ()); if (np != new_points.end ()) { r->second.insert (EdgeWithIndex (db::Edge (np->second, np->second), s->n, s->n, s->c)); } else { r->second.insert (*s); } } else { std::map ::const_iterator ne = new_edges.find (*s); if (ne != new_edges.end ()) { r->second.insert (EdgeWithIndex (ne->second, s->n, s->nn, s->c)); } else { r->second.insert (*s); } } } } // change the shape references if required if (shape != sps->first) { for (std::vector::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) { std::set sel; sel.swap ((*rr)->second); lay::ObjectInstPath inst_path ((*rr)->first); inst_path.set_shape (shape); m_selection.erase ((*rr)->first); m_selection.insert (std::make_pair (inst_path, std::set ())).first->second.swap (sel); } } } // then move all instances. // TODO: DTrans should have a ctor that takes a vector db::DTrans move_trans_inst = db::DTrans (db::DVector () + lay::snap_angle (m_current - m_start, move_ac ())); // sort the selected objects (the instances) by the cell they are in // The key is a pair: cell_index, cv_index std::map , std::vector > insts_by_cell; for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { if (r->first.is_cell_inst ()) { insts_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), r->first.cv_index ()), std::vector ())).first->second.push_back (r); } } std::vector > insts_to_transform; for (std::map , std::vector >::const_iterator ibc = insts_by_cell.begin (); ibc != insts_by_cell.end (); ++ibc) { insts_to_transform.clear (); insts_to_transform.reserve (ibc->second.size ()); for (std::vector ::const_iterator i = ibc->second.begin (); i != ibc->second.end (); ++i) { insts_to_transform.push_back (std::make_pair ((*i)->first.back ().inst_ptr, (*i)->first.trans ())); } const lay::CellView &cv = view ()->cellview (ibc->first.second); if (cv.is_valid ()) { const std::vector *tv_list = tv.per_cv (ibc->first.second); if (tv_list && ! tv_list->empty ()) { db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans (); db::ICplxTrans move_trans_dbu (tt.inverted () * db::DCplxTrans (move_trans_inst) * tt); std::sort (insts_to_transform.begin (), insts_to_transform.end ()); std::vector >::const_iterator unique_end = std::unique (insts_to_transform.begin (), insts_to_transform.end ()); db::Cell &cell = cv->layout ().cell (ibc->first.first); for (std::vector >::const_iterator inst = insts_to_transform.begin (); inst != unique_end; ++inst) { db::ICplxTrans mt (inst->second.inverted () * move_trans_dbu * inst->second); cell.transform (inst->first, mt); } } } } handle_guiding_shape_changes (); } void PartialService::edit_cancel () { // stop dragging, clear selection m_dragging = false; if (mp_box) { delete mp_box; mp_box = 0; } widget ()->ungrab_mouse (this); selection_to_view (); } bool PartialService::wheel_event (int /*delta*/, bool /*horizonal*/, const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) { hover_reset (); return false; } bool PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (m_dragging) { set_cursor (lay::Cursor::size_all); m_alt_ac = ac_from_buttons (buttons); // drag the vertex or edge/segment if (is_single_point_selection ()) { // for a single selected point, m_start is the original position and we snap the target - // thus, we can bring the point on grid or to an object's edge or vertex m_current = snap2 (p); } else { m_current = m_start + snap (p - m_start); } selection_to_view (); m_alt_ac = lay::AC_Global; } else if (prio) { if (mp_box) { m_alt_ac = ac_from_buttons (buttons); m_p2 = p; mp_box->set_points (m_p1, m_p2); m_alt_ac = lay::AC_Global; } else if (view ()->transient_selection_mode ()) { m_hover_wait = true; m_timer.start (); m_hover_point = p; } } // pass on this event to other handlers return false; } bool PartialService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) { hover_reset (); if (! view ()->is_editable ()) { return false; } // only respond to left button clicks if ((buttons & lay::LeftButton) == 0) { return false; } // only respond to first order events if (! prio) { return false; } if (m_dragging) { // eat events if already dragging return true; } else if (! mp_box) { m_alt_ac = ac_from_buttons (buttons); if (m_selection.empty ()) { // clear other selection when this mode gets active view ()->clear_selection (); // nothing is selected yet: // try to select something here. // (select is allowed to throw an exception) try { partial_select (db::DBox (p, p), lay::Editable::Replace); } catch (tl::Exception &ex) { tl::error << ex.msg (); QMessageBox::critical (0, QObject::tr ("Error"), tl::to_qstring (ex.msg ())); // clear selection partial_select (db::DBox (), lay::Editable::Reset); } } if (m_selection.empty () || ((buttons & lay::ShiftButton) != 0) || ((buttons & lay::ControlButton) != 0)) { // if nothing was selected by this point or Ctrl or Shift was pressed, start dragging a box view ()->stop_redraw (); // TODO: how to restart if selection is aborted? m_buttons = buttons; if (mp_box) { delete mp_box; } m_p1 = p; m_p2 = p; mp_box = new lay::RubberBox (widget (), m_color, p, p); mp_box->set_stipple (6); // coarse hatched widget ()->grab_mouse (this, true); } else { // something was selected: start dragging this .. m_dragging = true; m_keep_selection = true; if (is_single_point_selection ()) { // for a single selected point we use the original point as the start location which // allows to bring a to grid m_current = m_start = single_selected_point (); } else { m_current = m_start = p; } widget ()->grab_mouse (this, true); } m_alt_ac = lay::AC_Global; return true; } return false; } bool PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) { hover_reset (); if (! view ()->is_editable ()) { return false; } // only respond to left button clicks if ((buttons & lay::LeftButton) == 0) { return false; } // only respond to first order events if (! prio) { return false; } if (m_dragging) { m_alt_ac = ac_from_buttons (buttons); if (m_current != m_start) { // stop dragging widget ()->ungrab_mouse (this); manager ()->transaction (tl::to_string (QObject::tr ("Partial move"))); // heuristically, if there is just one edge selected: do not confine to the movement // angle constraint - the edge usually is confined enough db::DTrans move_trans; if (m_selection.size () == 1 && m_selection.begin ()->second.size () == 3 /*p1,p2,edge*/) { move_trans = db::DTrans (m_current - m_start); } else { // TODO: DTrans should have a ctor that takes a vector move_trans = db::DTrans (lay::snap_angle (m_current - m_start, move_ac ())); } transform_selection (move_trans); manager ()->commit (); } if (! m_keep_selection) { m_selection.clear (); } m_dragging = false; selection_to_view (); m_alt_ac = lay::AC_Global; return true; } else if (widget ()->mouse_event_viewport ().contains (p)) { // clear other selection when this mode gets active // (save the selection so our own selection does not get cleared) partial_objects selection = m_selection; view ()->clear_selection (); m_selection = selection; m_alt_ac = ac_from_buttons (buttons); lay::Editable::SelectionMode mode = lay::Editable::Replace; bool shift = ((buttons & lay::ShiftButton) != 0); bool ctrl = ((buttons & lay::ControlButton) != 0); if (shift && ctrl) { mode = lay::Editable::Invert; } else if (shift) { mode = lay::Editable::Add; } else if (ctrl) { mode = lay::Editable::Reset; } // select is allowed to throw an exception try { // compute search box double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag (); 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; } } } if (new_selection) { if (mode == lay::Editable::Replace) { m_selection.clear (); } // 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 (); if (f0 != finder.end () && f0->first.layer () == view ()->cellview (f0->first.cv_index ())->layout ().guiding_shape_layer ()) { m_selection.clear (); } else { partial_objects::const_iterator s0 = m_selection.begin (); if (s0 != m_selection.end () && s0->first.layer () == view ()->cellview (s0->first.cv_index ())->layout ().guiding_shape_layer ()) { m_selection.clear (); } } // collect the founds from the finder for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end (); ++f) { if (mode == lay::Editable::Replace || mode == lay::Editable::Add) { // select partial_objects::iterator sel = m_selection.find (f->first); if (sel == m_selection.end ()) { sel = m_selection.insert (std::make_pair (f->first, std::set ())).first; } sel->second.insert (f->second.begin (), f->second.end ()); } else if (mode == lay::Editable::Reset) { // unselect if (m_selection.find (f->first) != m_selection.end ()) { m_selection.erase (f->first); } } else { // invert selection if (m_selection.find (f->first) != m_selection.end ()) { m_selection.erase (f->first); } else { m_selection.insert (std::make_pair (f->first, std::set ())).first->second.insert (f->second.begin (), f->second.end ()); } } } } // start dragging with that single selection if (mode == lay::Editable::Replace && ! m_selection.empty ()) { m_dragging = true; m_keep_selection = ! new_selection; if (is_single_point_selection ()) { // for a single selected point we use the original point as the start location which // allows to bring a to grid m_current = m_start = single_selected_point (); } else { m_current = m_start = p; } } selection_to_view (); } catch (tl::Exception &ex) { tl::error << ex.msg (); QMessageBox::critical (0, QObject::tr ("Error"), tl::to_qstring (ex.msg ())); // clear selection partial_select (db::DBox (), lay::Editable::Reset); } m_alt_ac = lay::AC_Global; return true; } return false; } bool PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) { hover_reset (); if (! view ()->is_editable ()) { return false; } if ((buttons & lay::LeftButton) != 0 && prio) { m_alt_ac = ac_from_buttons (buttons); // stop dragging widget ()->ungrab_mouse (this); m_dragging = false; partial_select (db::DBox (p, p), lay::Editable::Replace); if (! m_selection.empty ()) { partial_objects::iterator r = m_selection.begin (); // we assert above that we have at least one selected element if (! r->first.is_cell_inst ()) { manager ()->transaction (tl::to_string (QObject::tr ("Insert point"))); // snap the point db::DPoint new_point_d = snap (p); // build the transformation variants cache TransformationVariants tv (view (), true /*per cv and layer*/, false /*per cv*/); const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); if (tv_list && ! tv_list->empty ()) { const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * (cv.context_trans () * r->first.trans ()); db::Point new_point = db::Point (tt.inverted () * new_point_d); // modify the shapes and replace db::Shapes &shapes = cv->layout ().cell (r->first.cell_index ()).shapes (r->first.layer ()); db::Shape shape = r->first.shape (); if (shape.is_polygon ()) { db::Polygon poly; shape.polygon (poly); db::Polygon new_poly; if (insert_point_poly (poly, r->second, new_point, new_poly)) { shape = shapes.replace (shape, new_poly); } } else if (shape.is_path ()) { db::Path path; shape.path (path); db::Path new_path; if (insert_point_path (path, r->second, new_point, new_path)) { shape = shapes.replace (shape, new_path); } } else if (shape.is_box ()) { // convert the box into a polygon unless the shape is on a guiding shape layer // (if it's a guiding shape we must not change it's nature) if (r->first.layer () != view ()->cellview (r->first.cv_index ())->layout ().guiding_shape_layer ()) { db::Polygon poly (shape.box ()); db::Polygon new_poly; if (insert_point_poly (poly, r->second, new_point, new_poly)) { shape = shapes.replace (shape, new_poly); } } } lay::ObjectInstPath obj = r->first; obj.set_shape (shape); m_selection.clear (); m_selection.insert (std::make_pair (obj, std::set ())); handle_guiding_shape_changes (); manager ()->commit (); selection_to_view (); } } } m_alt_ac = lay::AC_Global; return true; } else { return false; } } bool PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) { hover_reset (); if (prio && mp_box) { m_alt_ac = ac_from_buttons (buttons); widget ()->ungrab_mouse (this); delete mp_box; mp_box = 0; if (widget ()->mouse_event_viewport ().contains (p)) { lay::Editable::SelectionMode mode = lay::Editable::Replace; bool shift = ((m_buttons & lay::ShiftButton) != 0); bool ctrl = ((m_buttons & lay::ControlButton) != 0); if (shift && ctrl) { mode = lay::Editable::Invert; } else if (shift) { mode = lay::Editable::Add; } else if (ctrl) { mode = lay::Editable::Reset; } // select is allowed to throw an exception try { partial_select (db::DBox (m_p1, m_p2), mode); } catch (tl::Exception &ex) { tl::error << ex.msg (); QMessageBox::critical (0, QObject::tr ("Error"), tl::to_qstring (ex.msg ())); // clear selection partial_select (db::DBox (), lay::Editable::Reset); } } m_alt_ac = lay::AC_Global; return true; } return false; } size_t PartialService::selection_size () { return m_selection.size (); } void PartialService::del () { // stop dragging widget ()->ungrab_mouse (this); std::map >, std::vector > shapes_to_delete_by_cell; for (partial_objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r) { if (! r->first.is_cell_inst ()) { const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); // modify the shapes and replace db::Shapes &shapes = cv->layout ().cell (r->first.cell_index ()).shapes (r->first.layer ()); db::Shape shape = r->first.shape (); if (shape.is_polygon ()) { db::Polygon poly; shape.polygon (poly); db::Polygon new_poly = del_points_poly (poly, r->second); if (new_poly.hull ().size () < 3) { shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector ())).first->second.push_back (r); } else { shapes.replace (shape, new_poly); } } else if (shape.is_path ()) { db::Path path; shape.path (path); db::Path new_path = del_points_path (path, r->second); if (new_path.points () < 2) { shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector ())).first->second.push_back (r); } else { shapes.replace (shape, new_path); } } else if (shape.is_box ()) { // if more than one point is deleted, the box basically collapses, if one point is deleted // nothing changes on the box. if (r->second.size () > 1) { shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector ())).first->second.push_back (r); } } else if (shape.is_text ()) { shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector ())).first->second.push_back (r); } } } // delete all shapes that are really lost std::vector shapes_to_delete; for (std::map >, std::vector >::const_iterator sbc = shapes_to_delete_by_cell.begin (); sbc != shapes_to_delete_by_cell.end (); ++sbc) { const lay::CellView &cv = view ()->cellview (sbc->first.second.first); if (cv.is_valid ()) { // don't delete guiding shapes if (sbc->first.second.second != cv->layout ().guiding_shape_layer ()) { for (std::vector ::const_iterator s = sbc->second.begin (); s != sbc->second.end (); ++s) { cv->layout ().cell (sbc->first.first).shapes (sbc->first.second.second).erase_shape ((*s)->first.shape ()); } } } } // then delete all instances. // sort the selected objects (the instances) by the cell they are in // The key is a pair: cell_index, cv_index std::map , std::vector > insts_by_cell; for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { if (r->first.is_cell_inst ()) { const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); if (cv.is_valid ()) { cv->layout ().cell (r->first.cell_index ()).erase (r->first.back ().inst_ptr); } } } // Hint: calling this method is somewhat dangerous since the selection is not necessarily valid (the shapes // may have been deleted). However, since we did not delete guiding shapes before and this method in particular // handles guiding shapes, this should be fairly safe. handle_guiding_shape_changes (); m_selection.clear (); m_dragging = false; selection_to_view (); } lay::InstanceMarker * PartialService::new_inst_marker (size_t &nmarker, unsigned int cv_index, bool transient) { lay::InstanceMarker *marker; if (transient) { if (nmarker >= m_transient_inst_markers.size ()) { marker = new lay::InstanceMarker (view (), cv_index); m_transient_inst_markers.push_back (marker); } else { marker = m_transient_inst_markers [nmarker]; } } else { if (nmarker >= m_inst_markers.size ()) { marker = new lay::InstanceMarker (view (), cv_index); m_inst_markers.push_back (marker); } else { marker = m_inst_markers [nmarker]; } } ++nmarker; return marker; } lay::Marker * PartialService::new_marker (size_t &nmarker, unsigned int cv_index, bool transient) { lay::Marker *marker; if (transient) { if (nmarker >= m_transient_markers.size ()) { marker = new lay::Marker (view (), cv_index); m_transient_markers.push_back (marker); } else { marker = m_transient_markers [nmarker]; } } else { if (nmarker >= m_markers.size ()) { marker = new lay::Marker (view (), cv_index); m_markers.push_back (marker); } else { marker = m_markers [nmarker]; } } ++nmarker; if (transient) { marker->set_vertex_size (0); marker->set_dither_pattern (1 /*hollow*/); marker->set_frame_pattern (0 /*solid*/); marker->set_line_width (1); marker->set_halo (false); } else { marker->set_vertex_size (-1 /*default*/); marker->set_dither_pattern (1 /*hollow*/); marker->set_frame_pattern (0 /*solid*/); marker->set_line_width (-1 /*default*/); marker->set_halo (-1 /*default*/); } return marker; } void PartialService::enter_path (db::Path &p, size_t &nmarker, partial_objects::const_iterator sel, const std::map &new_points, const std::map &new_edges, const db::ICplxTrans >, const std::vector &tv, bool transient) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_dither_pattern (3 /*dotted*/); marker->set_frame_pattern (2 /*dotted*/); marker->set_line_width (1); marker->set_halo (0); modify_path (p, new_points, new_edges); marker->set (p, gt, tv); } void PartialService::enter_polygon (db::Polygon &p, size_t &nmarker, partial_objects::const_iterator sel, const std::map &new_points, const std::map &new_edges, const db::ICplxTrans >, const std::vector &tv, bool transient) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_dither_pattern (3 /*dotted*/); marker->set_frame_pattern (2 /*dotted*/); marker->set_line_width (1); marker->set_halo (0); modify_polygon (p, new_points, new_edges); marker->set (p, gt, tv); } void PartialService::enter_vertices (size_t &nmarker, partial_objects::const_iterator sel, const std::map &new_points, const std::map & /*new_edges*/, const db::ICplxTrans >, const std::vector &tv, bool transient) { // TODO: create vertex markers only for vertices that are not for an edge // and use "fat" vertices on the edge markers. for (std::set ::const_iterator e = sel->second.begin (); e != sel->second.end (); ++e) { if (e->p1 () == e->p2 ()) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); db::Point pnew = e->p1 (); std::map ::const_iterator np = new_points.find (PointWithIndex (pnew, e->n, e->c)); if (np != new_points.end ()) { pnew = np->second; } marker->set (db::Edge (pnew, pnew), gt, tv); } } } void PartialService::enter_edge (const EdgeWithIndex &e, size_t &nmarker, partial_objects::const_iterator sel, const std::map &new_points, const std::map &new_edges, const db::ICplxTrans >, const std::vector &tv, bool transient) { db::Point ep1 (e.p1 ()); db::Point ep2 (e.p2 ()); bool p1_sel = sel->second.find (EdgeWithIndex (db::Edge (ep1, ep1), e.n, e.n, e.c)) != sel->second.end (); bool p2_sel = sel->second.find (EdgeWithIndex (db::Edge (ep2, ep2), e.nn, e.nn, e.c)) != sel->second.end (); bool p12_sel = sel->second.find (e) != sel->second.end (); if (p1_sel || p2_sel || p12_sel) { // map points to moved ones std::map ::const_iterator np; np = new_points.find (e.pi1 ()); if (np != new_points.end ()) { ep1 = np->second; } np = new_points.find (e.pi2 ()); if (np != new_points.end ()) { ep2 = np->second; } db::Edge enew (ep1, ep2); std::map ::const_iterator ne; ne = new_edges.find (e); if (ne != new_edges.end ()) { enew = ne->second; if (enew.p1 () != ep1) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_vertex_size (0); marker->set (db::Edge (ep1, enew.p1 ()), gt, tv); } if (enew.p2 () != ep2) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_vertex_size (0); marker->set (db::Edge (enew.p2 (), ep2), gt, tv); } } if (p2_sel && !p12_sel) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_vertex_size (0); 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 (p1_sel && !p12_sel) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_vertex_size (0); 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 (p12_sel) { lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient); marker->set_vertex_size (0); marker->set (enew, gt, tv); } } } db::DPoint PartialService::single_selected_point () const { // build the transformation variants cache and // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (m_selection.begin ()->first.cv_index (), m_selection.begin ()->first.layer ()); const lay::CellView &cv = view ()->cellview (m_selection.begin ()->first.cv_index ()); db::ICplxTrans gt (cv.context_trans () * m_selection.begin ()->first.trans ()); db::CplxTrans tt = (*tv_list)[0] * db::CplxTrans (cv->layout ().dbu ()) * gt; return tt * m_selection.begin ()->second.begin ()->p1 (); } bool PartialService::is_single_point_selection () const { return (m_selection.size () == 1 && ! m_selection.begin ()->first.is_cell_inst () && m_selection.begin ()->second.size () == 1 /*p*/); } bool PartialService::select (const db::DBox &box, SelectionMode mode) { if (box.empty () && mode == lay::Editable::Reset) { // clear selection m_selection.clear (); selection_to_view (); } return false; } void PartialService::selection_to_view () { // if dragging, establish the current displacement db::DTrans move_trans; if (m_dragging) { // heuristically, if there is just one edge selected: do not confine to the movement // angle constraint - the edge usually is confined enough if (m_selection.size () == 1 && ! m_selection.begin ()->first.is_cell_inst () && m_selection.begin ()->second.size () == 3 /*p1,p2,edge*/) { move_trans = db::DTrans (m_current - m_start); } else { // TODO: DTrans should have a ctor that takes a vector move_trans = db::DTrans (lay::snap_angle (m_current - m_start, move_ac ())); } // display vector view ()->message (std::string ("dx: ") + tl::micron_to_string (move_trans.disp ().x ()) + std::string (" dy: ") + tl::micron_to_string (move_trans.disp ().y ()) + std::string (" d: ") + tl::micron_to_string (move_trans.disp ().length ())); } // build the transformation variants cache TransformationVariants tv (view ()); size_t n_marker = 0; size_t n_inst_marker = 0; for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); if (! r->first.is_cell_inst ()) { const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); if (tv_list && !tv_list->empty ()) { // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. db::ICplxTrans gt (cv.context_trans () * r->first.trans ()); db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * gt; db::Vector move_vector (tt.inverted () * (move_trans * (tt * db::Point ()))); // create the shift sets describing how points and edges are being moved std::map new_edges; std::map new_points; if (m_dragging) { create_shift_sets (r->first.shape (), r->second, new_points, new_edges, move_vector); } // create the markers to represent vertices and edges enter_vertices (n_marker, r, new_points, new_edges, gt, *tv_list, false); if (r->first.shape ().is_polygon ()) { for (unsigned int c = 0; c < r->first.shape ().holes () + 1; ++c) { unsigned int n = 0; db::Shape::polygon_edge_iterator ee; for (db::Shape::polygon_edge_iterator e = r->first.shape ().begin_edge (c); ! e.at_end (); e = ee, ++n) { ee = e; ++ee; unsigned int nn = ee.at_end () ? 0 : n + 1; enter_edge (EdgeWithIndex (*e, n, nn, c), n_marker, r, new_points, new_edges, gt, *tv_list, false); } } db::Polygon poly; r->first.shape ().polygon (poly); // warning: poly is modified: enter_polygon (poly, n_marker, r, new_points, new_edges, gt, *tv_list, false); } else if (r->first.shape ().is_path ()) { if (r->first.shape ().begin_point () != r->first.shape ().end_point ()) { db::Shape::point_iterator pt = r->first.shape ().begin_point (); db::Point p1 = *pt; unsigned int n = 0; while (true) { ++pt; if (pt == r->first.shape ().end_point ()) { break; } enter_edge (EdgeWithIndex (db::Edge (p1, *pt), n, n + 1, 0), n_marker, r, new_points, new_edges, gt, *tv_list, false); p1 = *pt; ++n; } // TODO: ... put this somewhere else: db::Path path; r->first.shape ().path (path); // warning: path is modified: enter_path (path, n_marker, r, new_points, new_edges, gt, *tv_list, false); } } else if (r->first.shape ().is_box ()) { // convert to polygon and test those edges db::Polygon poly (r->first.shape ().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; enter_edge (EdgeWithIndex (*e, n, nn, 0), n_marker, r, new_points, new_edges, gt, *tv_list, false); } // warning: poly is modified: enter_polygon (poly, n_marker, r, new_points, new_edges, gt, *tv_list, false); } else if (r->first.shape ().is_text ()) { db::Point tp (r->first.shape ().text_trans () * db::Point ()); enter_edge (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0), n_marker, r, new_points, new_edges, gt, *tv_list, false); } } } else { // compute the global transformation including movement, context and explicit transformation db::ICplxTrans gt = db::VCplxTrans (cv->layout ().dbu ()) * db::DCplxTrans (move_trans) * db::CplxTrans (cv->layout ().dbu ()); gt *= (cv.context_trans () * r->first.trans ()); const std::vector *tv_list = tv.per_cv (r->first.cv_index ()); if (tv_list && ! tv_list->empty ()) { lay::InstanceMarker *marker = new_inst_marker (n_inst_marker, r->first.cv_index (), false); marker->set (r->first.back ().inst_ptr, gt, *tv_list); } } } // delete superfluous markers resize_markers (n_marker, false); resize_inst_markers (n_inst_marker, false); } void PartialService::resize_markers (size_t n, bool transient) { if (transient) { for (std::vector::iterator r = m_transient_markers.begin () + n; r != m_transient_markers.end (); ++r) { delete *r; } m_transient_markers.erase (m_transient_markers.begin () + n, m_transient_markers.end ()); } else { for (std::vector::iterator r = m_markers.begin () + n; r != m_markers.end (); ++r) { delete *r; } m_markers.erase (m_markers.begin () + n, m_markers.end ()); } } void PartialService::resize_inst_markers (size_t n, bool transient) { if (transient) { for (std::vector::iterator r = m_transient_inst_markers.begin () + n; r != m_transient_inst_markers.end (); ++r) { delete *r; } m_transient_inst_markers.erase (m_transient_inst_markers.begin () + n, m_transient_inst_markers.end ()); } else { for (std::vector::iterator r = m_inst_markers.begin () + n; r != m_inst_markers.end (); ++r) { delete *r; } m_inst_markers.erase (m_inst_markers.begin () + n, m_inst_markers.end ()); } } bool PartialService::partial_select (const db::DBox &box, lay::Editable::SelectionMode mode) { clear_partial_transient_selection (); // compute search box double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag (); db::DBox search_box = box.enlarged (db::DVector (l, l)); bool needs_update = false; bool any_selected = false; // clear before unless "add" is selected if (mode == lay::Editable::Replace) { if (! m_selection.empty ()) { m_selection.clear (); needs_update = true; } } if (box.empty ()) { // unconditional selection if (mode == lay::Editable::Reset) { if (! m_selection.empty ()) { m_selection.clear (); needs_update = true; } } else { // extract all shapes // ... not implemented yet ... } } else { PartialShapeFinder finder (box.is_point (), m_top_level_sel, db::ShapeIterator::All); finder.find (view (), search_box); // We must make sure that guiding shapes are only selected alone. The first selected object will // determine whether we take guiding shapes into account or not. bool gs_mode = (finder.begin () != finder.end () && finder.begin ()->first.layer () == view ()->cellview (finder.begin ()->first.cv_index ())->layout ().guiding_shape_layer ()); // Clear the selection if it was consisting of a guiding shape or non-guiding shape before (depending on mode). // This way we make sure there is not mixture between guiding shapes and others. if (m_selection.begin () != m_selection.end ()) { if (gs_mode != (m_selection.begin ()->first.layer () == view ()->cellview (m_selection.begin ()->first.cv_index ())->layout ().guiding_shape_layer ())) { m_selection.clear (); needs_update = true; } } // collect the founds from the finder for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end (); ++f) { if (gs_mode == (f->first.layer () == view ()->cellview (f->first.cv_index ())->layout ().guiding_shape_layer ())) { if (mode == lay::Editable::Replace || mode == lay::Editable::Add) { // select partial_objects::iterator sel = m_selection.find (f->first); if (sel == m_selection.end ()) { sel = m_selection.insert (std::make_pair (f->first, std::set ())).first; } sel->second.insert (f->second.begin (), f->second.end ()); } else if (mode == lay::Editable::Reset) { // unselect if (m_selection.find (f->first) != m_selection.end ()) { m_selection.erase (f->first); } } else { // invert selection if (m_selection.find (f->first) != m_selection.end ()) { m_selection.erase (f->first); } else { m_selection.insert (std::make_pair (f->first, std::set ())).first->second.insert (f->second.begin (), f->second.end ()); } } needs_update = true; any_selected = true; } } // check, if there is a selected instance inside the box - in this case, we do not do a new selection if (! box.is_point ()) { lay::InstFinder inst_finder (box.is_point (), m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no exludes*/, true /*visible layers*/); inst_finder.find (view (), search_box); // collect the founds from the finder for (lay::InstFinder::iterator f = inst_finder.begin (); f != inst_finder.end (); ++f) { if (mode == lay::Editable::Replace || mode == lay::Editable::Add) { // select m_selection.insert (std::make_pair (*f, std::set ())); } else if (mode == lay::Editable::Reset) { // unselect if (m_selection.find (*f) != m_selection.end ()) { m_selection.erase (*f); } } else { // invert selection if (m_selection.find (*f) != m_selection.end ()) { m_selection.erase (*f); } else { m_selection.insert (std::make_pair (*f, std::set ())); } } needs_update = true; any_selected = true; } } } // if required, update the list of objects to display the selection if (needs_update) { selection_to_view (); } return any_selected; } bool PartialService::handle_guiding_shape_changes () { // just allow one guiding shape to be selected if (m_selection.empty ()) { return false; } partial_objects::const_iterator s = m_selection.begin (); unsigned int cv_index = s->first.cv_index (); lay::CellView cv = view ()->cellview (cv_index); db::Layout *layout = &cv->layout (); if (s->first.is_cell_inst () || s->first.layer () != layout->guiding_shape_layer ()) { return false; } if (! s->first.shape ().has_prop_id ()) { return false; } if (! layout->is_pcell_instance (s->first.cell_index ()).first) { return false; } db::cell_index_type top_cell = std::numeric_limits::max (); db::cell_index_type parent_cell = std::numeric_limits::max (); db::Instance parent_inst; db::pcell_parameters_type parameters_for_pcell; // determine parent cell and instance if required lay::ObjectInstPath::iterator e = s->first.end (); if (e == s->first.begin ()) { top_cell = s->first.cell_index (); } else { --e; db::cell_index_type pc = s->first.topcell (); if (e != s->first.begin ()) { --e; pc = e->inst_ptr.cell_index (); } parent_cell = pc; parent_inst = s->first.back ().inst_ptr; } db::property_names_id_type pn = layout->properties_repository ().prop_name_id ("name"); const db::PropertiesRepository::properties_set &input_props = layout->properties_repository ().properties (s->first.shape ().prop_id ()); db::PropertiesRepository::properties_set::const_iterator input_pv = input_props.find (pn); if (input_pv == input_props.end ()) { return false; } std::string shape_name = input_pv->second.to_string (); // Hint: get_parameters_from_pcell_and_guiding_shapes invalidates the shapes because it resets the changed // guiding shapes. We must not access s->shape after that. if (! get_parameters_from_pcell_and_guiding_shapes (layout, s->first.cell_index (), parameters_for_pcell)) { return false; } partial_objects new_sel; if (parent_cell != std::numeric_limits ::max ()) { db::Instance new_inst = layout->cell (parent_cell).change_pcell_parameters (parent_inst, parameters_for_pcell); // try to identify the selected shape in the new shapes and select this one db::Shapes::shape_iterator sh = layout->cell (new_inst.cell_index ()).shapes (layout->guiding_shape_layer ()).begin (db::ShapeIterator::All); while (! sh.at_end ()) { const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (sh->prop_id ()); db::PropertiesRepository::properties_set::const_iterator pv = props.find (pn); if (pv != props.end ()) { if (pv->second.to_string () == shape_name) { lay::ObjectInstPath inst_path = s->first; inst_path.back ().inst_ptr = new_inst; inst_path.back ().array_inst = new_inst.begin (); inst_path.set_shape (*sh); new_sel.insert (std::make_pair (inst_path, s->second)); break; } } ++sh; } } if (top_cell != std::numeric_limits ::max ()) { // TODO: implement the case of a PCell variant being a top cell // Currently there is not way to create such a configuration ... } // remove superfluous proxies layout->cleanup (); m_selection = new_sel; selection_to_view (); return true; } } // namespace edt