/* 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 "tlProgress.h" #include "layFinder.h" namespace lay { /** * @brief A pretty heuristic method to determine the "enclosing distance" of a polygon around a point */ template double poly_dist (Iter edge, const Point &pt) { double distance = std::numeric_limits::max (); while (! edge.at_end ()) { std::pair ret = (*edge).projected (pt); if (ret.first) { double d (ret.second.distance (pt)); if (d < distance) { distance = d; } } ++edge; } return distance; } // ------------------------------------------------------------- // max. number of tries in single-click selection before giving up static int point_sel_tests = 10000; // max. number of tries in single-click selection before giving up static int inst_point_sel_tests = 10000; // ---------------------------------------------------------------------- // Finder implementation Finder::Finder (bool point_mode, bool top_level_sel) : m_min_level (0), m_max_level (0), mp_layout (0), mp_view (0), m_cv_index (0), m_point_mode (point_mode), m_top_level_sel (top_level_sel) { m_distance = std::numeric_limits::max (); } Finder::~Finder () { // .. nothing yet .. } bool Finder::closer (double d) { // the proximity is checked and delivered in micron units. d *= mp_view->cellview (m_cv_index)->layout ().dbu (); if (d <= m_distance) { m_distance = d; return true; } else { return false; } } void Finder::start (lay::LayoutView *view, const lay::CellView &cv, unsigned int cv_index, const std::vector &trans, const db::Box ®ion, int min_level, int max_level, const std::vector &layers) { m_layers = layers; m_region = region; mp_layout = &cv->layout (); mp_view = view; m_cv_index = cv_index; m_min_level = std::max (0, min_level); m_max_level = std::max (m_min_level, std::min (max_level, m_top_level_sel ? ((int) cv.specific_path ().size () + 1) : max_level)); if (layers.size () == 1) { m_box_convert = db::box_convert (*mp_layout, (unsigned int) layers [0]); m_cell_box_convert = db::box_convert ((unsigned int) layers [0]); } else { m_box_convert = db::box_convert (*mp_layout); m_cell_box_convert = db::box_convert (); } m_path.erase (m_path.begin (), m_path.end ()); for (std::vector::const_iterator t = trans.begin (); t != trans.end (); ++t) { do_find (*cv.cell (), cv.specific_path ().size (), *t * cv.context_trans ()); } } unsigned int Finder::test_edge (const db::Edge &edg, double &distance, bool &match) { unsigned int ret = 0; // we hit the region with the edge end points - take the closest vertex if (m_region.contains (edg.p1 ()) || m_region.contains (edg.p2 ())) { double d1 = edg.p1 ().double_distance (m_region.center ()); double d2 = edg.p2 ().double_distance (m_region.center ()); // snap to the point - nothing can get closer distance = 0.0; if (d1 < d2) { ret = 1; } else { ret = 2; } match = true; } // if the edge cuts through the active region: test the // edge as a whole if (ret == 0 && edg.clipped (m_region).first) { double d = edg.distance_abs (m_region.center ()); if (! match || d < distance) { distance = d; ret = 3; } match = true; } return ret; } void Finder::do_find (const db::Cell &cell, int level, const db::ICplxTrans &t) { if (level <= m_max_level /*take level of cell itself*/ && cell.is_proxy () && m_layers.size () == 1 && (unsigned int) m_layers [0] == mp_layout->guiding_shape_layer ()) { // when looking at the guiding shape layer, we can visit this cell as well allowing to find the guiding shapes db::Box touch_box (t.inverted () * m_region); if (level >= m_min_level) { visit_cell (cell, touch_box, t, level); } } else if (level < m_max_level && (t * m_cell_box_convert (cell)).touches (m_region) && (mp_view->select_inside_pcells_mode () || !cell.is_proxy ()) && !mp_view->is_cell_hidden (cell.cell_index (), m_cv_index)) { db::Box touch_box (t.inverted () * m_region); if (level >= m_min_level) { visit_cell (cell, touch_box, t, level); } db::Cell::touching_iterator inst = cell.begin_touching (touch_box); while (! inst.at_end ()) { const db::CellInstArray &cell_inst = inst->cell_inst (); for (db::CellInstArray::iterator p = cell_inst.begin_touching (touch_box, m_box_convert); ! p.at_end (); ++p) { m_path.push_back (db::InstElement (*inst, p)); do_find (mp_layout->cell (cell_inst.object ().cell_index ()), level + 1, t * cell_inst.complex_trans (*p)); m_path.pop_back (); } ++inst; } } } // ------------------------------------------------------------- // ShapeFinder implementation ShapeFinder::ShapeFinder (bool point_mode, bool top_level_sel, db::ShapeIterator::flags_type flags, const std::set *excludes) : Finder (point_mode, top_level_sel), mp_excludes ((excludes && !excludes->empty ()) ? excludes : 0), m_flags (flags), m_cv_index (0), m_topcell (0), mp_prop_sel (0), m_inv_prop_sel (false), mp_progress (0) { m_tries = point_sel_tests; } struct LPContextEqualOp { bool operator() (const lay::LayerPropertiesConstIterator &a, const lay::LayerPropertiesConstIterator &b) { if (a->cellview_index () != b->cellview_index ()) { return false; } if (a->inverse_prop_sel () != b->inverse_prop_sel ()) { return false; } if (a->prop_sel () != b->prop_sel ()) { return false; } if (a->trans () != b->trans ()) { return false; } if (a->hier_levels () != b->hier_levels ()) { return false; } return true; } }; struct LPContextCompareOp { bool operator() (const lay::LayerPropertiesConstIterator &a, const lay::LayerPropertiesConstIterator &b) { if (a->cellview_index () != b->cellview_index ()) { return a->cellview_index () < b->cellview_index (); } if (a->inverse_prop_sel () != b->inverse_prop_sel ()) { return a->inverse_prop_sel () < b->inverse_prop_sel (); } if (a->prop_sel () != b->prop_sel ()) { return a->prop_sel () < b->prop_sel (); } if (a->trans () != b->trans ()) { return a->trans () < b->trans (); } if (a->hier_levels () != b->hier_levels ()) { return a->hier_levels () < b->hier_levels (); } return false; } }; bool ShapeFinder::find (lay::LayoutView *view, const db::DBox ®ion_mu) { tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Selecting ..."))); progress.set_unit (1000); progress.set_format (""); mp_progress = &progress; m_context_layers.clear (); m_cells_with_context.clear (); std::vector lprops; for (lay::LayerPropertiesConstIterator lp = view->begin_layers (); ! lp.at_end (); ++lp) { if (lp->is_visual ()) { lprops.push_back (lp); } } std::sort (lprops.begin (), lprops.end (), LPContextCompareOp ()); std::vector layers; for (std::vector::const_iterator llp = lprops.begin (); llp != lprops.end (); ) { layers.clear (); lay::LayerPropertiesConstIterator lp0 = *llp; LPContextEqualOp eq; do { layers.push_back ((*llp)->layer_index ()); ++llp; } while (llp != lprops.end () && eq(lp0, *llp)); find_internal (view, lp0->cellview_index (), &lp0->prop_sel (), lp0->inverse_prop_sel (), lp0->hier_levels (), lp0->trans (), layers, region_mu); } // search on the guiding shapes layer as well // Use the visible layers for the context: the guiding shape is only looked up for cells // having one of these layers m_context_layers.clear (); for (std::vector::const_iterator llp = lprops.begin (); llp != lprops.end (); ++llp) { m_context_layers.push_back ((*llp)->layer_index ()); } std::set< std::pair > variants = view->cv_transform_variants (); for (std::set< std::pair >::const_iterator v = variants.begin (); v != variants.end (); ++v) { layers.clear (); layers.push_back (view->cellview (v->second)->layout ().guiding_shape_layer ()); std::vector trans; trans.push_back (v->first); find_internal (view, (unsigned int) v->second, 0, false, lay::HierarchyLevelSelection (), trans, layers, region_mu); } mp_progress = 0; m_cells_with_context.clear (); m_context_layers.clear (); return ! m_founds.empty (); } bool ShapeFinder::find (lay::LayoutView *view, const lay::LayerProperties &lprops, const db::DBox ®ion_mu) { tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Selecting ..."))); progress.set_unit (1000); progress.set_format (""); mp_progress = &progress; m_cells_with_context.clear (); m_context_layers.clear (); std::vector layers; layers.push_back (lprops.layer_index ()); bool result = find_internal (view, lprops.cellview_index (), &lprops.prop_sel (), lprops.inverse_prop_sel (), lprops.hier_levels (), lprops.trans (), layers, region_mu); mp_progress = 0; return result; } bool ShapeFinder::find_internal (lay::LayoutView *view, unsigned int cv_index, const std::set *prop_sel, bool inv_prop_sel, const lay::HierarchyLevelSelection &hier_sel, const std::vector &trans_mu, const std::vector &layers, const db::DBox ®ion_mu) { m_cv_index = cv_index; const lay::CellView &cv = view->cellview (m_cv_index); if (! cv.is_valid ()) { return false; } m_topcell = cv.cell_index (); double dbu = cv->layout ().dbu (); db::Box region = db::VCplxTrans (1.0 / dbu) * region_mu; std::vector trans; trans.reserve(trans_mu.size()); for (std::vector::const_iterator t = trans_mu.begin(); t != trans_mu.end(); ++t) { trans.push_back(db::VCplxTrans(1.0 / dbu) * *t * db::CplxTrans(dbu)); } mp_prop_sel = prop_sel; m_inv_prop_sel = inv_prop_sel; int ctx_path_length = int (cv.specific_path ().size ()); int min_level = view->get_min_hier_levels (); int max_level = view->get_max_hier_levels (); if (hier_sel.has_from_level ()) { min_level = hier_sel.from_level (ctx_path_length, min_level); } if (hier_sel.has_to_level ()) { max_level = hier_sel.to_level (ctx_path_length, max_level); } // actually find try { start (view, cv, m_cv_index, trans, region, min_level, max_level, layers); } catch (StopException) { // .. } // return true if anything was found return ! m_founds.empty (); } void ShapeFinder::checkpoint () { if (! point_mode ()) { ++*mp_progress; } else { if (--m_tries < 0) { throw StopException (); } } } void ShapeFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const db::ICplxTrans &t, int /*level*/) { if (! m_context_layers.empty ()) { std::map::const_iterator ctx = m_cells_with_context.find (cell.cell_index ()); if (ctx == m_cells_with_context.end ()) { bool has_context = false; for (std::vector::const_iterator l = m_context_layers.begin (); l != m_context_layers.end () && ! has_context; ++l) { if (! cell.bbox ((unsigned int) *l).empty ()) { has_context = true; } } ctx = m_cells_with_context.insert (std::make_pair (cell.cell_index (), has_context)).first; } if (! ctx->second) { return; } } if (! point_mode ()) { checkpoint (); 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))) { const db::Shapes &shapes = cell.shapes ((unsigned int) *l); db::ShapeIterator shape = shapes.begin_touching (search_box, m_flags, mp_prop_sel, m_inv_prop_sel); while (! shape.at_end ()) { checkpoint (); // in box mode, just test the boxes if (shape->bbox ().inside (search_box)) { m_founds.push_back (lay::ObjectInstPath ()); m_founds.back ().set_cv_index (m_cv_index); m_founds.back ().set_topcell (m_topcell); m_founds.back ().assign_path (path ().begin (), path ().end ()); m_founds.back ().set_layer (*l); m_founds.back ().set_shape (*shape); // Remove the selection if it's part of the excluded set if (mp_excludes != 0 && mp_excludes->find (m_founds.back ()) != mp_excludes->end ()) { 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); db::ShapeIterator shape = shapes.begin_touching (search_box, m_flags, mp_prop_sel, m_inv_prop_sel); while (! shape.at_end ()) { bool match = false; double d = std::numeric_limits::max (); checkpoint (); db::Point point (search_box.center ()); // in point mode, test the edges and use a "closest" criterion if (shape->is_polygon ()) { for (db::Shape::polygon_edge_iterator e = shape->begin_edge (); ! e.at_end (); ++e) { test_edge (t * *e, d, match); } // test if inside the polygon if (! match && inside_poly (shape->begin_edge (), point) >= 0) { d = t.ctrans (poly_dist (shape->begin_edge (), point)); match = true; } } 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; for (; pt != shape->end_point (); ++pt) { test_edge (t * db::Edge (p, *pt), d, match); p = *pt; } } // convert to polygon and test those edges db::Polygon poly; shape->polygon (poly); for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) { test_edge (t * *e, d, match); } // test if inside the polygon if (! match && inside_poly (poly.begin_edge (), point) >= 0) { d = t.ctrans (poly_dist (poly.begin_edge (), point)); match = true; } } else if (shape->is_box ()) { const db::Box &box = shape->box (); // point-like boxes are handles which attract the finder if (box.width () == 0 && box.height () == 0) { d = 0.0; match = true; } else { // convert to polygon and test those edges db::Polygon poly (box); for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) { test_edge (t * *e, d, match); } if (! match && box.contains (search_box.center ())) { d = t.ctrans (poly_dist (poly.begin_edge (), point)); match = true; } } } else if (shape->is_text ()) { db::Point tp (shape->text_trans () * db::Point ()); if (search_box.contains (tp)) { d = t.ctrans (tp.distance (search_box.center ())); match = true; } } if (match) { if (mp_excludes) { // with an exclude list first create the selection item so we can check // if it's part of the exclude set. lay::ObjectInstPath found; found.set_cv_index (m_cv_index); found.set_topcell (m_topcell); found.assign_path (path ().begin (), path ().end ()); found.set_layer (*l); found.set_shape (*shape); // in point mode just store the found object that has the least "distance" and is // not in the exclude set if (mp_excludes->find (found) == mp_excludes->end () && closer (d)) { if (m_founds.empty ()) { m_founds.push_back (found); } else { m_founds.front () = found; } } } else if (closer (d)) { // in point mode just store that found that has the least "distance" if (m_founds.empty ()) { m_founds.push_back (lay::ObjectInstPath ()); } m_founds.back ().set_cv_index (m_cv_index); m_founds.back ().set_topcell (m_topcell); m_founds.back ().assign_path (path ().begin (), path ().end ()); m_founds.back ().set_layer (*l); m_founds.back ().set_shape (*shape); } } ++shape; } } } } } // ------------------------------------------------------------- // InstFinder implementation InstFinder::InstFinder (bool point_mode, bool top_level_sel, bool full_arrays, bool enclose_inst, const std::set *excludes, bool visible_layers) : Finder (point_mode, top_level_sel), m_cv_index (0), m_topcell (0), mp_excludes ((excludes && !excludes->empty ()) ? excludes : 0), m_full_arrays (full_arrays), m_enclose_insts (enclose_inst), m_visible_layers (visible_layers), mp_view (0), mp_progress (0) { m_tries = inst_point_sel_tests; } bool InstFinder::find (lay::LayoutView *view, const db::DBox ®ion_mu) { tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Selecting ..."))); progress.set_unit (1000); progress.set_format (""); mp_progress = &progress; std::set< std::pair > variants = view->cv_transform_variants (); for (std::set< std::pair >::const_iterator v = variants.begin (); v != variants.end (); ++v) { find (view, v->second, v->first, region_mu); } mp_progress = 0; return ! m_founds.empty (); } bool InstFinder::find (lay::LayoutView *view, unsigned int cv_index, const db::DCplxTrans &trans_mu, const db::DBox ®ion_mu) { tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Selecting ..."))); progress.set_unit (1000); progress.set_format (""); mp_progress = &progress; bool result = find_internal (view, cv_index, trans_mu, region_mu); mp_progress = 0; return result; } bool InstFinder::find_internal (lay::LayoutView *view, unsigned int cv_index, const db::DCplxTrans &trans_mu, const db::DBox ®ion_mu) { const lay::CellView &cv = view->cellview (cv_index); if (! cv.is_valid ()) { return false; } m_visible_layer_indexes.clear (); if (m_visible_layers) { for (lay::LayerPropertiesConstIterator l = view->begin_layers (); !l.at_end (); ++l) { if (! l->has_children () && l->visible (true /*real*/) && l->valid (true /*real*/) && l->cellview_index () == int (cv_index)) { m_visible_layer_indexes.push_back (l->layer_index ()); } } } if (!m_visible_layers || view->guiding_shapes_visible ()) { m_visible_layer_indexes.push_back (cv->layout ().guiding_shape_layer ()); } m_cv_index = cv_index; m_topcell = cv.cell ()->cell_index (); mp_view = view; double dbu = cv->layout ().dbu (); db::Box region = db::VCplxTrans (1.0 / dbu) * region_mu; // actually find try { std::vector tv; tv.push_back (db::VCplxTrans (1.0 / dbu) * trans_mu * db::CplxTrans (dbu)); start (view, cv, cv_index, tv, region, view->get_min_hier_levels (), view->get_max_hier_levels (), std::vector ()); } catch (StopException) { // .. } // return true if anything was found return ! m_founds.empty (); } void InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const db::ICplxTrans &t, int level) { if (! point_mode ()) { ++*mp_progress; // look for instances to check here .. db::Cell::touching_iterator inst = cell.begin_touching (search_box); while (! inst.at_end ()) { const db::CellInstArray &cell_inst = inst->cell_inst (); const db::Cell &inst_cell = layout ().cell (cell_inst.object ().cell_index ()); ++*mp_progress; // just consider the instances exactly at the last level of // hierarchy (this is where the boxes are drawn) or of cells that // are hidden. if (level == max_level () - 1 || inst_cell.is_proxy () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { db::box_convert bc (layout ()); for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) { ++*mp_progress; db::Box ibox; if (inst_cell.bbox ().empty ()) { ibox = db::Box (db::Point (0, 0), db::Point (0, 0)); } else if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { ibox = inst_cell.bbox (); } else { for (std::vector::const_iterator l = m_visible_layer_indexes.begin (); l != m_visible_layer_indexes.end (); ++l) { ibox += inst_cell.bbox (*l); } } if (! ibox.empty ()) { db::Box box = cell_inst.complex_trans (*p) * ibox; // in box mode, just test the boxes if (! m_enclose_insts || box.inside (search_box)) { // in point mode just store that found that has the least "distance" m_founds.push_back (lay::ObjectInstPath ()); m_founds.back ().set_cv_index (m_cv_index); m_founds.back ().set_topcell (m_topcell); m_founds.back ().assign_path (path ().begin (), path ().end ()); // add the selected instance as the last element of the path db::InstElement el; el.inst_ptr = *inst; if (! m_full_arrays) { el.array_inst = p; } m_founds.back ().add_path (el); // Remove the selection if it's part of the excluded set if (mp_excludes != 0 && mp_excludes->find (m_founds.back ()) != mp_excludes->end ()) { m_founds.pop_back (); } // in "full arrays" mode, a single reference to that array is sufficient if (m_full_arrays) { break; } } } } } ++inst; } } else { if (--m_tries < 0) { throw StopException (); } // look for instances to check here .. db::Cell::touching_iterator inst = cell.begin_touching (search_box); while (! inst.at_end ()) { if (--m_tries < 0) { throw StopException (); } const db::CellInstArray &cell_inst = inst->cell_inst (); const db::Cell &inst_cell = layout ().cell (cell_inst.object ().cell_index ()); // just consider the instances exactly at the last level of // hierarchy (this is where the boxes are drawn) or if of cells that // are hidden. if (level == max_level () - 1 || inst_cell.is_proxy () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { db::box_convert bc (layout ()); for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) { if (--m_tries < 0) { throw StopException (); } bool match = false; double d = std::numeric_limits::max (); db::Box ibox; if (inst_cell.bbox ().empty ()) { ibox = db::Box (db::Point (0, 0), db::Point (0, 0)); } else if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) { ibox = inst_cell.bbox (); } else { for (std::vector::const_iterator l = m_visible_layer_indexes.begin (); l != m_visible_layer_indexes.end (); ++l) { ibox += inst_cell.bbox (*l); } } if (! ibox.empty ()) { if (ibox.width () == 0 && ibox.height () == 0) { match = true; d = 0.0; } else { // convert to polygon and test those edges db::Polygon poly (cell_inst.complex_trans (*p) * db::Polygon (ibox)); for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) { test_edge (t * *e, d, match); } if (! match && db::inside_poly (poly.begin_edge (), search_box.center ())) { d = t.ctrans (poly_dist (poly.begin_edge (), search_box.center ())); match = true; } } d += 1; // the instance has a small penalty so that shapes win over instances if (match && closer (d)) { // in point mode just store that found that has the least "distance" if (m_founds.empty ()) { m_founds.push_back (lay::ObjectInstPath ()); } m_founds.back ().set_cv_index (m_cv_index); m_founds.back ().set_topcell (m_topcell); m_founds.back ().assign_path (path ().begin (), path ().end ()); // add the selected instance as the last element of the path db::InstElement el; el.inst_ptr = *inst; if (! m_full_arrays) { el.array_inst = p; } m_founds.back ().add_path (el); } } if (match) { if (mp_excludes) { // with an exclude list first create the selection item so we can check // if it's part of the exclude set. lay::ObjectInstPath found; found.set_cv_index (m_cv_index); found.set_topcell (m_topcell); found.assign_path (path ().begin (), path ().end ()); // add the selected instance as the last element of the path db::InstElement el; el.inst_ptr = *inst; if (! m_full_arrays) { el.array_inst = p; } found.add_path (el); // in point mode just store the found object that has the least "distance" and is // not in the exclude set if (mp_excludes->find (found) == mp_excludes->end () && closer (d)) { if (m_founds.empty ()) { m_founds.push_back (found); } else { m_founds.front () = found; } } } else if (closer (d)) { // in point mode just store that found that has the least "distance" if (m_founds.empty ()) { m_founds.push_back (lay::ObjectInstPath ()); } m_founds.back ().set_cv_index (m_cv_index); m_founds.back ().set_topcell (m_topcell); m_founds.back ().assign_path (path ().begin (), path ().end ()); // add the selected instance as the last element of the path db::InstElement el; el.inst_ptr = *inst; if (! m_full_arrays) { el.array_inst = p; } m_founds.back ().add_path (el); } } } } ++inst; } } } } // namespace edt