mirror of https://github.com/KLayout/klayout.git
1629 lines
44 KiB
C++
1629 lines
44 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 "edtMainService.h"
|
|
#include "edtServiceImpl.h"
|
|
#include "edtPropertiesPages.h"
|
|
#include "edtInstPropertiesPage.h"
|
|
#include "dbEdge.h"
|
|
#include "dbLibrary.h"
|
|
#include "dbLibraryManager.h"
|
|
#include "dbPCellDeclaration.h"
|
|
#include "dbPolygonTools.h"
|
|
#include "dbEdgeProcessor.h"
|
|
#include "layMarker.h"
|
|
#include "layLayerProperties.h"
|
|
#include "layLayoutView.h"
|
|
#include "layTipDialog.h"
|
|
|
|
#include <QInputDialog>
|
|
#include <QApplication>
|
|
|
|
namespace edt
|
|
{
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ShapeEditService implementation
|
|
|
|
ShapeEditService::ShapeEditService (db::Manager *manager, lay::LayoutView *view, db::ShapeIterator::flags_type shape_types)
|
|
: edt::Service (manager, view, shape_types),
|
|
m_layer (0), m_cv_index (0), mp_cell (0), mp_layout (0), m_combine_mode (CM_Add)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool
|
|
ShapeEditService::configure (const std::string &name, const std::string &value)
|
|
{
|
|
if (name == cfg_edit_combine_mode) {
|
|
CMConverter ().from_string (value, m_combine_mode);
|
|
return false; // pass to other plugins
|
|
} else {
|
|
return edt::Service::configure (name, value);
|
|
}
|
|
}
|
|
|
|
void
|
|
ShapeEditService::get_edit_layer ()
|
|
{
|
|
lay::LayerPropertiesConstIterator cl = view ()->current_layer ();
|
|
if (cl.is_null ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Please select a layer first")));
|
|
}
|
|
|
|
if (! cl->visible (true)) {
|
|
lay::TipDialog td (QApplication::activeWindow (),
|
|
tl::to_string (QObject::tr ("You are about to draw on a hidden layer. The result won't be visible.")),
|
|
"drawing-on-invisible-layer");
|
|
td.exec_dialog ();
|
|
}
|
|
|
|
int cv_index = cl->cellview_index ();
|
|
const lay::CellView &cv = view ()->cellview (cv_index);
|
|
int layer = cl->layer_index ();
|
|
|
|
if (cv_index < 0 || ! cv.is_valid ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Please select a cell first")));
|
|
}
|
|
|
|
if (layer < 0 || ! cv->layout ().is_valid_layer ((unsigned int) layer)) {
|
|
|
|
if (cl->has_children ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Please select a valid drawing layer first")));
|
|
} else {
|
|
|
|
// create this layer now
|
|
const lay::ParsedLayerSource &source = cl->source (true /*real*/);
|
|
|
|
db::LayerProperties db_lp;
|
|
if (source.has_name ()) {
|
|
db_lp.name = source.name ();
|
|
}
|
|
db_lp.layer = source.layer ();
|
|
db_lp.datatype = source.datatype ();
|
|
|
|
cv->layout ().insert_layer (db_lp);
|
|
|
|
// update the layer index inside the layer view
|
|
cl->realize_source ();
|
|
|
|
// Hint: we could have taken the new index from insert_layer, but this
|
|
// is a nice test:
|
|
layer = cl->layer_index ();
|
|
tl_assert (layer >= 0);
|
|
|
|
}
|
|
}
|
|
|
|
m_layer = (unsigned int) layer;
|
|
m_cv_index = (unsigned int) cv_index;
|
|
m_trans = (cl->trans ().front () * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ()).inverted ();
|
|
mp_layout = &(cv->layout ());
|
|
mp_cell = &(mp_layout->cell (cv.cell_index ()));
|
|
|
|
if (mp_cell->is_proxy ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot put a shape into a PCell or library cell")));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Deliver a good interpolation between two points m and p
|
|
*
|
|
* This method uses an intermediate point o to determine the edge that is emerged from point m.
|
|
* An edge is searched that emerges from p and intersects with the m->o edge in a way that the intersection
|
|
* point is closest to o.
|
|
*
|
|
* This method returns the intersection point ("new o") and a flag if the search was sucessful (.first of return value).
|
|
*/
|
|
std::pair <bool, db::DPoint>
|
|
ShapeEditService::interpolate (const db::DPoint &m, const db::DPoint &o, const db::DPoint &p) const
|
|
{
|
|
if (fabs (m.x () - o.x ()) < 1e-6 && fabs (m.y () - o.y ()) < 1e-6) {
|
|
return std::pair <bool, db::DPoint> (false, db::DPoint ());
|
|
}
|
|
|
|
std::vector <db::DVector> delta;
|
|
delta.reserve (4);
|
|
delta.push_back (db::DVector (1.0, 0.0));
|
|
delta.push_back (db::DVector (0.0, 1.0));
|
|
if (connect_ac () == lay::AC_Diagonal) {
|
|
delta.push_back (db::DVector (1.0, -1.0));
|
|
delta.push_back (db::DVector (1.0, 1.0));
|
|
}
|
|
|
|
bool c_set = false;
|
|
db::DPoint c;
|
|
for (std::vector <db::DVector>::const_iterator d = delta.begin (); d != delta.end (); ++d) {
|
|
std::pair <bool, db::DPoint> ip = db::DEdge (m, o).cut_point (db::DEdge (p - *d, p));
|
|
if (ip.first && (! c_set || o.sq_distance (ip.second) < o.sq_distance (c))) {
|
|
c = ip.second;
|
|
c_set = true;
|
|
}
|
|
}
|
|
|
|
return std::make_pair (c_set, c);
|
|
}
|
|
|
|
void
|
|
ShapeEditService::do_mouse_move_inactive (const db::DPoint &p)
|
|
{
|
|
// display the next (snapped) position where editing would start
|
|
db::DPoint pp = snap (p);
|
|
std::string pos = std::string ("x: ") + tl::micron_to_string (pp.x ()) +
|
|
std::string (" y: ") + tl::micron_to_string (pp.y ());
|
|
view ()->message (pos);
|
|
}
|
|
|
|
void
|
|
ShapeEditService::deliver_shape (const db::Polygon &poly)
|
|
{
|
|
if (m_combine_mode == CM_Add) {
|
|
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create polygon")));
|
|
cell ().shapes (layer ()).insert (poly);
|
|
manager ()->commit ();
|
|
|
|
} else {
|
|
|
|
std::vector<db::Shape> shapes;
|
|
std::vector<db::Polygon> result;
|
|
|
|
std::vector<db::Polygon> input;
|
|
input.push_back (poly);
|
|
|
|
std::vector<db::Polygon> input_left;
|
|
if (m_combine_mode == CM_Diff) {
|
|
input_left = input;
|
|
}
|
|
|
|
db::EdgeProcessor ep;
|
|
bool any = false;
|
|
|
|
db::ShapeIterator s = cell ().shapes (layer ()).begin_touching (poly.box (), db::ShapeIterator::Polygons | db::ShapeIterator::Paths | db::ShapeIterator::Boxes);
|
|
while (! s.at_end ()) {
|
|
|
|
std::vector<db::Polygon> subject;
|
|
subject.push_back (db::Polygon ());
|
|
s->polygon (subject.back ());
|
|
|
|
if (db::interact_pp (poly, subject.back ())) {
|
|
|
|
any = true;
|
|
|
|
if (m_combine_mode == CM_Merge) {
|
|
ep.boolean (subject, input, result, db::BooleanOp::Or);
|
|
input = result;
|
|
input_left.clear ();
|
|
input_left.swap (result);
|
|
} else if (m_combine_mode == CM_Erase) {
|
|
ep.boolean (subject, input, result, db::BooleanOp::ANotB);
|
|
} else if (m_combine_mode == CM_Mask) {
|
|
ep.boolean (subject, input, result, db::BooleanOp::And);
|
|
} else if (m_combine_mode == CM_Diff) {
|
|
ep.boolean (subject, input, result, db::BooleanOp::ANotB);
|
|
std::vector<db::Polygon> l;
|
|
ep.boolean (input_left, subject, l, db::BooleanOp::ANotB);
|
|
l.swap (input_left);
|
|
}
|
|
|
|
shapes.push_back (*s);
|
|
|
|
}
|
|
|
|
++s;
|
|
|
|
}
|
|
|
|
// If nothing was found, simply pass the input to the result
|
|
if (! any && (m_combine_mode == CM_Merge || m_combine_mode == CM_Diff)) {
|
|
result = input;
|
|
}
|
|
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Combine shape with background")));
|
|
|
|
// Erase existing shapes
|
|
for (std::vector<db::Shape>::const_iterator s = shapes.begin (); s != shapes.end (); ++s) {
|
|
cell ().shapes (layer ()).erase_shape (*s);
|
|
}
|
|
|
|
// Add new shapes
|
|
for (std::vector<db::Polygon>::const_iterator p = result.begin (); p != result.end (); ++p) {
|
|
cell ().shapes (layer ()).insert (*p);
|
|
}
|
|
for (std::vector<db::Polygon>::const_iterator p = input_left.begin (); p != input_left.end (); ++p) {
|
|
cell ().shapes (layer ()).insert (*p);
|
|
}
|
|
|
|
manager ()->commit ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
ShapeEditService::deliver_shape (const db::Path &path)
|
|
{
|
|
if (m_combine_mode == CM_Add) {
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create path")));
|
|
cell ().shapes (layer ()).insert (path);
|
|
manager ()->commit ();
|
|
} else {
|
|
deliver_shape (path.polygon ());
|
|
}
|
|
}
|
|
|
|
void
|
|
ShapeEditService::deliver_shape (const db::Box &box)
|
|
{
|
|
if (m_combine_mode == CM_Add) {
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create box")));
|
|
cell ().shapes (layer ()).insert (box);
|
|
manager ()->commit ();
|
|
} else {
|
|
deliver_shape (db::Polygon (box));
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// PolygonService implementation
|
|
|
|
PolygonService::PolygonService (db::Manager *manager, lay::LayoutView *view)
|
|
: ShapeEditService (manager, view, db::ShapeIterator::Polygons),
|
|
m_closure_set (false), m_closure ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
lay::PropertiesPage *
|
|
PolygonService::properties_page (QWidget *parent)
|
|
{
|
|
return new edt::PolygonPropertiesPage (this, parent);
|
|
}
|
|
|
|
void
|
|
PolygonService::do_begin_edit (const db::DPoint &p)
|
|
{
|
|
get_edit_layer ();
|
|
|
|
db::DPoint pp = snap2 (p);
|
|
m_last = pp;
|
|
|
|
m_points.clear ();
|
|
m_points.push_back (pp);
|
|
m_points.push_back (pp);
|
|
m_closure_set = false;
|
|
|
|
update_marker ();
|
|
}
|
|
|
|
void
|
|
PolygonService::set_last_point (const db::DPoint &p)
|
|
{
|
|
m_points.back () = snap2 (p, m_last);
|
|
|
|
// for manhattan polygons allow some movement of the projected edge
|
|
if (m_points.size () >= 3 && connect_ac () == lay::AC_Ortho) {
|
|
|
|
db::DPoint p_grid = snap2 (p);
|
|
std::pair<bool, db::DPoint> ip = interpolate (m_points.end ()[-3], m_last, p_grid);
|
|
if (ip.first) {
|
|
|
|
m_points.end ()[-2] = ip.second;
|
|
m_points.back () = p_grid;
|
|
|
|
}
|
|
|
|
} else if (m_points.size () >= 2) {
|
|
m_points.end ()[-2] = m_last;
|
|
}
|
|
}
|
|
|
|
void
|
|
PolygonService::do_mouse_move (const db::DPoint &p)
|
|
{
|
|
set_cursor (lay::Cursor::cross);
|
|
if (m_points.size () >= 2) {
|
|
set_last_point (p);
|
|
}
|
|
add_closure ();
|
|
update_marker ();
|
|
}
|
|
|
|
bool
|
|
PolygonService::do_mouse_click (const db::DPoint &p)
|
|
{
|
|
if (m_points.size () >= 1) {
|
|
m_last = m_points.back ();
|
|
m_points.push_back (db::DPoint ());
|
|
set_last_point (p);
|
|
}
|
|
// do not do a add_closure here - this will not work since we may have two identical points on top.
|
|
return false;
|
|
}
|
|
|
|
void
|
|
PolygonService::do_finish_edit ()
|
|
{
|
|
// one point is reserved for the current one
|
|
if (m_points.size () < 4) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("A polygon must have at least 3 points")));
|
|
}
|
|
m_points.pop_back ();
|
|
|
|
deliver_shape (get_polygon (true /*compress*/));
|
|
}
|
|
|
|
db::Polygon
|
|
PolygonService::get_polygon (bool compress) const
|
|
{
|
|
db::Polygon poly;
|
|
|
|
std::vector<db::Point> points_dbu;
|
|
points_dbu.reserve (m_points.size () + 1);
|
|
for (std::vector<db::DPoint>::const_iterator p = m_points.begin (); p != m_points.end (); ++p) {
|
|
points_dbu.push_back (trans () * *p);
|
|
}
|
|
if (m_closure_set) {
|
|
points_dbu.push_back (trans () * m_closure);
|
|
}
|
|
|
|
if (compress) {
|
|
|
|
std::vector<db::Point>::iterator wp = points_dbu.begin ();
|
|
|
|
db::Point p0 = points_dbu.back ();
|
|
|
|
for (std::vector<db::Point>::const_iterator p = points_dbu.begin (); p != points_dbu.end (); ++p) {
|
|
|
|
db::Point p1 = *p;
|
|
db::Point p2 = (p + 1 == points_dbu.end () ? points_dbu [0] : p[1]);
|
|
|
|
if (db::vprod_sign (db::Vector (p0, p1), db::Vector (p1, p2)) != 0) {
|
|
*wp++ = p1;
|
|
}
|
|
|
|
p0 = p1;
|
|
|
|
}
|
|
|
|
points_dbu.erase (wp, points_dbu.end ());
|
|
|
|
}
|
|
|
|
poly.assign_hull (points_dbu.begin (), points_dbu.end (), false);
|
|
|
|
return poly;
|
|
}
|
|
|
|
void
|
|
PolygonService::do_cancel_edit ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool
|
|
PolygonService::selection_applies (const lay::ObjectInstPath &sel) const
|
|
{
|
|
return !sel.is_cell_inst () && sel.shape ().is_polygon ();
|
|
}
|
|
|
|
void
|
|
PolygonService::add_closure ()
|
|
{
|
|
if (connect_ac () == lay::AC_Any || m_points.size () < 3) {
|
|
m_closure_set = false;
|
|
} else {
|
|
|
|
std::vector <db::DVector> delta;
|
|
delta.reserve (4);
|
|
|
|
// Even for diagonal mode, we try to do manhattan closing
|
|
|
|
delta.push_back (db::DVector (1.0, 0.0));
|
|
delta.push_back (db::DVector (0.0, 1.0));
|
|
// TODO: for Diagonal mode, this scheme does not work pretty well.
|
|
#if 0
|
|
if (connect_ac () == lay::AC_Diagonal) {
|
|
delta.push_back (db::DVector (1.0, -1.0));
|
|
delta.push_back (db::DVector (1.0, 1.0));
|
|
}
|
|
#endif
|
|
|
|
// Determine the closing point by determining the one of the possible closing points
|
|
// (given the angle constraints) that is closest to the current one.
|
|
|
|
m_closure = db::DPoint ();
|
|
m_closure_set = false;
|
|
|
|
std::vector <db::DPoint>::const_iterator pi;
|
|
|
|
db::DPoint p1, pl;
|
|
|
|
pi = m_points.begin () + 1;
|
|
while (pi != m_points.end () - 1 && *pi == m_points [0]) {
|
|
++pi;
|
|
}
|
|
p1 = *pi;
|
|
|
|
pi = m_points.end () - 2;
|
|
while (pi != m_points.begin () + 1 && *pi == m_points.back ()) {
|
|
--pi;
|
|
}
|
|
pl = *pi;
|
|
|
|
// first try a direct cut between last and first segment ..
|
|
db::DEdge e1 (m_points [0], m_points [1]);
|
|
db::DEdge e2 (m_points.end ()[-2], m_points.back ());
|
|
|
|
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
|
|
if (cp.first &&
|
|
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) + 1e-6 &&
|
|
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ()) + 1e-6) {
|
|
m_closure = cp.second;
|
|
m_closure_set = true;
|
|
}
|
|
|
|
// if that is not working out, try to keep one edge any vary the possible edges emerging from
|
|
// the other point
|
|
if ( ! m_closure_set) {
|
|
|
|
for (std::vector <db::DVector>::const_iterator d1 = delta.begin (); d1 != delta.end (); ++d1) {
|
|
|
|
db::DEdge e1 (m_points [0], m_points [0] + *d1);
|
|
db::DEdge e2 (m_points.end ()[-2], m_points.back ());
|
|
|
|
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
|
|
if (cp.first && (! m_closure_set || cp.second.sq_distance (m_points.back ()) < m_closure.sq_distance (m_points.back ())) &&
|
|
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) &&
|
|
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ())) {
|
|
m_closure = cp.second;
|
|
m_closure_set = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if ( ! m_closure_set) {
|
|
|
|
for (std::vector <db::DVector>::const_iterator d2 = delta.begin (); d2 != delta.end (); ++d2) {
|
|
|
|
db::DEdge e1 (m_points [0], m_points [1]);
|
|
db::DEdge e2 (m_points.back (), m_points.back () + *d2);
|
|
|
|
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
|
|
if (cp.first && (! m_closure_set || cp.second.sq_distance (m_points.back ()) < m_closure.sq_distance (m_points.back ())) &&
|
|
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) &&
|
|
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ())) {
|
|
m_closure = cp.second;
|
|
m_closure_set = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// if that is not working out, try each possible variations of edges from start and end point
|
|
if ( ! m_closure_set) {
|
|
for (std::vector <db::DVector>::const_iterator d1 = delta.begin (); d1 != delta.end (); ++d1) {
|
|
for (std::vector <db::DVector>::const_iterator d2 = delta.begin (); d2 != delta.end (); ++d2) {
|
|
|
|
db::DEdge e1 (m_points [0], m_points [0] + *d1);
|
|
db::DEdge e2 (m_points.back (), m_points.back () + *d2);
|
|
|
|
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
|
|
if (cp.first && (! m_closure_set || cp.second.sq_distance (m_points.back ()) < m_closure.sq_distance (m_points.back ())) &&
|
|
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) &&
|
|
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ())) {
|
|
m_closure = cp.second;
|
|
m_closure_set = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
PolygonService::update_marker ()
|
|
{
|
|
if (m_points.size () == 2) {
|
|
|
|
db::Edge edge (trans () * m_points [0], trans () * m_points [1]);
|
|
|
|
lay::Marker *marker = new lay::Marker (view (), cv_index ());
|
|
marker->set (edge, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
|
|
set_edit_marker (marker);
|
|
|
|
} else if (m_points.size () > 2) {
|
|
|
|
std::vector<db::Point> points_dbu;
|
|
points_dbu.reserve (m_points.size () + 1);
|
|
for (std::vector<db::DPoint>::const_iterator p = m_points.begin (); p != m_points.end (); ++p) {
|
|
points_dbu.push_back (trans () * *p);
|
|
}
|
|
|
|
db::Path path (points_dbu.begin (), points_dbu.end (), 0);
|
|
|
|
lay::Marker *marker;
|
|
|
|
marker = new lay::Marker (view (), cv_index ());
|
|
marker->set (path, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
|
|
set_edit_marker (marker);
|
|
|
|
db::DPoint pl = m_points.back ();
|
|
|
|
if (m_closure_set) {
|
|
|
|
db::Edge edge (trans () * pl, trans () * m_closure);
|
|
marker = new lay::Marker (view (), cv_index ());
|
|
if (std::abs (edge.dy ()) < std::abs (edge.dx ())) {
|
|
marker->set_frame_pattern (34);
|
|
} else {
|
|
marker->set_frame_pattern (39);
|
|
}
|
|
marker->set (edge, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
|
|
add_edit_marker (marker);
|
|
|
|
pl = m_closure;
|
|
|
|
}
|
|
|
|
db::Edge edge (trans () * pl, trans () * m_points.front ());
|
|
marker = new lay::Marker (view (), cv_index ());
|
|
if (std::abs (edge.dy ()) < std::abs (edge.dx ())) {
|
|
marker->set_frame_pattern (34);
|
|
} else {
|
|
marker->set_frame_pattern (39);
|
|
}
|
|
marker->set (edge, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
|
|
add_edit_marker (marker);
|
|
|
|
} else {
|
|
set_edit_marker (0);
|
|
}
|
|
|
|
if (m_points.size () >= 2) {
|
|
view ()->message (std::string ("lx: ") +
|
|
tl::micron_to_string (m_points.back ().x () - m_points.end () [-2].x ()) +
|
|
std::string (" ly: ") +
|
|
tl::micron_to_string (m_points.back ().y () - m_points.end () [-2].y ()) +
|
|
std::string (" l: ") +
|
|
tl::micron_to_string (m_points.back ().distance (m_points.end () [-2])));
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// BoxService implementation
|
|
|
|
BoxService::BoxService (db::Manager *manager, lay::LayoutView *view)
|
|
: ShapeEditService (manager, view, db::ShapeIterator::Boxes)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
lay::PropertiesPage *
|
|
BoxService::properties_page (QWidget *parent)
|
|
{
|
|
return new edt::BoxPropertiesPage (this, parent);
|
|
}
|
|
|
|
void
|
|
BoxService::do_begin_edit (const db::DPoint &p)
|
|
{
|
|
get_edit_layer ();
|
|
|
|
db::DPoint pp = snap2 (p);
|
|
m_p1 = m_p2 = pp;
|
|
|
|
set_edit_marker (new lay::Marker (view (), cv_index ()));
|
|
update_marker ();
|
|
}
|
|
|
|
db::Box
|
|
BoxService::get_box () const
|
|
{
|
|
return db::Box (trans () * m_p1, trans () * m_p2);
|
|
}
|
|
|
|
void
|
|
BoxService::update_marker ()
|
|
{
|
|
lay::Marker *marker = dynamic_cast<lay::Marker *> (edit_marker ());
|
|
if (marker) {
|
|
|
|
marker->set (get_box (), db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
|
|
|
|
view ()->message (std::string ("lx: ") +
|
|
tl::micron_to_string (m_p2.x () - m_p1.x ()) +
|
|
std::string (" ly: ") +
|
|
tl::micron_to_string (m_p2.y () - m_p1.y ()));
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
BoxService::do_mouse_move (const db::DPoint &p)
|
|
{
|
|
set_cursor (lay::Cursor::cross);
|
|
m_p2 = snap2 (p);
|
|
update_marker ();
|
|
}
|
|
|
|
bool
|
|
BoxService::do_mouse_click (const db::DPoint &p)
|
|
{
|
|
do_mouse_move (p);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BoxService::do_finish_edit ()
|
|
{
|
|
deliver_shape (get_box ());
|
|
}
|
|
|
|
void
|
|
BoxService::do_cancel_edit ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool
|
|
BoxService::selection_applies (const lay::ObjectInstPath &sel) const
|
|
{
|
|
return !sel.is_cell_inst () && sel.shape ().is_box ();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// TextService implementation
|
|
|
|
TextService::TextService (db::Manager *manager, lay::LayoutView *view)
|
|
: ShapeEditService (manager, view, db::ShapeIterator::Texts),
|
|
m_rot (0)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
TextService::~TextService ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
lay::PropertiesPage *
|
|
TextService::properties_page (QWidget *parent)
|
|
{
|
|
return new edt::TextPropertiesPage (this, parent);
|
|
}
|
|
|
|
void
|
|
TextService::do_begin_edit (const db::DPoint &p)
|
|
{
|
|
get_edit_layer ();
|
|
|
|
m_text.trans (db::DTrans (m_rot, snap2 (p) - db::DPoint ()));
|
|
|
|
lay::DMarker *marker = new lay::DMarker (view ());
|
|
marker->set_vertex_shape (lay::ViewOp::Cross);
|
|
marker->set_vertex_size (9 /*cross vertex size*/);
|
|
set_edit_marker (marker);
|
|
update_marker ();
|
|
}
|
|
|
|
void
|
|
TextService::update_marker ()
|
|
{
|
|
lay::DMarker *marker = dynamic_cast<lay::DMarker *> (edit_marker ());
|
|
if (marker) {
|
|
|
|
marker->set (m_text);
|
|
|
|
std::string pos = std::string ("x: ") +
|
|
tl::micron_to_string (m_text.trans ().disp ().x ()) +
|
|
std::string (" y: ") +
|
|
tl::micron_to_string (m_text.trans ().disp ().y ());
|
|
if (m_text.trans ().rot () != 0) {
|
|
pos += std::string (" ") + ((const db::DFTrans &) m_text.trans ()).to_string ();
|
|
}
|
|
|
|
view ()->message (pos);
|
|
|
|
}
|
|
}
|
|
|
|
bool
|
|
TextService::do_activated ()
|
|
{
|
|
m_rot = 0;
|
|
|
|
// Show editor options dialog to allow entering of width
|
|
std::vector<edt::MainService *> edt_main_services = view ()->get_plugins <edt::MainService> ();
|
|
if (edt_main_services.size () > 0) {
|
|
edt_main_services [0]->cm_edit_options ();
|
|
}
|
|
|
|
return true; // start editing immediately
|
|
}
|
|
|
|
void
|
|
TextService::do_mouse_move (const db::DPoint &p)
|
|
{
|
|
set_cursor (lay::Cursor::cross);
|
|
m_text.trans (db::DTrans (m_rot, snap2 (p) - db::DPoint ()));
|
|
update_marker ();
|
|
}
|
|
|
|
void
|
|
TextService::do_mouse_transform (const db::DPoint &p, db::DFTrans trans)
|
|
{
|
|
m_rot = (db::DFTrans (m_rot) * trans).rot ();
|
|
m_text.trans (db::DTrans (m_rot, p - db::DPoint ()));
|
|
update_marker ();
|
|
}
|
|
|
|
bool
|
|
TextService::do_mouse_click (const db::DPoint &p)
|
|
{
|
|
do_mouse_move (p);
|
|
return true;
|
|
}
|
|
|
|
db::Text
|
|
TextService::get_text () const
|
|
{
|
|
db::Point p_dbu = trans () * (db::DPoint () + m_text.trans ().disp ());
|
|
return db::Text (m_text.string (), db::Trans (m_text.trans ().rot (), p_dbu - db::Point ()), db::coord_traits<db::Coord>::rounded (trans ().ctrans (m_text.size ())), db::NoFont, m_text.halign (), m_text.valign ());
|
|
}
|
|
|
|
void
|
|
TextService::do_finish_edit ()
|
|
{
|
|
get_edit_layer ();
|
|
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create text")));
|
|
cell ().shapes (layer ()).insert (get_text ());
|
|
manager ()->commit ();
|
|
|
|
if (! view ()->text_visible ()) {
|
|
|
|
lay::TipDialog td (QApplication::activeWindow (),
|
|
tl::to_string (QObject::tr ("A text object is created but texts are disabled for drawing and are not visible. Do you want to enable drawing of texts?\n\nChoose \"Yes\" to enable text drawing now.")),
|
|
"text-created-but-not-visible",
|
|
lay::TipDialog::yesno_buttons);
|
|
|
|
lay::TipDialog::button_type button = lay::TipDialog::null_button;
|
|
td.exec_dialog (button);
|
|
if (button == lay::TipDialog::yes_button) {
|
|
view ()->text_visible (true);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
TextService::do_cancel_edit ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool
|
|
TextService::selection_applies (const lay::ObjectInstPath &sel) const
|
|
{
|
|
return !sel.is_cell_inst () && sel.shape ().is_text ();
|
|
}
|
|
|
|
bool
|
|
TextService::configure (const std::string &name, const std::string &value)
|
|
{
|
|
if (name == cfg_edit_text_size) {
|
|
double size (0);
|
|
tl::from_string (value, size);
|
|
if (m_text.size () != size) {
|
|
m_text.size (size);
|
|
update_marker ();
|
|
}
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_text_halign) {
|
|
db::HAlign ha = db::HAlignLeft;
|
|
HAlignConverter hac;
|
|
hac.from_string (value, ha);
|
|
if (m_text.halign () != ha) {
|
|
m_text.halign (ha);
|
|
update_marker ();
|
|
}
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_text_valign) {
|
|
db::VAlign va = db::VAlignBottom;
|
|
VAlignConverter vac;
|
|
vac.from_string (value, va);
|
|
if (m_text.valign () != va) {
|
|
m_text.valign (va);
|
|
update_marker ();
|
|
}
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_text_string) {
|
|
if (m_text.string () != value) {
|
|
m_text.string (value);
|
|
update_marker ();
|
|
}
|
|
return true; // taken
|
|
}
|
|
|
|
return ShapeEditService::configure (name, value);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// PathService implementation
|
|
|
|
PathService::PathService (db::Manager *manager, lay::LayoutView *view)
|
|
: ShapeEditService (manager, view, db::ShapeIterator::Paths),
|
|
m_width (0.1), m_bgnext (0.0), m_endext (0.0), m_type (Flush), m_needs_update (true)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
PathService::~PathService ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
lay::PropertiesPage *
|
|
PathService::properties_page (QWidget *parent)
|
|
{
|
|
if (view ()->is_editable ()) {
|
|
return new edt::EditablePathPropertiesPage (this, parent);
|
|
} else {
|
|
return new edt::PathPropertiesPage (this, parent);
|
|
}
|
|
}
|
|
|
|
void
|
|
PathService::do_begin_edit (const db::DPoint &p)
|
|
{
|
|
get_edit_layer ();
|
|
|
|
db::DPoint pp = snap2 (p);
|
|
m_last = pp;
|
|
|
|
m_points.clear ();
|
|
m_points.push_back (pp);
|
|
m_points.push_back (pp);
|
|
|
|
set_edit_marker (new lay::Marker (view (), cv_index ()));
|
|
update_marker ();
|
|
}
|
|
|
|
bool
|
|
PathService::do_activated ()
|
|
{
|
|
// Show editor options dialog to allow entering of width
|
|
std::vector<edt::MainService *> edt_main_services = view ()->get_plugins <edt::MainService> ();
|
|
if (edt_main_services.size () > 0) {
|
|
edt_main_services [0]->cm_edit_options ();
|
|
}
|
|
|
|
return false; // don't start editing immediately
|
|
}
|
|
|
|
void
|
|
PathService::set_last_point (const db::DPoint &p)
|
|
{
|
|
m_points.back () = snap2 (p, m_last);
|
|
|
|
// for manhattan polygons allow some movement of the projected edge
|
|
if (m_points.size () >= 3 && connect_ac () == lay::AC_Ortho) {
|
|
|
|
db::DPoint p_grid = snap2 (p);
|
|
std::pair<bool, db::DPoint> ip = interpolate (m_points.end ()[-3], m_last, p_grid);
|
|
if (ip.first) {
|
|
|
|
m_points.end ()[-2] = ip.second;
|
|
m_points.back () = p_grid;
|
|
|
|
}
|
|
|
|
} else if (m_points.size () >= 2) {
|
|
m_points.end ()[-2] = m_last;
|
|
}
|
|
}
|
|
|
|
void
|
|
PathService::do_mouse_move (const db::DPoint &p)
|
|
{
|
|
set_cursor (lay::Cursor::cross);
|
|
if (m_points.size () >= 2) {
|
|
set_last_point (p);
|
|
}
|
|
update_marker ();
|
|
}
|
|
|
|
bool
|
|
PathService::do_mouse_click (const db::DPoint &p)
|
|
{
|
|
if (m_points.size () >= 1) {
|
|
m_last = m_points.back ();
|
|
m_points.push_back (db::DPoint ());
|
|
set_last_point (p);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
PathService::do_finish_edit ()
|
|
{
|
|
// one point is reserved for the "current one"
|
|
if (m_points.size () < 3) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("A path must have at least 2 points")));
|
|
}
|
|
m_points.pop_back ();
|
|
|
|
deliver_shape (get_path ());
|
|
}
|
|
|
|
void
|
|
PathService::update_marker ()
|
|
{
|
|
lay::Marker *marker = dynamic_cast<lay::Marker *> (edit_marker ());
|
|
if (marker) {
|
|
|
|
db::Path path (get_path ());
|
|
marker->set (path, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
|
|
|
|
if (m_points.size () >= 2) {
|
|
view ()->message (std::string ("lx: ") +
|
|
tl::micron_to_string (m_points.back ().x () - m_points.end () [-2].x ()) +
|
|
std::string (" ly: ") +
|
|
tl::micron_to_string (m_points.back ().y () - m_points.end () [-2].y ()) +
|
|
std::string (" l: ") +
|
|
tl::micron_to_string (m_points.back ().distance (m_points.end () [-2])));
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
db::Path
|
|
PathService::get_path () const
|
|
{
|
|
db::Path path;
|
|
|
|
std::vector<db::Point> points_dbu;
|
|
points_dbu.reserve (m_points.size ());
|
|
for (std::vector<db::DPoint>::const_iterator p = m_points.begin (); p != m_points.end (); ++p) {
|
|
points_dbu.push_back (trans () * *p);
|
|
}
|
|
|
|
path.width (trans ().ctrans (m_width));
|
|
|
|
path.round (m_type == Round);
|
|
if (m_type == Flush) {
|
|
path.bgn_ext (0);
|
|
path.end_ext (0);
|
|
} else if (m_type == Square || m_type == Round) {
|
|
path.bgn_ext (path.width () / 2);
|
|
path.end_ext (path.width () / 2);
|
|
} else {
|
|
path.bgn_ext (trans ().ctrans (m_bgnext));
|
|
path.end_ext (trans ().ctrans (m_endext));
|
|
}
|
|
path.assign (points_dbu.begin (), points_dbu.end ());
|
|
|
|
return path;
|
|
}
|
|
|
|
void
|
|
PathService::do_cancel_edit ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool
|
|
PathService::selection_applies (const lay::ObjectInstPath &sel) const
|
|
{
|
|
return !sel.is_cell_inst () && sel.shape ().is_path ();
|
|
}
|
|
|
|
bool
|
|
PathService::configure (const std::string &name, const std::string &value)
|
|
{
|
|
if (name == cfg_edit_path_width) {
|
|
tl::from_string (value, m_width);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_path_ext_var_begin) {
|
|
tl::from_string (value, m_bgnext);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_path_ext_var_end) {
|
|
tl::from_string (value, m_endext);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_path_width) {
|
|
tl::from_string (value, m_width);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_path_ext_type) {
|
|
m_type = Flush;
|
|
if (value == "square") {
|
|
m_type = Square;
|
|
} else if (value == "round") {
|
|
m_type = Round;
|
|
} else if (value == "variable") {
|
|
m_type = Variable;
|
|
}
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
return ShapeEditService::configure (name, value);
|
|
}
|
|
|
|
void
|
|
PathService::config_finalize ()
|
|
{
|
|
if (m_needs_update) {
|
|
update_marker ();
|
|
m_needs_update = false;
|
|
}
|
|
|
|
ShapeEditService::config_finalize ();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// InstService implementation
|
|
|
|
InstService::InstService (db::Manager *manager, lay::LayoutView *view)
|
|
: edt::Service (manager, view),
|
|
m_angle (0.0), m_scale (1.0),
|
|
m_mirror (false), m_cell_name (""), m_lib_name (""), m_pcell_parameters (""),
|
|
m_array (false), m_rows (1), m_columns (1),
|
|
m_row_x (0.0), m_row_y (0.0), m_column_x (0.0), m_column_y (0.0),
|
|
m_place_origin (false), m_reference_transaction_id (0),
|
|
m_needs_update (true), m_has_valid_cell (false), m_in_drag_drop (false),
|
|
m_current_cell (0), m_drag_drop_cell (0), m_cv_index (-1)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
lay::PropertiesPage *
|
|
InstService::properties_page (QWidget *parent)
|
|
{
|
|
return new edt::InstPropertiesPage (this, parent);
|
|
}
|
|
|
|
bool
|
|
InstService::do_activated ()
|
|
{
|
|
// Show editor options dialog to allow entering of parameters
|
|
std::vector<edt::MainService *> edt_main_services = view ()->get_plugins <edt::MainService> ();
|
|
if (edt_main_services.size () > 0) {
|
|
edt_main_services [0]->cm_edit_options ();
|
|
}
|
|
|
|
m_cv_index = view ()->active_cellview_index ();
|
|
m_has_valid_cell = false;
|
|
|
|
return true; // start editing immediately
|
|
}
|
|
|
|
bool
|
|
InstService::drag_enter_event (const db::DPoint &p, const lay::DragDropDataBase *data)
|
|
{
|
|
const lay::CellDragDropData *cd = dynamic_cast <const lay::CellDragDropData *> (data);
|
|
if (cd && cd->layout () == & view ()->active_cellview ()->layout ()) {
|
|
|
|
view ()->cancel ();
|
|
|
|
// NOTE: the cancel above might delete the cell we are dragging (if that is
|
|
// a non-placed PCell). Hence we need to check whether the cell still is valid
|
|
if (cd->layout ()->is_valid_cell_index (cd->cell_index ())) {
|
|
|
|
set_edit_marker (0);
|
|
|
|
m_cv_index = view ()->active_cellview_index ();
|
|
m_in_drag_drop = true;
|
|
m_drag_drop_cell = cd->cell_index ();
|
|
|
|
do_begin_edit (p);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
InstService::drag_move_event (const db::DPoint &p, const lay::DragDropDataBase * /*data*/)
|
|
{
|
|
if (m_in_drag_drop) {
|
|
do_mouse_move (p);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
InstService::drag_leave_event ()
|
|
{
|
|
if (m_in_drag_drop) {
|
|
set_edit_marker (0);
|
|
do_cancel_edit ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
InstService::selection_applies (const lay::ObjectInstPath &sel) const
|
|
{
|
|
return sel.is_cell_inst ();
|
|
}
|
|
|
|
bool
|
|
InstService::drop_event (const db::DPoint & /*p*/, const lay::DragDropDataBase * /*data*/)
|
|
{
|
|
if (m_in_drag_drop) {
|
|
set_edit_marker (0);
|
|
do_finish_edit ();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
InstService::do_begin_edit (const db::DPoint &p)
|
|
{
|
|
m_has_valid_cell = false;
|
|
m_disp = snap (p);
|
|
|
|
const lay::CellView &cv = view ()->cellview (m_cv_index);
|
|
if (! cv.is_valid ()) {
|
|
return;
|
|
}
|
|
|
|
if (cv.cell ()->is_proxy ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot put an instance into a PCell or library cell")));
|
|
}
|
|
|
|
m_trans = cv.context_trans ();
|
|
|
|
std::pair<bool, db::cell_index_type> ci = make_cell (cv);
|
|
if (ci.first) {
|
|
// use the snapped lower left corner of the bbox unless the origin is inside the bbox
|
|
db::Box cell_bbox = cv->layout ().cell (ci.second).bbox ();
|
|
if (! m_place_origin && ! cell_bbox.contains (db::Point ())) {
|
|
db::CplxTrans ct (1.0, m_angle, m_mirror, db::DVector ());
|
|
m_disp = db::DPoint () + (m_disp - snap (cell_bbox.transformed (ct).lower_left () * cv->layout ().dbu ()));
|
|
}
|
|
}
|
|
|
|
// compute the transformation variants
|
|
// TODO: this is duplicated code
|
|
// TODO: from this computed vector we take just the first one!
|
|
std::vector<db::DCplxTrans> tv;
|
|
for (lay::LayerPropertiesConstIterator l = view ()->begin_layers (); !l.at_end (); ++l) {
|
|
if (! l->has_children ()) {
|
|
int cvi = (l->cellview_index () >= 0) ? l->cellview_index () : 0;
|
|
if (cvi == m_cv_index) {
|
|
tv.insert (tv.end (), l->trans ().begin (), l->trans ().end ());
|
|
}
|
|
}
|
|
}
|
|
std::sort (tv.begin (), tv.end ());
|
|
tv.erase (std::unique (tv.begin (), tv.end ()), tv.end ());
|
|
if (! tv.empty ()) {
|
|
m_trans = db::VCplxTrans (1.0 / cv->layout ().dbu ()) * tv [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ();
|
|
}
|
|
|
|
lay::Marker *marker = new lay::Marker (view (), m_cv_index, ! show_shapes_of_instances (), show_shapes_of_instances () ? max_shapes_of_instances () : 0);
|
|
marker->set_vertex_shape (lay::ViewOp::Cross);
|
|
marker->set_vertex_size (9 /*cross vertex size*/);
|
|
set_edit_marker (marker);
|
|
update_marker ();
|
|
}
|
|
|
|
std::pair<bool, db::cell_index_type>
|
|
InstService::make_cell (const lay::CellView &cv)
|
|
{
|
|
if (m_in_drag_drop) {
|
|
return std::make_pair (true, m_drag_drop_cell);
|
|
}
|
|
|
|
if (m_has_valid_cell) {
|
|
return std::make_pair (true, m_current_cell);
|
|
}
|
|
|
|
lay::LayerState layer_state = view ()->layer_snapshot ();
|
|
|
|
db::Layout *layout;
|
|
db::Library *lib = db::LibraryManager::instance ().lib_ptr_by_name (m_lib_name);
|
|
|
|
// find the layout the cell has to be looked up: that is either the layout of the current instance or
|
|
// the library selected
|
|
if (lib) {
|
|
layout = &lib->layout ();
|
|
} else {
|
|
layout = &cv->layout ();
|
|
}
|
|
|
|
std::pair<bool, db::cell_index_type> ci = layout->cell_by_name (m_cell_name.c_str ());
|
|
std::pair<bool, db::pcell_id_type> pci = layout->pcell_by_name (m_cell_name.c_str ());
|
|
if (! ci.first && ! pci.first) {
|
|
// throw tl::Exception (tl::to_string (QObject::tr ("Not a valid cell name: %s")).c_str (), tl::to_string (cell_name_le->text ()).c_str ());
|
|
return std::pair<bool, db::cell_index_type> (false, 0);
|
|
}
|
|
|
|
m_reference_transaction_id = manager ()->transaction (tl::to_string (QObject::tr ("Create reference cell")), m_reference_transaction_id);
|
|
db::cell_index_type inst_cell_index = ci.second;
|
|
|
|
try {
|
|
|
|
// instantiate the PCell
|
|
if (pci.first) {
|
|
|
|
std::vector<tl::Variant> pv;
|
|
|
|
const db::PCellDeclaration *pc_decl = layout->pcell_declaration (pci.second);
|
|
if (pc_decl) {
|
|
|
|
std::map<std::string, tl::Variant> parameters;
|
|
tl::Extractor ex (m_pcell_parameters.c_str ());
|
|
while (! ex.at_end ()) {
|
|
std::string n;
|
|
ex.read_word_or_quoted (n);
|
|
ex.test (":");
|
|
ex.read (parameters.insert (std::make_pair (n, tl::Variant ())).first->second);
|
|
ex.test (";");
|
|
}
|
|
|
|
for (std::vector<db::PCellParameterDeclaration>::const_iterator pd = pc_decl->parameter_declarations ().begin (); pd != pc_decl->parameter_declarations ().end (); ++pd) {
|
|
std::map<std::string, tl::Variant>::const_iterator p = parameters.find (pd->get_name ());
|
|
if (p != parameters.end ()) {
|
|
pv.push_back (p->second);
|
|
} else {
|
|
pv.push_back (pd->get_default ());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
inst_cell_index = layout->get_pcell_variant (pci.second, pv);
|
|
|
|
}
|
|
|
|
// reference the library
|
|
if (lib) {
|
|
layout = & cv->layout ();
|
|
layout->cleanup ();
|
|
inst_cell_index = layout->get_lib_proxy (lib, inst_cell_index);
|
|
}
|
|
|
|
view ()->add_new_layers (layer_state);
|
|
|
|
manager ()->commit ();
|
|
|
|
} catch (...) {
|
|
manager ()->commit ();
|
|
throw;
|
|
}
|
|
|
|
m_has_valid_cell = true;
|
|
m_current_cell = inst_cell_index;
|
|
|
|
return std::pair<bool, db::cell_index_type> (true, inst_cell_index);
|
|
}
|
|
|
|
void
|
|
InstService::do_mouse_move (const db::DPoint &p)
|
|
{
|
|
set_cursor (lay::Cursor::cross);
|
|
|
|
const lay::CellView &cv = view ()->cellview (m_cv_index);
|
|
if (! cv.is_valid ()) {
|
|
return;
|
|
}
|
|
|
|
m_disp = snap (p);
|
|
|
|
std::pair<bool, db::cell_index_type> ci = make_cell (cv);
|
|
if (ci.first) {
|
|
// use the snapped lower left corner of the bbox unless the origin is inside the bbox
|
|
db::Box cell_bbox = cv->layout ().cell (ci.second).bbox ();
|
|
if (! m_place_origin && ! cell_bbox.contains (db::Point ())) {
|
|
db::CplxTrans ct (1.0, m_angle, m_mirror, db::DVector ());
|
|
m_disp = db::DPoint () + (m_disp - snap (cell_bbox.transformed (ct).lower_left () * cv->layout ().dbu ()));
|
|
}
|
|
}
|
|
|
|
update_marker ();
|
|
}
|
|
|
|
void
|
|
InstService::do_mouse_transform (const db::DPoint &p, db::DFTrans trans)
|
|
{
|
|
db::DCplxTrans ct (1.0, m_angle, m_mirror, db::DVector ());
|
|
ct *= db::DCplxTrans (trans);
|
|
|
|
m_angle = ct.angle ();
|
|
m_mirror = ct.is_mirror ();
|
|
|
|
db::DPoint r (m_row_x, m_row_y);
|
|
r.transform (trans);
|
|
m_row_x = r.x ();
|
|
m_row_y = r.y ();
|
|
|
|
db::DPoint c (m_column_x, m_column_y);
|
|
c.transform (trans);
|
|
m_column_x = c.x ();
|
|
m_column_y = c.y ();
|
|
|
|
// honour the new transformation
|
|
do_mouse_move (p);
|
|
}
|
|
|
|
bool
|
|
InstService::do_mouse_click (const db::DPoint &p)
|
|
{
|
|
do_mouse_move (p);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
InstService::do_finish_edit ()
|
|
{
|
|
try {
|
|
|
|
db::CellInstArray inst;
|
|
if (get_inst (inst)) {
|
|
|
|
// check for recursive hierarchy
|
|
const lay::CellView &cv = view ()->cellview (m_cv_index);
|
|
std::set <db::cell_index_type> called, callers;
|
|
|
|
cv->layout ().cell (inst.object ().cell_index ()).collect_called_cells (called);
|
|
called.insert (inst.object ().cell_index ());
|
|
cv->layout ().cell (cv.cell_index ()).collect_caller_cells (callers);
|
|
callers.insert (cv.cell_index ());
|
|
|
|
std::vector <db::cell_index_type> intersection;
|
|
std::set_intersection (called.begin (), called.end (), callers.begin (), callers.end (), std::back_inserter (intersection));
|
|
if (! intersection.empty ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Inserting this instance would create a recursive hierarchy")));
|
|
}
|
|
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create instance")), m_reference_transaction_id);
|
|
m_reference_transaction_id = 0;
|
|
db::Instance i = cv->layout ().cell (cv.cell_index ()).insert (inst);
|
|
cv->layout ().cleanup ();
|
|
manager ()->commit ();
|
|
|
|
if (m_in_drag_drop) {
|
|
|
|
lay::ObjectInstPath sel;
|
|
sel.set_cv_index (m_cv_index);
|
|
sel.set_topcell (cv.cell_index ());
|
|
sel.add_path (db::InstElement (i, db::CellInstArray::iterator ()));
|
|
|
|
add_selection (sel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_has_valid_cell = false;
|
|
m_in_drag_drop = false;
|
|
|
|
} catch (...) {
|
|
m_has_valid_cell = false;
|
|
m_in_drag_drop = false;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void
|
|
InstService::do_cancel_edit ()
|
|
{
|
|
// Undo "create reference" transactions which basically unfinished "create instance" transactions
|
|
if (m_reference_transaction_id > 0 && manager ()->last_transaction_id () == m_reference_transaction_id) {
|
|
manager ()->undo ();
|
|
}
|
|
|
|
m_reference_transaction_id = 0;
|
|
m_has_valid_cell = false;
|
|
m_in_drag_drop = false;
|
|
|
|
// clean up any proxy cells created so far
|
|
const lay::CellView &cv = view ()->cellview (m_cv_index);
|
|
if (cv.is_valid ()) {
|
|
cv->layout ().cleanup ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
InstService::configure (const std::string &name, const std::string &value)
|
|
{
|
|
if (name == cfg_edit_inst_cell_name) {
|
|
m_cell_name = value;
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_lib_name) {
|
|
m_lib_name = value;
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_pcell_parameters) {
|
|
m_pcell_parameters = value;
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_place_origin) {
|
|
tl::from_string (value, m_place_origin);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_scale) {
|
|
tl::from_string (value, m_scale);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_angle) {
|
|
tl::from_string (value, m_angle);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_mirror) {
|
|
tl::from_string (value, m_mirror);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_array) {
|
|
tl::from_string (value, m_array);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_rows) {
|
|
tl::from_string (value, m_rows);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_row_x) {
|
|
tl::from_string (value, m_row_x);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_row_y) {
|
|
tl::from_string (value, m_row_y);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_columns) {
|
|
tl::from_string (value, m_columns);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_column_x) {
|
|
tl::from_string (value, m_column_x);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
if (name == cfg_edit_inst_column_y) {
|
|
tl::from_string (value, m_column_y);
|
|
m_needs_update = true;
|
|
return true; // taken
|
|
}
|
|
|
|
return edt::Service::configure (name, value);
|
|
}
|
|
|
|
void
|
|
InstService::config_finalize ()
|
|
{
|
|
if (m_needs_update) {
|
|
m_has_valid_cell = false;
|
|
update_marker ();
|
|
m_needs_update = false;
|
|
}
|
|
|
|
edt::Service::config_finalize ();
|
|
}
|
|
|
|
void
|
|
InstService::update_marker ()
|
|
{
|
|
lay::Marker *marker = dynamic_cast<lay::Marker *> (edit_marker ());
|
|
if (marker) {
|
|
marker->set ();
|
|
db::CellInstArray inst;
|
|
if (get_inst (inst)) {
|
|
marker->set (inst, m_trans);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
InstService::get_inst (db::CellInstArray &inst)
|
|
{
|
|
const lay::CellView &cv = view ()->cellview (m_cv_index);
|
|
if (cv.is_valid ()) {
|
|
|
|
std::pair<bool, db::cell_index_type> ci = make_cell (cv);
|
|
if (ci.first) {
|
|
|
|
// compute the instance's transformation
|
|
db::VCplxTrans pt = (db::CplxTrans (cv->layout ().dbu ()) * m_trans).inverted ();
|
|
db::ICplxTrans trans;
|
|
if (m_in_drag_drop) {
|
|
trans = db::ICplxTrans (1.0, 0.0, false, pt * m_disp - db::Point ());
|
|
} else {
|
|
trans = db::ICplxTrans (m_scale, m_angle, m_mirror, pt * m_disp - db::Point ());
|
|
}
|
|
|
|
if (! m_in_drag_drop && m_array && m_rows > 0 && m_columns > 0) {
|
|
db::Vector row = db::Vector (pt * db::DVector (m_row_x, m_row_y));
|
|
db::Vector column = db::Vector (pt * db::DVector (m_column_x, m_column_y));
|
|
inst = db::CellInstArray (db::CellInst (ci.second), trans, row, column, m_rows, m_columns);
|
|
} else {
|
|
inst = db::CellInstArray (db::CellInst (ci.second), trans);
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace edt
|
|
|
|
|