klayout/src/edt/edtServiceImpl.cc

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