mirror of https://github.com/KLayout/klayout.git
2782 lines
85 KiB
C++
2782 lines
85 KiB
C++
|
|
/*
|
|
|
|
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 <QMessageBox>
|
|
|
|
#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 <cmath>
|
|
|
|
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<bool, db::Point> 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<db::Point::coord_type>::min ()) || dp.x () >= double (std::numeric_limits<db::Point::coord_type>::max ()) ||
|
|
dp.y () <= double (std::numeric_limits<db::Point::coord_type>::min ()) || dp.y () >= double (std::numeric_limits<db::Point::coord_type>::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<db::Point::coord_type>::min ()) || dp.x () >= double (std::numeric_limits<db::Point::coord_type>::max ()) ||
|
|
dp.y () <= double (std::numeric_limits<db::Point::coord_type>::min ()) || dp.y () >= double (std::numeric_limits<db::Point::coord_type>::max ())) {
|
|
return std::make_pair (false, p);
|
|
} else {
|
|
return std::make_pair (true, db::Point (dp));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Transform by a given transformation
|
|
*/
|
|
template <class T>
|
|
Constraint &transform (const T &t)
|
|
{
|
|
m_axis.transform (t);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Return the transformed version
|
|
*/
|
|
template <class T>
|
|
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<EdgeWithIndex> &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<db::Point> 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 <bool, db::Point> 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 <db::Point> &ctr)
|
|
{
|
|
// compress contour (remove redundant points)
|
|
// and assign to path
|
|
|
|
std::vector<db::Point>::iterator wp = ctr.begin ();
|
|
std::vector<db::Point>::iterator wp0 = wp;
|
|
std::vector<db::Point>::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<EdgeWithIndex> &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<db::Point> 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 <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, bool compress = false)
|
|
{
|
|
std::vector<db::Point> ctr;
|
|
ctr.reserve (p.points ());
|
|
|
|
std::map <PointWithIndex, db::Point>::const_iterator np;
|
|
std::map <EdgeWithIndex, db::Edge>::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<EdgeWithIndex> &sel, db::Point &ins, db::Polygon &new_poly)
|
|
{
|
|
for (unsigned int c = 0; c < p.holes () + 1; ++c) {
|
|
|
|
bool found = false;
|
|
|
|
std::vector<db::Point> 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 <bool, db::Point> 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<EdgeWithIndex> &sel)
|
|
{
|
|
db::Polygon new_poly = p;
|
|
|
|
for (unsigned int c = 0; c < p.holes () + 1; ++c) {
|
|
|
|
std::vector<db::Point> 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 <PointWithIndex, db::Point> &new_points,
|
|
const std::map <EdgeWithIndex, db::Edge> &new_edges,
|
|
bool compress = false)
|
|
{
|
|
for (unsigned int c = 0; c < p.holes () + 1; ++c) {
|
|
|
|
std::vector<db::Point> ctr;
|
|
size_t points = p.contour (c).size ();
|
|
ctr.reserve (points);
|
|
|
|
std::map <PointWithIndex, db::Point>::const_iterator np;
|
|
std::map <EdgeWithIndex, db::Edge>::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 <PointWithIndex, Constraint> &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 <EdgeWithIndex> &sel, std::map <PointWithIndex, db::Point> &new_points, std::map <EdgeWithIndex, db::Edge> &new_edges, db::Vector mv)
|
|
{
|
|
// Set up a map of new edges and new points
|
|
for (std::set <EdgeWithIndex>::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 <EdgeWithIndex>::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 <PointWithIndex, Constraint> 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 <PointWithIndex, db::Point>::iterator np = new_points.begin (); np != new_points.end (); ++np) {
|
|
np->second += mv;
|
|
}
|
|
|
|
// The edges are treated somewhat more elaborate:
|
|
for (std::map <EdgeWithIndex, db::Edge>::iterator ne = new_edges.begin (); ne != new_edges.end (); ++ne) {
|
|
|
|
std::map <PointWithIndex, Constraint>::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 <bool, db::Point> 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 <bool, db::Point> 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<std::pair <lay::ObjectInstPath, std::vector <EdgeWithIndex> > > 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<int>::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<EdgeWithIndex> &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<int>::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 <EdgeWithIndex> 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<double>::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 <EdgeWithIndex> (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<db::DCplxTrans> *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 <EdgeWithIndex, db::Edge> new_edges;
|
|
std::map <PointWithIndex, db::Point> 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<db::DCplxTrans> *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 <db::Shape, std::vector<partial_objects::iterator> > 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<db::DCplxTrans> *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<partial_objects::iterator> ())).first->second.push_back (r);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::map <db::Shape, std::vector<partial_objects::iterator> >::iterator sps = sel_per_shape.begin (); sps != sel_per_shape.end (); ++sps) {
|
|
|
|
db::Shape shape = sps->first;
|
|
|
|
for (std::vector<partial_objects::iterator>::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<db::DCplxTrans> *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 <EdgeWithIndex, db::Edge> new_edges;
|
|
std::map <PointWithIndex, db::Point> 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 <PointWithIndex, db::Point>::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 <EdgeWithIndex> new_sel;
|
|
new_sel.swap (r->second);
|
|
|
|
for (std::set <EdgeWithIndex>::const_iterator s = new_sel.begin (); s != new_sel.end () && m_keep_selection; ++s) {
|
|
if (s->p1 () == s->p2 ()) {
|
|
std::map <PointWithIndex, db::Point>::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 <EdgeWithIndex, db::Edge>::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<partial_objects::iterator>::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) {
|
|
|
|
std::set <EdgeWithIndex> 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 <EdgeWithIndex> ())).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::pair <db::cell_index_type, unsigned int>, std::vector <partial_objects::const_iterator> > 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 <partial_objects::const_iterator> ())).first->second.push_back (r);
|
|
}
|
|
}
|
|
|
|
std::vector <std::pair <db::Instance, db::ICplxTrans> > insts_to_transform;
|
|
for (std::map <std::pair <db::cell_index_type, unsigned int>, std::vector <partial_objects::const_iterator> >::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 <partial_objects::const_iterator>::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<db::DCplxTrans> *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 <std::pair <db::Instance, db::ICplxTrans> >::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 <std::pair <db::Instance, db::ICplxTrans> >::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<edt::EdgeWithIndex>::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 <EdgeWithIndex> ())).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<EdgeWithIndex> ())).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<db::DCplxTrans> *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<EdgeWithIndex> ()));
|
|
|
|
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::pair <db::cell_index_type, std::pair <unsigned int, unsigned int> >, std::vector <partial_objects::const_iterator> > 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 <partial_objects::const_iterator> ())).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 <partial_objects::const_iterator> ())).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 <partial_objects::const_iterator> ())).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 <partial_objects::const_iterator> ())).first->second.push_back (r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// delete all shapes that are really lost
|
|
std::vector <db::Shape> shapes_to_delete;
|
|
for (std::map <std::pair <db::cell_index_type, std::pair <unsigned int, unsigned int> >, std::vector <partial_objects::const_iterator> >::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 <partial_objects::const_iterator>::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::pair <db::cell_index_type, unsigned int>, std::vector <partial_objects::const_iterator> > 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 <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, const db::ICplxTrans >, const std::vector<db::DCplxTrans> &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 <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, const db::ICplxTrans >, const std::vector<db::DCplxTrans> &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 <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> & /*new_edges*/, const db::ICplxTrans >, const std::vector<db::DCplxTrans> &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 <EdgeWithIndex>::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 <PointWithIndex, db::Point>::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 <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, const db::ICplxTrans >, const std::vector<db::DCplxTrans> &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 <PointWithIndex, db::Point>::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 <EdgeWithIndex, db::Edge>::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<db::DCplxTrans> *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<db::DCplxTrans> *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 <EdgeWithIndex, db::Edge> new_edges;
|
|
std::map <PointWithIndex, db::Point> 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<db::DCplxTrans> *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<lay::Marker *>::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<lay::Marker *>::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<lay::InstanceMarker *>::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<lay::InstanceMarker *>::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 <EdgeWithIndex> ())).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 <EdgeWithIndex> ())).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 <EdgeWithIndex> ()));
|
|
} 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 <EdgeWithIndex> ()));
|
|
}
|
|
}
|
|
|
|
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<db::cell_index_type>::max ();
|
|
db::cell_index_type parent_cell = std::numeric_limits<db::cell_index_type>::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 <db::cell_index_type>::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 <db::cell_index_type>::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
|
|
|
|
|