Ruler's auto-measure function improved

Already a very useful feature.
First tests added for snap feature.
This commit is contained in:
Matthias Koefferlein 2017-07-01 10:03:47 +02:00
parent c263878a4b
commit 4ebfb24fca
6 changed files with 249 additions and 98 deletions

View File

@ -1509,7 +1509,7 @@ Service::deactivated ()
}
std::pair<bool, db::DPoint>
Service::snap1 (const db::DPoint &p, bool obj_snap, const std::vector <db::DEdge> &cutlines)
Service::snap1 (const db::DPoint &p, bool obj_snap)
{
db::DVector g;
if (m_grid_snap) {
@ -1517,7 +1517,7 @@ Service::snap1 (const db::DPoint &p, bool obj_snap, const std::vector <db::DEdge
}
double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (m_snap_range);
return lay::obj_snap (obj_snap ? mp_view : 0, p, g, snap_range, cutlines);
return lay::obj_snap (obj_snap ? mp_view : 0, p, g, snap_range);
}

View File

@ -486,7 +486,7 @@ private:
std::vector<ant::Template> m_ruler_templates;
unsigned int m_current_template;
std::pair<bool, db::DPoint> snap1 (const db::DPoint &p, bool obj_snap, const std::vector <db::DEdge> &cutlines = std::vector <db::DEdge> ());
std::pair<bool, db::DPoint> snap1 (const db::DPoint &p, bool obj_snap);
std::pair<bool, db::DPoint> snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac);
const ant::Template &current_template () const;

View File

@ -222,17 +222,45 @@ snap_angle (const db::DVector &in, lay::angle_constraint_type ac)
// ---------------------------------------------------------------------------------------
// obj_snap implementations
/**
* @brief A finder for a point on a contour
*
* This is an object to look up a point on a contour.
* It will start from a gvein point and look into the direction of given cutlines.
* It will try to find the closest snapping point while also considering snapping.
*
* There are basically three modes:
* - free mode (no cutlines, but snapping)
* - constrained mode (with a variety of directions)
* - directed mode (one direction and positive direction only)
*
* There are more and mode constraints with each mode. In constrained mode, the
* resulting point will sit in one of the given directions. In directed mode,
* the (exact) resulting point will sit on the line.
*
* Vertex and non-vertex snapping is available: with vertex snapping, the search
* point will snap to vertexes too. Without vertex snapping, the snap point is
* guaranteed to sit on an edge.
*
* The finder will deliver two solutions if available: an exact one where the
* point sits exactly on the constraint and a more generic one where the resoluting
* point is allowed to deviate a little from that direction. The exact solution is
* priority in the generic case.
*/
class ContourFinder
{
public:
ContourFinder (const db::DPoint &original, const db::DVector &grid, const std::vector <db::DEdge> &cutlines, bool directed = false)
/**
* @brief Constructor
*/
ContourFinder (const db::DPoint &original, const db::DVector &grid, const std::vector <db::DEdge> &cutlines, bool with_vertex = true, bool directed = false)
: m_any (false), m_any_exact (false),
m_original (original), m_is_vertex (false), m_is_vertex_exact (false),
m_tests (10000 /* max. number of tests, TODO: make variable? */),
mp_layout (0), m_cutlines (cutlines), mp_prop_sel (0), m_inv_prop_sel (false),
m_directed (directed)
m_with_vertex (with_vertex), m_directed (directed)
{
m_projection_constraint = ! m_cutlines.empty () && ! directed;
m_projection_constraint = ! m_cutlines.empty ();
// Add synthetic cutlines for the grid snap along edges
if (m_cutlines.empty () && grid != db::DVector ()) {
@ -249,23 +277,76 @@ public:
}
}
void
find (lay::LayoutView *view, int cv_index, const std::vector<db::DCplxTrans> &trans, const db::Layout &layout, const db::DBox &region, const db::Cell &cell, unsigned int l,
const std::set<db::properties_id_type> *prop_sel, bool inv_prop_sel, int min_hier_level, int max_hier_level)
/**
* @brief Triggers the finder
* This will run the finder on the given view.
* "search_range" is the search range in micron units.
*/
void find (lay::LayoutView *view, double search_range)
{
m_region = region * (1.0 / layout.dbu ());
mp_layout = &layout;
mp_prop_sel = prop_sel;
m_inv_prop_sel = inv_prop_sel;
if (! view) {
return;
}
const lay::CellView &cv = view->cellview (cv_index);
for (lay::LayerPropertiesConstIterator l = view->begin_layers (); ! l.at_end (); ++l) {
if (l->is_visual ()) {
int cv_index = l->cellview_index ();
const lay::CellView &cv = view->cellview (l->cellview_index ());
if (cv.is_valid ()) {
const db::Layout &layout = cv->layout ();
double px = m_original.x ();
double py = m_original.y ();
double dd = std::max (0.0, search_range);
double dmin = layout.dbu () * std::numeric_limits <db::Coord>::min ();
double dmax = layout.dbu () * std::numeric_limits <db::Coord>::max ();
db::DBox dworld (dmin, dmin, dmax, dmax);
db::DBox dregion = dworld & db::DBox (px - dd, py - dd, px + dd, py + dd);
if (! dregion.empty ()) {
int ctx_levels = cv.specific_path ().size ();
int min_hier_level = view->get_min_hier_levels () - ctx_levels;
int max_hier_level = view->get_max_hier_levels () - ctx_levels;
if (l->hier_levels ().has_from_level ()) {
min_hier_level = l->hier_levels ().from_level (ctx_levels, min_hier_level);
}
if (l->hier_levels ().has_to_level ()) {
max_hier_level = l->hier_levels ().to_level (ctx_levels, max_hier_level);
}
m_region = db::DBox (dregion.p1 (), dregion.p2 ()) * (1.0 / layout.dbu ());
mp_layout = &layout;
mp_prop_sel = &l->prop_sel ();
m_inv_prop_sel = l->inverse_prop_sel ();
const std::vector<db::DCplxTrans> &trans = l->trans ();
for (std::vector<db::DCplxTrans>::const_iterator t = trans.begin (); t != trans.end () && m_tests > 0; ++t) {
do_find (view, cv_index, *cv.cell (), l->layer_index (), min_hier_level, max_hier_level, *t * db::CplxTrans () * cv.context_trans ());
}
}
}
}
for (std::vector<db::DCplxTrans>::const_iterator t = trans.begin (); t != trans.end () && m_tests > 0; ++t) {
do_find (view, cv_index, cell, l, min_hier_level, max_hier_level, *t * db::CplxTrans () * cv.context_trans ());
}
}
/**
* @brief Returns a value indicating whether a vertex was found
* Returns true, if the point returned by \get_found is a vertex.
* In that case, the associated edges (at least two of them) are
* returned by \get_found_vertex edges.
*/
bool is_vertex () const
{
if (m_any_exact) {
@ -277,6 +358,10 @@ public:
}
}
/**
* @brief Returns the vertex edges if a vertex was found
* See \is_vertex for details.
*/
std::pair<db::DEdge, db::DEdge> get_found_vertex_edges () const
{
if (m_any_exact) {
@ -288,6 +373,10 @@ public:
}
}
/**
* @brief Returns a value indicating whether an associated edge is present
* See \get_found_edge for details.
*/
bool has_found_edge () const
{
if (m_any_exact) {
@ -299,6 +388,11 @@ public:
}
}
/**
* @brief Gets the edge associated with the best point (see \get_found)
* This edge is valid if \has_found_edge returns true. If a vertex is
* present, get_found_vertex_edges should be used.
*/
db::DEdge get_found_edge () const
{
if (m_any_exact) {
@ -310,6 +404,9 @@ public:
}
}
/**
* @brief Gets the best point found
*/
db::DPoint get_found () const
{
if (m_any_exact) {
@ -321,11 +418,17 @@ public:
}
}
/**
* @brief Returns true if there is a generic solution
*/
bool any () const
{
return (m_any || m_any_exact);
}
/**
* @brief Returns true if there is an exact solution
*/
bool any_exact () const
{
return m_any_exact;
@ -374,9 +477,13 @@ private:
}
}
void closest (const db::DPoint &p)
void closest (const db::DPoint &p)
{
if (! m_projection_constraint) {
if (! m_with_vertex) {
// Only edges are considered for snapping.
} else if (! m_projection_constraint) {
find_closest_exact (p, db::DEdge (p, p));
@ -574,18 +681,20 @@ private:
void
test_edge (const db::DEdge &edg)
{
// experimentally disabled this:
// vertex snap is just annoying when trying to measure the width of simulation contours ..
// But: when measuring corner-to-corner distances it is very valuable ..
#if 1
// we hit the region with this edge: vertex snap.
if (m_region.contains (edg.p1 ())) {
closest (edg.p1 ());
if (m_with_vertex) {
// vertex snap is just annoying when trying to measure the width of simulation contours ..
// But: when measuring corner-to-corner distances it is very valuable ..
// we hit the region with this edge: vertex snap.
if (m_region.contains (edg.p1 ())) {
closest (edg.p1 ());
}
if (m_region.contains (edg.p2 ())) {
closest (edg.p2 ());
}
}
if (m_region.contains (edg.p2 ())) {
closest (edg.p2 ());
}
#endif
// if the edge cuts through the active region: test the
// edge as a whole
@ -608,67 +717,16 @@ private:
bool m_inv_prop_sel;
bool m_projection_constraint;
bool m_directed;
bool m_with_vertex;
};
static
void run_finder (ContourFinder &finder, lay::LayoutView *view, const db::DPoint &dp, double snap_range)
{
if (! view) {
return;
}
for (lay::LayerPropertiesConstIterator l = view->begin_layers (); ! l.at_end (); ++l) {
if (l->is_visual ()) {
const lay::CellView &cv = view->cellview (l->cellview_index ());
if (cv.is_valid ()) {
double px = dp.x ();
double py = dp.y ();
double dd = std::max (0.0, snap_range);
double dmin = cv->layout ().dbu () * std::numeric_limits <db::Coord>::min ();
double dmax = cv->layout ().dbu () * std::numeric_limits <db::Coord>::max ();
db::DBox dworld (dmin, dmin, dmax, dmax);
db::DBox dregion = dworld & db::DBox (px - dd, py - dd, px + dd, py + dd);
const lay::CellView &cv = view->cellview (l->cellview_index ());
int ctx_levels = cv.specific_path ().size ();
int min_hier_level = view->get_min_hier_levels () - ctx_levels;
int max_hier_level = view->get_max_hier_levels () - ctx_levels;
if (l->hier_levels ().has_from_level ()) {
min_hier_level = l->hier_levels ().from_level (ctx_levels, min_hier_level);
}
if (l->hier_levels ().has_to_level ()) {
max_hier_level = l->hier_levels ().to_level (ctx_levels, max_hier_level);
}
if (! dregion.empty ()) {
finder.find (view, l->cellview_index (), l->trans (), cv->layout (),
db::DBox (dregion.p1 (), dregion.p2 ()),
*cv.cell (),
l->layer_index (),
&l->prop_sel (), l->inverse_prop_sel (),
min_hier_level, max_hier_level);
}
}
}
}
}
std::pair <bool, db::DPoint>
obj_snap (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double snap_range, const std::vector <db::DEdge> &cutlines)
static std::pair <bool, db::DPoint>
do_obj_snap (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double snap_range, const std::vector <db::DEdge> &cutlines)
{
db::DPoint dp (pt);
ContourFinder finder (dp, grid, cutlines);
run_finder (finder, view, dp, snap_range);
finder.find (view, snap_range);
// in grid snap mode, for the "object free" analysis snap to the grid now
if (grid != db::DVector ()) {
@ -708,15 +766,15 @@ obj_snap (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid,
}
std::pair <bool, db::DEdge>
obj_snap2 (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double min_search_range, double max_search_range, const std::vector <db::DEdge> &cutlines)
do_obj_snap2 (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double min_search_range, double max_search_range, const std::vector <db::DEdge> &cutlines)
{
db::DPoint dp (pt);
ContourFinder finder (dp, grid, cutlines);
ContourFinder finder (dp, grid, cutlines, false /*no vertex snap*/);
double sr = min_search_range;
while (sr < max_search_range + 1e-6) {
run_finder (finder, view, dp, sr);
finder.find (view, sr);
if (finder.any ()) {
@ -752,16 +810,19 @@ obj_snap2 (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid,
}
ContourFinder finder2 (dp, grid, cl, true /*directional cutlines*/);
ContourFinder finder2 (dp, grid, cl, false /*no vertex snap*/, true /*directional cutlines*/);
double sr2 = min_search_range;
while (sr2 < max_search_range + 1e-6) {
run_finder (finder2, view, dp, sr2);
finder2.find (view, sr2);
if (finder2.any_exact ()) {
db::DPoint p2 = finder2.get_found ();
return std::make_pair (true, db::DEdge (p1, p2));
}
sr2 *= 2.0;
}
return std::make_pair (false, db::DEdge ());
@ -796,12 +857,24 @@ make_cutlines (lay::angle_constraint_type snap_mode, const db::DPoint &p1, std::
}
}
std::pair <bool, db::DPoint>
obj_snap (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double snap_range)
{
return do_obj_snap (view, pt, grid, snap_range, std::vector<db::DEdge> ());
}
std::pair <bool, db::DPoint>
obj_snap (lay::LayoutView *view, const db::DPoint &p1, const db::DPoint &p2, const db::DVector &grid, lay::angle_constraint_type snap_mode, double snap_range)
{
std::vector <db::DEdge> cutlines;
make_cutlines (snap_mode, p1, cutlines);
return obj_snap (view, p2, grid, snap_range, cutlines);
return do_obj_snap (view, p2, grid, snap_range, cutlines);
}
std::pair <bool, db::DEdge>
obj_snap2 (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double min_search_range, double max_search_range)
{
return do_obj_snap2 (view, pt, grid, min_search_range, max_search_range, std::vector<db::DEdge> ());
}
std::pair <bool, db::DEdge>
@ -809,7 +882,7 @@ obj_snap2 (lay::LayoutView *view, const db::DPoint &p1, const db::DPoint &p2, co
{
std::vector <db::DEdge> cutlines;
make_cutlines (snap_mode, p1, cutlines);
return obj_snap2 (view, p2, grid, min_search_range, max_search_range, cutlines);
return do_obj_snap2 (view, p2, grid, min_search_range, max_search_range, cutlines);
}
}

View File

@ -124,16 +124,16 @@ namespace lay
* @param pt The point to snap
* @param grid Either (0,0) to disable grid snapping or a (gx,gy) value for the (potentially anisotropic grid)
* @param snap_range The search range for objects
* @param axes The projection edges.
*/
LAYBASIC_PUBLIC std::pair <bool, db::DPoint> obj_snap (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double snap_range, const std::vector <db::DEdge> &axes = std::vector <db::DEdge> ());
LAYBASIC_PUBLIC std::pair <bool, db::DPoint> obj_snap (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double snap_range);
/**
* @brief Same than obj_snap, but delivers two points on two opposite sides of the initial point
*
* This method basically implements "auto measure"
* This method basically implements "auto measure". The first value of the returned pair
* is true if such an edge could be found. Otherwise it's false.
*/
LAYBASIC_PUBLIC std::pair <bool, db::DEdge> obj_snap2 (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double min_search_range, double max_search_range, const std::vector <db::DEdge> &axes = std::vector <db::DEdge> ());
LAYBASIC_PUBLIC std::pair <bool, db::DEdge> obj_snap2 (lay::LayoutView *view, const db::DPoint &pt, const db::DVector &grid, double min_search_range, double max_search_range);
/**
* @brief combined grid-, projection- and object snapping provided to implementing "magnetic features"
@ -146,7 +146,8 @@ namespace lay
/**
* @brief Same than obj_snap, but delivers two points on two opposite sides of the initial point
*
* This method basically implements "auto measure"
* This method basically implements "auto measure". The first value of the returned pair
* is true if such an edge could be found. Otherwise it's false.
*/
LAYBASIC_PUBLIC std::pair <bool, db::DEdge> obj_snap2 (lay::LayoutView *view, const db::DPoint &pr, const db::DPoint &pt, const db::DVector &grid, lay::angle_constraint_type ac, double min_search_range, double max_search_range);

76
src/unit_tests/laySnap.cc Normal file
View File

@ -0,0 +1,76 @@
/*
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 "laySnap.h"
#include "layLayoutView.h"
#include "layApplication.h"
#include "layMainWindow.h"
#include "utHead.h"
TEST(1)
{
db::Manager mgr;
lay::LayoutView view (&mgr, lay::Application::instance ()->is_editable (), lay::MainWindow::instance ());
int cv1 = view.create_layout ("", true, false);
db::Layout &ly1 = view.cellview (cv1)->layout ();
db::Cell &top = ly1.cell (ly1.add_cell ("TOP"));
unsigned int l1 = ly1.insert_layer (db::LayerProperties (1, 0));
view.select_cell (0, top.cell_index ());
lay::LayerPropertiesNode lp;
lp.set_source ("1/0@1");
view.insert_layer (view.begin_layers (), lp);
db::Polygon poly;
db::Point pts[] = {
db::Point (0, 0),
db::Point (1000, 0),
db::Point (0, 1000)
};
poly.assign_hull (pts, pts + sizeof (pts) / sizeof (pts[0]));
top.shapes (l1).insert (poly);
view.set_max_hier_levels (1);
std::pair<bool, db::DPoint> res;
res = lay::obj_snap (&view, db::DPoint (0.505, 0.505), db::DVector (), 0.1);
EXPECT_EQ (res.first, true);
EXPECT_EQ (res.second.to_string (), "0.5,0.5");
res = lay::obj_snap (&view, db::DPoint (0.505, 1.005), db::DVector (), 0.1);
EXPECT_EQ (res.first, false);
EXPECT_EQ (res.second.to_string (), "0.505,1.005");
res = lay::obj_snap (&view, db::DPoint (0.005, 1.005), db::DVector (), 0.1);
EXPECT_EQ (res.first, true);
EXPECT_EQ (res.second.to_string (), "0,1");
res = lay::obj_snap (&view, db::DPoint (1.000, 0.505), db::DPoint (0.505, 0.500), db::DVector (), lay::AC_Horizontal, 0.1);
EXPECT_EQ (res.first, true);
EXPECT_EQ (res.second.to_string (), "0.495,0.505");
}
// .. TODO: implement ..

View File

@ -100,7 +100,8 @@ SOURCES = \
laySalt.cc \
tlFileUtils.cc \
tlHttpStream.cc \
tlWebDAV.cc
tlWebDAV.cc \
laySnap.cc
# main components:
SOURCES += \