Merge pull request #1669 from KLayout/editor-hooks

Editor hooks
This commit is contained in:
Matthias Köfferlein 2024-04-17 22:37:29 +02:00 committed by GitHub
commit 1d873d2ea5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 2219 additions and 109 deletions

View File

@ -43,6 +43,7 @@
using them to select edges.
* Enhancement: New RBA/pya Features
- Main window title: MainWindow#title (property)
- MainWindow#synchronous (getter added)
- LayoutView#is_dirty?
- Triangulation: Region#delaunay
- Quality rasterizer: Region#rasterize

View File

@ -346,4 +346,156 @@ TriangulationProcessor::process (const db::Polygon &poly, std::vector<db::Polygo
}
}
// -----------------------------------------------------------------------------------
// DRCHullProcessor implementation
DRCHullProcessor::DRCHullProcessor (db::Coord d, db::metrics_type metrics, size_t n_circle)
: m_d (d), m_metrics (metrics), m_n_circle (n_circle)
{
// .. nothing yet ..
}
static void create_edge_segment_euclidian (std::vector<db::Point> &points, const db::Edge &e, const db::Edge &ee, db::Coord dist, size_t n_circle)
{
db::Vector d (e.d ());
db::Vector n (-d.y (), d.x ());
db::Vector dd (ee.d ());
db::Vector nn (-dd.y (), dd.x ());
if ((d.x () == 0 && d.y () == 0) || (dd.x () == 0 && dd.y () == 0)) {
// should not happen
return;
}
double f = dist / n.double_length ();
double ff = dist / nn.double_length ();
points.push_back (e.p1 () + db::Vector (n * f));
points.push_back (e.p2 () + db::Vector (n * f));
if (db::vprod_sign (nn, n) < 0) {
// concave corner
points.push_back (e.p2 ());
points.push_back (e.p2 () + db::Vector (nn * ff));
} else {
double amax;
if (db::vprod_sign (nn, n) == 0) {
amax = db::sprod_sign (nn, n) < 0 ? M_PI : 0.0;
} else {
amax = atan2 (db::vprod (nn, n), db::sprod (nn, n));
}
double da = M_PI * 2.0 / n_circle;
double f2 = f / cos (0.5 * da);
int na = int (floor (amax / da + db::epsilon));
double a0 = 0.5 * (amax - da * (na - 1));
for (int i = 0; i < na; ++i) {
double a = i * da + a0;
points.push_back (e.p2 () + db::Vector (d * (f2 * sin (a)) + n * (f2 * cos (a))));
}
}
}
static void create_edge_segment_square (std::vector<db::Point> &points, const db::Edge &e, db::Coord dist)
{
db::Vector d (e.d ());
db::Vector n (-d.y (), d.x ());
if (d.x () == 0 && d.y () == 0) {
return;
}
double f = dist / n.double_length ();
points.push_back (e.p1 ());
points.push_back (e.p1 () + db::Vector (d * -f));
points.push_back (e.p1 () + db::Vector (d * -f + n * f));
points.push_back (e.p2 () + db::Vector (d * f + n * f));
points.push_back (e.p2 () + db::Vector (d * f));
}
static void create_edge_segment_projection (std::vector<db::Point> &points, const db::Edge &e, db::Coord dist)
{
db::Vector d (e.d ());
db::Vector n (-d.y (), d.x ());
if (d.x () == 0 && d.y () == 0) {
return;
}
double f = dist / n.double_length ();
points.push_back (e.p1 ());
points.push_back (e.p1 () + db::Vector (n * f));
points.push_back (e.p2 () + db::Vector (n * f));
}
static void create_edge_segment (std::vector<db::Point> &points, db::metrics_type metrics, const db::Edge &e, const db::Edge &ee, db::Coord d, size_t n_circle)
{
if (metrics == db::Euclidian) {
create_edge_segment_euclidian (points, e, ee, d, n_circle);
} else if (metrics == db::Square) {
create_edge_segment_square (points, e, d);
} else if (metrics == db::Projection) {
create_edge_segment_projection (points, e, d);
}
}
void
DRCHullProcessor::process (const db::Polygon &poly, std::vector<db::Polygon> &result) const
{
db::EdgeProcessor ep;
std::vector<db::Point> points;
for (unsigned int i = 0; i < poly.holes () + 1; ++i) {
points.clear ();
auto c = poly.contour (i);
if (c.size () < 2) {
continue;
}
for (auto p = c.begin (); p != c.end (); ++p) {
auto pp = p;
if (++pp == c.end ()) {
pp = c.begin ();
}
auto ppp = pp;
if (++ppp == c.end ()) {
ppp = c.begin ();
}
create_edge_segment (points, m_metrics, db::Edge (*p, *pp), db::Edge (*pp, *ppp), m_d, m_n_circle);
}
for (auto p = points.begin (); p != points.end (); ++p) {
auto pp = p;
if (++ pp == points.end ()) {
pp = points.begin ();
}
ep.insert (db::Edge (*p, *pp));
}
}
db::SimpleMerge op;
db::PolygonContainer psink (result);
db::PolygonGenerator pg (psink, false);
ep.process (pg, op);
}
}

View File

@ -29,6 +29,7 @@
#include "dbPolygonTools.h"
#include "dbEdgesUtils.h"
#include "dbTriangles.h"
#include "dbEdgePairRelations.h"
namespace db
{
@ -464,6 +465,24 @@ private:
db::MagnificationAndOrientationReducer m_vars;
};
/**
* @brief Computes DRC hulls for DRC space visualization
*/
class DB_PUBLIC_TEMPLATE DRCHullProcessor
: public db::PolygonProcessorBase
{
public:
DRCHullProcessor (db::Coord d, db::metrics_type metrics, size_t n_circle = 64);
void process (const db::Polygon &poly, std::vector<db::Polygon> &result) const;
private:
db::Coord m_d;
db::metrics_type m_metrics;
size_t m_n_circle;
};
}
#endif

View File

@ -24,6 +24,7 @@
#include "dbShape.h"
#include "dbBoxConvert.h"
#include "dbPolygonTools.h"
#include "dbHash.h"
#include "tlCpp.h"
namespace db
@ -862,6 +863,24 @@ Shape::box_type Shape::rectangle () const
return box_type ();
}
size_t
Shape::hash_value () const
{
size_t h = size_t (m_type);
h = std::hcombine (h, std::hfunc (m_trans));
if (m_stable) {
// Use the bytes of the iterator binary pattern (see operator<)
for (unsigned int i = 0; i < sizeof (tl::reuse_vector<box_type>::const_iterator); ++i) {
h = std::hcombine (h, size_t (m_generic.iter[i]));
}
} else {
h = std::hcombine (h, size_t (m_generic.any));
}
return h;
}
std::string
Shape::to_string () const
{

View File

@ -2769,6 +2769,11 @@ public:
return m_trans < d.m_trans;
}
/**
* @brief Hash value
*/
size_t hash_value () const;
/**
* @brief Convert to a string
*/
@ -2837,6 +2842,21 @@ public:
};
} // namespace db
namespace std
{
// provide a template specialization for std::hash<T>
template <>
struct hash <db::Shape>
{
size_t operator() (const db::Shape &s) const
{
return s.hash_value ();
}
};
} // namespace std
#endif

View File

@ -436,6 +436,11 @@ static db::Region refined_delaunay (const db::Region *r, double max_area, double
return res;
}
static db::Region drc_hull (const db::Region *r, db::metrics_type metrics, db::Coord space, size_t n_circle)
{
return r->processed (db::DRCHullProcessor (space, metrics, n_circle));
}
static db::Region minkowski_sum_pe (const db::Region *r, const db::Edge &e)
{
return r->processed (db::minkowski_sum_computation<db::Edge> (e));
@ -2782,6 +2787,17 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"The resulting polygons are not merged. In order to remove overlaps, use the \\merge or \\merged method."
"Merged semantics applies for the input of this method (see \\merged_semantics= for a description of this concept)\n"
) +
method_ext ("drc_hull", &drc_hull, gsi::arg ("metrics"), gsi::arg ("space"), gsi::arg ("n_circle", size_t (64)),
"@brief Computes a visualization of the forbidden region for a DRC space check\n"
"\n"
"@param metrics The metrics to apply\n"
"@param space The space value to apply\n"
"@param n_circle The full-circle number of points for the Euclidian space visualization\n"
"\n"
"@return The new polygons representing the forbidden region.\n"
"\n"
"This method has been introduced in version 0.29.1.\n"
) +
method_ext ("move", &move_p, gsi::arg ("v"),
"@brief Moves the region\n"
"\n"

View File

@ -2131,6 +2131,21 @@ Class<db::Shape> decl_Shape ("db", "Shape",
"Equality of shapes is not specified by the identity of the objects but by the\n"
"identity of the pointers - both shapes must refer to the same object.\n"
) +
gsi::method ("<", &db::Shape::operator<, gsi::arg ("other"),
"@brief Less operator\n"
"\n"
"The less operator implementation is based on pointers and not strictly reproducible."
"However, it is good enough so Shape objects can serve as keys in hashes (see also \\hash).\n"
"\n"
"This method has been introduced in version 0.29.1."
) +
gsi::method ("hash", &db::Shape::hash_value,
"@brief Hash function\n"
"\n"
"The hash function enables Shape objects as keys in hashes.\n"
"\n"
"This method has been introduced in version 0.29.1."
) +
gsi::method ("to_s", &db::Shape::to_string,
"@brief Create a string showing the contents of the reference\n"
"\n"

View File

@ -36,8 +36,10 @@ DEFINES += MAKE_EDT_LIBRARY
HEADERS = \
edtDialogs.h \
edtEditorHooks.h \
edtEditorOptionsPages.h \
edtInstPropertiesPage.h \
edtMoveTrackerService.h \
edtPCellParametersPage.h \
edtPropertiesPages.h \
edtPropertiesPageUtils.h \
@ -45,12 +47,15 @@ HEADERS = \
SOURCES = \
edtDialogs.cc \
edtEditorHooks.cc \
edtEditorOptionsPages.cc \
edtInstPropertiesPage.cc \
edtMoveTrackerService.cc \
edtPCellParametersPage.cc \
edtPropertiesPages.cc \
edtPropertiesPageUtils.cc \
edtRecentConfigurationPage.cc
edtRecentConfigurationPage.cc \
gsiDeclEdtEditorHooks.cc
# Enabled without Qt:

View File

@ -0,0 +1,161 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 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 "edtEditorHooks.h"
#include "tlObjectCollection.h"
#include "tlStaticObjects.h"
namespace edt
{
// ---------------------------------------------------------------
// EditorHooksManager definition and implementation
class EditorHooksManager;
static EditorHooksManager *sp_instance = 0;
static bool sp_instance_initialized = false;
class EditorHooksManager
{
public:
EditorHooksManager ()
{
// .. nothing yet ..
}
~EditorHooksManager ()
{
sp_instance = 0;
}
static EditorHooksManager *instance ()
{
if (! sp_instance && ! sp_instance_initialized) {
sp_instance = new EditorHooksManager ();
sp_instance_initialized = true;
tl::StaticObjects::reg (&sp_instance);
}
return sp_instance;
}
void register_editor_hooks (EditorHooks *hooks, const std::string &name)
{
// needed, so we do not loose the object in case we erase it:
tl::shared_ptr<EditorHooks> tmp (hooks);
// remove other hooks with the same name or with an identical address
for (auto h = m_hooks.begin (); h != m_hooks.end (); ) {
auto hh = h++;
if (hh.operator-> () && (hh.operator-> () == hooks || hh->name () == name)) {
m_hooks.erase (hh);
}
}
hooks->set_name (name);
m_hooks.push_back (hooks);
}
tl::weak_collection<EditorHooks>
get_editor_hooks (const std::string &for_technology)
{
tl::weak_collection<EditorHooks> res;
for (auto h = m_hooks.begin (); h != m_hooks.end (); ++h) {
if (h.operator-> () && (! h->for_technologies () || h->is_for_technology (for_technology))) {
res.push_back (h.operator-> ());
}
}
return res;
}
private:
tl::shared_collection<EditorHooks> m_hooks;
};
// ---------------------------------------------------------------
// EditorHooks implementation
EditorHooks::EditorHooks ()
{
// .. nothing yet ..
}
EditorHooks::~EditorHooks ()
{
// .. nothing yet ..
}
bool
EditorHooks::is_for_technology (const std::string &name) const
{
return m_technologies.find (name) != m_technologies.end ();
}
bool
EditorHooks::for_technologies () const
{
return ! m_technologies.empty ();
}
void
EditorHooks::set_technology (const std::string &t)
{
m_technologies.clear ();
if (! t.empty ()) {
m_technologies.insert (t);
}
}
void
EditorHooks::clear_technologies ()
{
m_technologies.clear ();
}
void
EditorHooks::add_technology (const std::string &tech)
{
m_technologies.insert (tech);
}
void
EditorHooks::register_editor_hooks (EditorHooks *hooks, const std::string &name)
{
if (EditorHooksManager::instance ()) {
hooks->keep ();
EditorHooksManager::instance ()->register_editor_hooks (hooks, name);
}
}
tl::weak_collection<EditorHooks>
EditorHooks::get_editor_hooks (const std::string &for_technology)
{
if (EditorHooksManager::instance ()) {
return EditorHooksManager::instance ()->get_editor_hooks (for_technology);
} else {
return tl::weak_collection<EditorHooks> ();
}
}
}

View File

@ -0,0 +1,294 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 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
*/
#ifndef HDR_edtEditorHooks
#define HDR_edtEditorHooks
#include "edtCommon.h"
#include "dbTrans.h"
#include "gsiObject.h"
#include "tlExceptions.h"
#include "tlLog.h"
#include "tlObjectCollection.h"
#include <set>
#include <string>
namespace lay
{
class CellViewRef;
class LayoutViewBase;
class LayerProperties;
class ObjectInstPath;
}
namespace db
{
class Instance;
class Shape;
}
namespace edt
{
/**
* @ brief The editor hooks handler object
*
* Editor hooks are a way to hook into the editor feature - for example
* to implement dynamic DRC or display hints.
*
* The protocols are:
*
* 1. Object Creation
*
* begin_create_shapes { begin_new_shapes { create_shape } end_new_shapes } [ commit_shapes ] end_create_shapes
* begin_create_instances { begin_new_instances { create_instance } end_new_instances } [ commit_instances ] end_create_instances
*
* 2. Interactive edit (move, transform, interactive clone)
*
* begin_edit { begin_edits { transformed | modified } end_edits } [ commit_edit ] end_edit
*
* Notation: { ... } means the sequence can be repeated, [ ... ] means the call is optional.
*/
class EDT_PUBLIC EditorHooks
: public gsi::ObjectBase, public tl::Object
{
public:
/**
* @brief Constructor
*
* The name is arbitrary, but should be unique, as hooks with the
* same name replace each other. This is a debugging aid for GSI as we can
* re-register hooks while we keep them in the system.
*/
EditorHooks ();
/**
* @brief Destructor
*/
virtual ~EditorHooks ();
// shape creation protocol
virtual void begin_create_shapes (lay::CellViewRef & /*cv*/, const lay::LayerProperties & /*layer*/) { }
virtual void begin_new_shapes () { }
virtual void create_shape (const db::Shape & /*shape*/, const db::CplxTrans & /*view_trans*/) { }
virtual void end_new_shapes () { }
virtual void commit_shapes () { }
virtual void end_create_shapes () { }
// instance creation protocol
virtual void begin_create_instances (lay::CellViewRef & /*cv*/) { }
virtual void begin_new_instances () { }
virtual void create_instance (const db::Instance & /*instance*/, const db::CplxTrans & /*view_trans*/) { }
virtual void end_new_instances () { }
virtual void commit_instances () { }
virtual void end_create_instances () { }
// editing protocol
virtual void begin_edit (lay::CellViewRef & /*cv*/) { }
virtual void begin_edits () { }
virtual void transformed (const lay::ObjectInstPath & /*object*/, const db::ICplxTrans & /*applied*/, const db::CplxTrans & /*view_trans*/) { }
virtual void modified (const lay::ObjectInstPath & /*object*/, const db::Shape & /*shape*/, const db::CplxTrans & /*view_trans*/) { }
virtual void end_edits () { }
virtual void commit_edit () { }
virtual void end_edit () { }
/**
* @brief Gets the name
*/
const std::string &name () const
{
return m_name;
}
/**
* @brief Sets the name
*/
void set_name (const std::string &name)
{
m_name = name;
}
/**
* @brief Gets the technology name this hook is associated with
*
* If this attribute is non-empty, the hook is selected only when the given technology is
* used for the layout.
*/
const std::set<std::string> &get_technologies () const
{
return m_technologies;
}
/**
* @brief Gets a value indicating whether this hook is associated with the given technology
*/
bool is_for_technology (const std::string &name) const;
/**
* @brief Gets a value indicating whether the hook is associated with any technology
*/
bool for_technologies () const;
/**
* @brief Sets the technology name this hook is associated with
*
* This will reset the list of technologies to this one.
* If the given technology string is empty, the list of technologies will be cleared.
*/
void set_technology (const std::string &t);
/**
* @brief Clears the list of technologies this hook is associated with
*/
void clear_technologies ();
/**
* @brief Additionally associate the hook with the given technology
*/
void add_technology (const std::string &tech);
/**
* @brief Registers the editor hook
*/
static void register_editor_hooks (EditorHooks *hooks, const std::string &name);
/**
* @brief Gets the editor hooks for a given technology
*
* The order of the hooks is determined by the registration order.
*/
static tl::weak_collection<EditorHooks> get_editor_hooks (const std::string &for_technology);
private:
std::set<std::string> m_technologies;
std::string m_name;
// no copying.
EditorHooks &operator= (const EditorHooks &);
EditorHooks (const EditorHooks &);
};
/**
* @brief A helper function to call editor hooks in the right sequence and with error handling
*/
inline
void call_editor_hooks (const tl::weak_collection<EditorHooks> &hooks, void (EditorHooks::*meth) ())
{
for (auto h = hooks.begin (); h != hooks.end (); ++h) {
try {
if (h.operator-> ()) {
(const_cast<EditorHooks *> (h.operator-> ())->*meth) ();
}
} catch (tl::CancelException &) {
return;
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (std::exception &ex) {
tl::error << ex.what ();
}
}
}
/**
* @brief A helper function to call editor hooks in the right sequence and with error handling
*
* This version provides one argument
*/
template <class A1>
inline
void call_editor_hooks (const tl::weak_collection<EditorHooks> &hooks, void (EditorHooks::*meth) (A1), A1 a1)
{
for (auto h = hooks.begin (); h != hooks.end (); ++h) {
try {
if (h.operator-> ()) {
(const_cast<EditorHooks *> (h.operator-> ())->*meth) (a1);
}
} catch (tl::CancelException &) {
return;
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (std::exception &ex) {
tl::error << ex.what ();
}
}
}
/**
* @brief A helper function to call editor hooks in the right sequence and with error handling
*
* This version provides two arguments
*/
template <class A1, class A2>
inline
void call_editor_hooks (const tl::weak_collection<EditorHooks> &hooks, void (EditorHooks::*meth) (A1, A2), A1 a1, A2 a2)
{
for (auto h = hooks.begin (); h != hooks.end (); ++h) {
try {
if (h.operator-> ()) {
(const_cast<EditorHooks *> (h.operator-> ())->*meth) (a1, a2);
}
} catch (tl::CancelException &) {
return;
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (std::exception &ex) {
tl::error << ex.what ();
}
}
}
/**
* @brief A helper function to call editor hooks in the right sequence and with error handling
*
* This version provides three arguments
*/
template <class A1, class A2, class A3>
inline
void call_editor_hooks (const tl::weak_collection<EditorHooks> &hooks, void (EditorHooks::*meth) (A1, A2, A3), A1 a1, A2 a2, A3 a3)
{
for (auto h = hooks.begin (); h != hooks.end (); ++h) {
try {
if (h.operator-> ()) {
(const_cast<EditorHooks *> (h.operator-> ())->*meth) (a1, a2, a3);
}
} catch (tl::CancelException &) {
return;
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (std::exception &ex) {
tl::error << ex.what ();
}
}
}
}
#endif

View File

@ -0,0 +1,168 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 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 "layLayoutViewBase.h"
#include "edtMoveTrackerService.h"
#include "edtService.h"
#include "edtPartialService.h"
namespace edt
{
// -------------------------------------------------------------
MoveTrackerService::MoveTrackerService (lay::LayoutViewBase *view)
: lay::EditorServiceBase (view),
mp_view (view)
{
// .. nothing yet ..
}
MoveTrackerService::~MoveTrackerService ()
{
// .. nothing yet ..
}
bool
MoveTrackerService::begin_move (lay::Editable::MoveMode mode, const db::DPoint & /*p*/, lay::angle_constraint_type /*ac*/)
{
if (view ()->is_editable () && mode == lay::Editable::Selected) {
open_editor_hooks ();
}
return false;
}
void
MoveTrackerService::issue_edit_events ()
{
if (m_editor_hooks.empty ()) {
return;
}
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::begin_edits);
// build the transformation variants cache
TransformationVariants tv (view ());
std::vector<edt::Service *> services = view ()->get_plugins<edt::Service> ();
std::vector<lay::ObjectInstPath> sel;
for (auto s = services.begin (); s != services.end (); ++s) {
edt::Service *svc = *s;
sel.clear ();
svc->get_selection (sel);
for (auto r = sel.begin (); r != sel.end (); ++r) {
const lay::CellView &cv = view ()->cellview (r->cv_index ());
// compute the transformation into context cell's micron space
double dbu = cv->layout ().dbu ();
db::CplxTrans gt = db::CplxTrans (dbu) * cv.context_trans () * r->trans ();
// get one representative global transformation
const std::vector<db::DCplxTrans> *tv_list = 0;
if (r->is_cell_inst ()) {
tv_list = tv.per_cv (r->cv_index ());
} else {
tv_list = tv.per_cv_and_layer (r->cv_index (), r->layer ());
}
if (tv_list && ! tv_list->empty ()) {
gt = tv_list->front () * gt;
}
// compute the move transformation in local object space
db::ICplxTrans applied = gt.inverted () * db::DCplxTrans (svc->move_trans ()) * gt;
call_editor_hooks<const lay::ObjectInstPath &, const db::ICplxTrans &, const db::CplxTrans &> (m_editor_hooks, &edt::EditorHooks::transformed, *r, applied, gt);
}
}
// make the Partial Edit Service issue "modify" events
std::vector<edt::PartialService *> partial_services = view ()->get_plugins<edt::PartialService> ();
for (auto s = partial_services.begin (); s != partial_services.end (); ++s) {
(*s)->issue_editor_hook_calls (m_editor_hooks);
}
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::end_edits);
}
void
MoveTrackerService::move (const db::DPoint & /*pu*/, lay::angle_constraint_type /*ac*/)
{
// we don't interpret this event, but use it to request status from the editor services
issue_edit_events ();
}
void
MoveTrackerService::move_transform (const db::DPoint & /*pu*/, db::DFTrans /*tr*/, lay::angle_constraint_type /*ac*/)
{
// we don't interpret this event, but use it to request status from the editor services
issue_edit_events ();
}
void
MoveTrackerService::end_move (const db::DPoint & /*p*/, lay::angle_constraint_type /*ac*/)
{
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::commit_edit);
move_cancel (); // formally this functionality fits here
}
void
MoveTrackerService::edit_cancel ()
{
move_cancel ();
}
void
MoveTrackerService::move_cancel ()
{
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::end_edit);
m_editor_hooks.clear ();
}
void
MoveTrackerService::open_editor_hooks ()
{
lay::CellViewRef cv_ref (view ()->cellview_ref (view ()->active_cellview_index ()));
if (! cv_ref.is_valid ()) {
return;
}
std::string technology;
if (cv_ref->layout ().technology ()) {
technology = cv_ref->layout ().technology ()->name ();
}
m_editor_hooks = edt::EditorHooks::get_editor_hooks (technology);
call_editor_hooks<lay::CellViewRef &> (m_editor_hooks, &edt::EditorHooks::begin_edit, (lay::CellViewRef &) cv_ref);
}
} // namespace edt

View File

@ -0,0 +1,97 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 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
*/
#ifndef HDR_edtMoveTrackerService
#define HDR_edtMoveTrackerService
#include "edtCommon.h"
#include "layEditorServiceBase.h"
#include "edtEditorHooks.h"
namespace edt {
/**
* @brief A service tracking move commands a forwarding them to the editor hooks
*/
class EDT_PUBLIC MoveTrackerService
: public lay::EditorServiceBase
{
public:
/**
* @brief The constructor
*/
MoveTrackerService (lay::LayoutViewBase *view);
/**
* @brief The destructor
*/
~MoveTrackerService ();
/**
* @brief Begin a "move" operation
*/
virtual bool begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::angle_constraint_type ac);
/**
* @brief Continue a "move" operation
*/
virtual void move (const db::DPoint &p, lay::angle_constraint_type ac);
/**
* @brief Transform during a move operation
*/
virtual void move_transform (const db::DPoint &p, db::DFTrans tr, lay::angle_constraint_type ac);
/**
* @brief Terminate a "move" operation
*/
virtual void end_move (const db::DPoint &p, lay::angle_constraint_type ac);
/**
* @brief Access to the view object
*/
lay::LayoutViewBase *view () const
{
tl_assert (mp_view != 0);
return mp_view;
}
/**
* @brief Cancel any edit operations (such as move)
*/
virtual void edit_cancel ();
private:
lay::LayoutViewBase *mp_view;
tl::weak_collection<edt::EditorHooks> m_editor_hooks;
void move_cancel ();
void open_editor_hooks ();
void issue_edit_events ();
};
}
#endif

View File

@ -1416,7 +1416,216 @@ PartialService::transform (const db::DCplxTrans &tr)
selection_to_view ();
}
void
void
PartialService::open_editor_hooks ()
{
lay::CellViewRef cv_ref (view ()->cellview_ref (view ()->active_cellview_index ()));
if (! cv_ref.is_valid ()) {
return;
}
std::string technology;
if (cv_ref->layout ().technology ()) {
technology = cv_ref->layout ().technology ()->name ();
}
m_editor_hooks = edt::EditorHooks::get_editor_hooks (technology);
call_editor_hooks<lay::CellViewRef &> (m_editor_hooks, &edt::EditorHooks::begin_edit, (lay::CellViewRef &) cv_ref);
}
void
PartialService::close_editor_hooks (bool commit)
{
if (commit) {
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::commit_edit);
}
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::end_edit);
m_editor_hooks.clear ();
}
void
PartialService::issue_editor_hook_calls (const tl::weak_collection<edt::EditorHooks> &hooks)
{
if (hooks.empty ()) {
return;
}
// NOTE: needs to be called during move operations
db::DTrans move_trans = db::DTrans (m_current - m_start);
// build the transformation variants cache
TransformationVariants tv (view ());
// Issue editor hook calls for the shape modification events
// 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);
}
}
}
db::Shapes tmp_shapes;
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 = tmp_shapes.insert (sps->first);
for (std::vector<partial_objects::iterator>::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) {
std::map <EdgeWithIndex, db::Edge> new_edges;
std::map <PointWithIndex, db::Point> new_points;
shape = modify_shape (tv, shape, (*rr)->first, (*rr)->second, move_trans, new_edges, new_points);
}
for (std::vector<partial_objects::iterator>::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) {
const lay::ObjectInstPath &sel = (*rr)->first;
const lay::CellView &cv = view ()->cellview (sel.cv_index ());
// compute the transformation into context cell's micron space
double dbu = cv->layout ().dbu ();
db::CplxTrans gt = db::CplxTrans (dbu) * cv.context_trans () * sel.trans ();
// get one representative global transformation
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (sel.cv_index (), sel.layer ());
if (tv_list && ! tv_list->empty ()) {
gt = tv_list->front () * gt;
}
call_editor_hooks<const lay::ObjectInstPath &, const db::Shape &, const db::CplxTrans &> (hooks, &edt::EditorHooks::modified, (*rr)->first, shape, gt);
}
}
// Issue editor hook calls for the instance transformation events
// 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);
}
}
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) {
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);
db::DCplxTrans tvt;
if (tv_list && ! tv_list->empty ()) {
tvt = tv_list->front ();
}
for (auto inst = ibc->second.begin (); inst != ibc->second.end (); ++inst) {
db::CplxTrans gt = tvt * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans () * (*inst)->first.trans ();
db::ICplxTrans applied = gt.inverted () * db::DCplxTrans (move_trans) * gt;
call_editor_hooks<const lay::ObjectInstPath &, const db::ICplxTrans &, const db::CplxTrans &> (hooks, &edt::EditorHooks::transformed, (*inst)->first, applied, gt);
}
}
}
}
db::Shape
PartialService::modify_shape (TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set <EdgeWithIndex> &sel, const db::DTrans &move_trans, std::map <EdgeWithIndex, db::Edge> &new_edges, std::map <PointWithIndex, db::Point> &new_points)
{
tl_assert (shape_in.shapes () != 0);
db::Shape shape = shape_in;
db::Shapes &shapes = *shape_in.shapes ();
const lay::CellView &cv = view ()->cellview (path.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 () * path.trans ());
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (path.cv_index (), path.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 ());
create_shift_sets (shape, sel, new_points, new_edges, move_vector);
// modify the shapes and insert
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);
}
} else if (shape.is_point ()) {
db::Point p;
shape.point (p);
std::map <PointWithIndex, db::Point>::const_iterator np = new_points.find (PointWithIndex (p, 0, 0));
if (np != new_points.end ()) {
shape = shapes.replace (shape, np->second);
}
}
return shape;
}
void
PartialService::transform_selection (const db::DTrans &move_trans)
{
// build the transformation variants cache
@ -1444,79 +1653,9 @@ PartialService::transform_selection (const db::DTrans &move_trans)
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);
}
} else if (shape.is_point ()) {
db::Point p;
shape.point (p);
std::map <PointWithIndex, db::Point>::const_iterator np = new_points.find (PointWithIndex (p, 0, 0));
if (np != new_points.end ()) {
shape = shapes.replace (shape, np->second);
}
}
shape = modify_shape (tv, shape, r->first, r->second, move_trans, new_edges, new_points);
// transform the selection
@ -1625,6 +1764,8 @@ PartialService::edit_cancel ()
ui ()->ungrab_mouse (this);
close_editor_hooks (false);
selection_to_view ();
}
@ -1671,6 +1812,10 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo
selection_to_view ();
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::begin_edits);
issue_editor_hook_calls (m_editor_hooks);
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::end_edits);
m_alt_ac = lay::AC_Global;
} else if (prio) {
@ -1796,6 +1941,8 @@ PartialService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bo
ui ()->grab_mouse (this, true);
open_editor_hooks ();
}
m_alt_ac = lay::AC_Global;
@ -1858,6 +2005,8 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo
m_dragging = false;
selection_to_view ();
close_editor_hooks (true);
m_alt_ac = lay::AC_Global;
return true;
@ -1991,6 +2140,8 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo
m_current = m_start = p;
}
open_editor_hooks ();
}
selection_to_view ();
@ -2023,6 +2174,8 @@ PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int butt
m_alt_ac = ac_from_buttons (buttons);
close_editor_hooks (false);
// stop dragging
ui ()->ungrab_mouse (this);
m_dragging = false;
@ -2353,6 +2506,8 @@ PartialService::end_move (const db::DPoint & /*p*/, lay::angle_constraint_type a
clear_mouse_cursors ();
close_editor_hooks (false);
m_alt_ac = lay::AC_Global;
}
@ -2530,6 +2685,8 @@ PartialService::del ()
m_dragging = false;
selection_to_view ();
close_editor_hooks (false);
// clean up the layouts that need to do so.
for (std::set<db::Layout *>::const_iterator l = needs_cleanup.begin (); l != needs_cleanup.end (); ++l) {
(*l)->cleanup ();

View File

@ -34,6 +34,7 @@
#include "tlDeferredExecution.h"
#include "edtUtils.h"
#include "edtConfig.h"
#include "edtEditorHooks.h"
#if defined(HAVE_QT)
# include <QObject>
@ -316,6 +317,11 @@ public:
*/
virtual void edit_cancel ();
/**
* @brief Issues editor hook calls ("modified") for the current selection and the given move transformation
*/
void issue_editor_hook_calls (const tl::weak_collection<edt::EditorHooks> &hooks);
#if defined(HAVE_QT)
public slots:
void timeout ();
@ -361,6 +367,8 @@ private:
bool m_hover_wait;
db::DPoint m_hover_point;
tl::weak_collection<edt::EditorHooks> m_editor_hooks;
// Deferred method to update the selection
tl::DeferredMethod<edt::PartialService> dm_selection_to_view;
@ -392,6 +400,10 @@ private:
db::DEdge single_selected_edge () const;
bool handle_guiding_shape_changes ();
void transform_selection (const db::DTrans &move_trans);
db::Shape modify_shape (TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set<EdgeWithIndex> &sel, const db::DTrans &move_trans, std::map <EdgeWithIndex, db::Edge> &new_edges, std::map <PointWithIndex, db::Point> &new_points);
void open_editor_hooks ();
void close_editor_hooks (bool commit);
};
}

View File

@ -34,6 +34,7 @@
#include "edtServiceImpl.h"
#include "edtMainService.h"
#include "edtPartialService.h"
#include "edtMoveTrackerService.h"
#if defined(HAVE_QT)
# include "edtEditorOptionsPages.h"
# include "edtRecentConfigurationPage.h"
@ -565,5 +566,26 @@ static tl::RegisteredClass<lay::PluginDeclaration> config_decl30 (
"edt::PartialService"
);
class MoveTrackerPluginDeclaration
: public lay::PluginDeclaration
{
public:
MoveTrackerPluginDeclaration ()
{
// .. nothing yet ..
}
virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*root*/, lay::LayoutViewBase *view) const
{
return new edt::MoveTrackerService (view);
}
};
static tl::RegisteredClass<lay::PluginDeclaration> config_decl40 (
new MoveTrackerPluginDeclaration (),
4100,
"edt::MoveTrackerService"
);
}

View File

@ -381,6 +381,22 @@ public:
*/
std::pair<bool, lay::ObjectInstPath> handle_guiding_shape_changes (const lay::ObjectInstPath &obj) const;
/**
* @brief Gets a value indicating whether a move operation is ongoing
*/
bool is_moving () const
{
return m_moving;
}
/**
* @brief Gets the current move transformation (in DBU space on context cell level)
*/
const db::DTrans &move_trans () const
{
return m_move_trans;
}
protected:
/**
* @brief Update m_markers to reflect the selection

View File

@ -131,7 +131,7 @@ ShapeEditService::get_edit_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 ()));
mp_cell = cv.cell ();
if (mp_cell->is_proxy ()) {
throw tl::Exception (tl::to_string (tr ("Cannot put a shape into a PCell or library cell")));
@ -157,7 +157,7 @@ ShapeEditService::update_edit_layer (const lay::LayerPropertiesConstIterator &cl
return;
}
if (cv->layout ().cell (cv.cell_index ()).is_proxy ()) {
if (cv.cell ()->is_proxy ()) {
return;
}
@ -198,7 +198,7 @@ ShapeEditService::update_edit_layer (const lay::LayerPropertiesConstIterator &cl
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 ()));
mp_cell = cv.cell ();
current_layer_changed ();
}
@ -400,6 +400,47 @@ ShapeEditService::deliver_shape (const db::Point &point)
}
}
void
ShapeEditService::open_editor_hooks ()
{
std::string technology;
if (mp_layout && mp_layout->technology ()) {
technology = mp_layout->technology ()->name ();
}
m_editor_hooks = edt::EditorHooks::get_editor_hooks (technology);
lay::CellViewRef cv_ref (view ()->cellview_ref (m_cv_index));
call_editor_hooks<lay::CellViewRef &, const lay::LayerProperties &> (m_editor_hooks, &edt::EditorHooks::begin_create_shapes, cv_ref, *view ()->current_layer ());
}
void
ShapeEditService::close_editor_hooks (bool with_commit)
{
if (with_commit) {
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::commit_shapes);
}
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::end_create_shapes);
m_editor_hooks.clear ();
}
template <class Shape>
void
ShapeEditService::deliver_shape_to_hooks (const Shape &shape)
{
db::Shapes tmp (true);
db::Shape s = tmp.insert (shape);
call_editor_hooks<const db::Shape &, const db::CplxTrans &> (m_editor_hooks, &edt::EditorHooks::create_shape, s, trans ().inverted ());
}
// explicit instantiations
template void ShapeEditService::deliver_shape_to_hooks<db::Polygon> (const db::Polygon &);
template void ShapeEditService::deliver_shape_to_hooks<db::Path> (const db::Path &);
template void ShapeEditService::deliver_shape_to_hooks<db::Box> (const db::Box &);
template void ShapeEditService::deliver_shape_to_hooks<db::Point> (const db::Point &);
template void ShapeEditService::deliver_shape_to_hooks<db::Text> (const db::Text &);
// -----------------------------------------------------------------------------
// PolygonService implementation
@ -433,6 +474,8 @@ PolygonService::do_begin_edit (const db::DPoint &p)
m_points.push_back (pp);
m_closure_set = false;
open_editor_hooks ();
update_marker ();
}
@ -503,33 +546,37 @@ PolygonService::do_mouse_click (const db::DPoint &p)
void
PolygonService::do_finish_edit ()
{
deliver_shape (get_polygon ());
deliver_shape (get_polygon (false));
commit_recent (view ());
close_editor_hooks (true);
}
db::Polygon
PolygonService::get_polygon () const
PolygonService::get_polygon (bool editing) const
{
db::Polygon poly;
if (m_points.size () < 4) {
if (! editing && m_points.size () + (m_closure_set ? 1 : 0) < 4) {
throw tl::Exception (tl::to_string (tr ("A polygon must have at least 3 points")));
}
std::vector<db::Point> points_dbu;
points_dbu.reserve (m_points.size ());
points_dbu.reserve (m_points.size () + 1);
// one point is reserved for the current one
for (std::vector<db::DPoint>::const_iterator p = m_points.begin (); p + 1 != m_points.end (); ++p) {
points_dbu.push_back (trans () * *p);
}
if (editing) {
points_dbu.push_back (trans () * m_points.back ());
}
if (m_closure_set) {
points_dbu.push_back (trans () * m_closure);
}
poly.assign_hull (points_dbu.begin (), points_dbu.end (), true, true /*remove reflected*/);
poly.assign_hull (points_dbu.begin (), points_dbu.end (), !editing /*compress*/, !editing /*remove reflected*/);
if (poly.hull ().size () < 3) {
if (! editing && poly.hull ().size () < 3) {
throw tl::Exception (tl::to_string (tr ("A polygon must have at least 3 effective points")));
}
@ -539,7 +586,7 @@ PolygonService::get_polygon () const
void
PolygonService::do_cancel_edit ()
{
// .. nothing yet ..
close_editor_hooks (false);
}
bool
@ -732,6 +779,17 @@ PolygonService::update_marker ()
std::string (" l: ") +
tl::micron_to_string (m_points.back ().distance (m_points.end () [-2])));
}
// call hooks with new shape
if (! editor_hooks ().empty ()) {
call_editor_hooks (editor_hooks (), &edt::EditorHooks::begin_new_shapes);
try {
deliver_shape_to_hooks (get_polygon (true));
} catch (...) {
// ignore exceptions
}
call_editor_hooks (editor_hooks (), &edt::EditorHooks::end_new_shapes);
}
}
// -----------------------------------------------------------------------------
@ -761,6 +819,8 @@ BoxService::do_begin_edit (const db::DPoint &p)
db::DPoint pp = snap2 (p);
m_p1 = m_p2 = pp;
open_editor_hooks ();
set_edit_marker (new lay::Marker (view (), cv_index ()));
update_marker ();
}
@ -785,6 +845,17 @@ BoxService::update_marker ()
tl::micron_to_string (m_p2.y () - m_p1.y ()));
}
// call hooks with new shape
if (! editor_hooks ().empty ()) {
call_editor_hooks (editor_hooks (), &edt::EditorHooks::begin_new_shapes);
try {
deliver_shape_to_hooks (get_box ());
} catch (...) {
// ignore exceptions
}
call_editor_hooks (editor_hooks (), &edt::EditorHooks::end_new_shapes);
}
}
void
@ -816,12 +887,13 @@ BoxService::do_finish_edit ()
{
deliver_shape (get_box ());
commit_recent (view ());
close_editor_hooks (true);
}
void
BoxService::do_cancel_edit ()
{
// .. nothing yet ..
close_editor_hooks (false);
}
bool
@ -857,6 +929,8 @@ PointService::do_begin_edit (const db::DPoint &p)
db::DPoint pp = snap2 (p);
m_p = pp;
open_editor_hooks ();
set_edit_marker (new lay::Marker (view (), cv_index ()));
update_marker ();
}
@ -882,6 +956,17 @@ PointService::update_marker ()
tl::micron_to_string (m_p.y ()));
}
// call hooks with new shape
if (! editor_hooks ().empty ()) {
call_editor_hooks (editor_hooks (), &edt::EditorHooks::begin_new_shapes);
try {
deliver_shape_to_hooks (get_point ());
} catch (...) {
// ignore exceptions
}
call_editor_hooks (editor_hooks (), &edt::EditorHooks::end_new_shapes);
}
}
void
@ -913,12 +998,13 @@ PointService::do_finish_edit ()
{
deliver_shape (get_point ());
commit_recent (view ());
close_editor_hooks (true);
}
void
PointService::do_cancel_edit ()
{
// .. nothing yet ..
close_editor_hooks (false);
}
bool
@ -959,6 +1045,8 @@ TextService::do_begin_edit (const db::DPoint &p)
m_text.trans (db::DTrans (m_rot, snap2 (p) - db::DPoint ()));
open_editor_hooks ();
lay::DMarker *marker = new lay::DMarker (view ());
marker->set_vertex_shape (lay::ViewOp::Cross);
marker->set_vertex_size (9 /*cross vertex size*/);
@ -985,6 +1073,17 @@ TextService::update_marker ()
view ()->message (pos);
}
// call hooks with new shape
if (! editor_hooks ().empty ()) {
call_editor_hooks (editor_hooks (), &edt::EditorHooks::begin_new_shapes);
try {
deliver_shape_to_hooks (get_text ());
} catch (...) {
// ignore exceptions
}
call_editor_hooks (editor_hooks (), &edt::EditorHooks::end_new_shapes);
}
}
bool
@ -1065,12 +1164,14 @@ TextService::do_finish_edit ()
}
#endif
close_editor_hooks (true);
}
void
TextService::do_cancel_edit ()
{
// .. nothing yet ..
close_editor_hooks (false);
}
bool
@ -1166,6 +1267,8 @@ PathService::do_begin_edit (const db::DPoint &p)
m_points.push_back (pp);
m_points.push_back (pp);
open_editor_hooks ();
set_edit_marker (new lay::Marker (view (), cv_index ()));
update_marker ();
}
@ -1250,6 +1353,8 @@ PathService::do_finish_edit ()
deliver_shape (get_path ());
commit_recent (view ());
close_editor_hooks (true);
}
void
@ -1271,6 +1376,17 @@ PathService::update_marker ()
}
}
// call hooks with new shape
if (! editor_hooks ().empty ()) {
call_editor_hooks (editor_hooks (), &edt::EditorHooks::begin_new_shapes);
try {
deliver_shape_to_hooks (get_path ());
} catch (...) {
// ignore exceptions
}
call_editor_hooks (editor_hooks (), &edt::EditorHooks::end_new_shapes);
}
}
db::Path
@ -1305,7 +1421,7 @@ PathService::get_path () const
void
PathService::do_cancel_edit ()
{
// .. nothing yet ..
close_editor_hooks (false);
}
bool
@ -1582,6 +1698,8 @@ InstService::do_begin_edit (const db::DPoint &p)
m_trans = db::VCplxTrans (1.0 / cv->layout ().dbu ()) * tv [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ();
}
open_editor_hooks ();
update_marker ();
}
@ -1756,7 +1874,7 @@ InstService::do_finish_edit ()
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);
cv.cell ()->collect_caller_cells (callers);
callers.insert (cv.cell_index ());
std::vector <db::cell_index_type> intersection;
@ -1769,7 +1887,7 @@ InstService::do_finish_edit ()
manager ()->transaction (tl::to_string (tr ("Create instance")), m_reference_transaction_id);
}
m_reference_transaction_id = 0;
db::Instance i = cv->layout ().cell (cv.cell_index ()).insert (inst);
db::Instance i = cv.cell ()->insert (inst);
cv->layout ().cleanup ();
if (manager ()) {
manager ()->commit ();
@ -1792,10 +1910,12 @@ InstService::do_finish_edit ()
m_has_valid_cell = false;
m_in_drag_drop = false;
close_editor_hooks (true);
} catch (...) {
m_has_valid_cell = false;
m_in_drag_drop = false;
close_editor_hooks (false);
throw;
}
}
@ -1819,6 +1939,8 @@ InstService::do_cancel_edit ()
if (cv.is_valid ()) {
cv->layout ().cleanup ();
}
close_editor_hooks (false);
}
void
@ -2128,6 +2250,35 @@ InstService::update_marker ()
} else {
set_edit_marker (0);
}
// call hooks with new shape
if (! editor_hooks ().empty ()) {
call_editor_hooks (editor_hooks (), &edt::EditorHooks::begin_new_instances);
try {
const lay::CellView &cv = view ()->cellview (m_cv_index);
db::CellInstArray inst;
if (cv.is_valid () && get_inst (inst)) {
// Note: the instance collection is temporary
db::Instances instances (cv.cell ());
db::Instance i = instances.insert (inst);
db::CplxTrans view_trans = db::CplxTrans (cv->layout ().dbu ()) * m_trans;
call_editor_hooks<const db::Instance &, const db::CplxTrans &> (m_editor_hooks, &edt::EditorHooks::create_instance, i, view_trans);
}
} catch (...) {
// ignore exceptions
}
call_editor_hooks (editor_hooks (), &edt::EditorHooks::end_new_instances);
}
}
bool
@ -2159,6 +2310,36 @@ InstService::get_inst (db::CellInstArray &inst)
return false;
}
void
InstService::open_editor_hooks ()
{
const lay::CellView &cv = view ()->cellview (m_cv_index);
if (! cv.is_valid ()) {
return;
}
std::string technology;
if (cv->layout ().technology ()) {
technology = cv->layout ().technology ()->name ();
}
m_editor_hooks = edt::EditorHooks::get_editor_hooks (technology);
lay::CellViewRef cv_ref (view ()->cellview_ref (m_cv_index));
call_editor_hooks<lay::CellViewRef &> (m_editor_hooks, &edt::EditorHooks::begin_create_instances, cv_ref);
}
void
InstService::close_editor_hooks (bool with_commit)
{
if (with_commit) {
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::commit_instances);
}
call_editor_hooks (m_editor_hooks, &edt::EditorHooks::end_create_instances);
m_editor_hooks.clear ();
}
} // namespace edt

View File

@ -26,6 +26,7 @@
#include "edtService.h"
#include "edtConfig.h"
#include "edtEditorHooks.h"
#include <memory>
@ -68,6 +69,16 @@ protected:
void deliver_shape (const db::Path &path);
void deliver_shape (const db::Box &box);
void deliver_shape (const db::Point &point);
void open_editor_hooks ();
template <class Shape>
void deliver_shape_to_hooks (const Shape &shape);
void close_editor_hooks (bool with_commit);
const tl::weak_collection<edt::EditorHooks> &editor_hooks ()
{
return m_editor_hooks;
}
virtual void current_layer_changed () { }
private:
@ -77,6 +88,7 @@ private:
db::Cell *mp_cell;
db::Layout *mp_layout;
combine_mode_type m_combine_mode;
tl::weak_collection<edt::EditorHooks> m_editor_hooks;
void update_edit_layer (const lay::LayerPropertiesConstIterator &iter);
};
@ -109,7 +121,7 @@ private:
db::DPoint m_last;
void update_marker ();
db::Polygon get_polygon () const;
db::Polygon get_polygon (bool editing) const;
void add_closure ();
void set_last_point (const db::DPoint &p);
};
@ -298,6 +310,7 @@ private:
const db::PCellDeclaration *mp_pcell_decl;
int m_cv_index;
db::ICplxTrans m_trans;
tl::weak_collection<edt::EditorHooks> m_editor_hooks;
void update_marker ();
bool get_inst (db::CellInstArray &inst);
@ -305,6 +318,13 @@ private:
tl::Variant get_default_layer_for_pcell ();
void sync_to_config ();
void switch_cell_or_pcell (bool switch_parameters);
void open_editor_hooks ();
void close_editor_hooks (bool with_commit);
const tl::weak_collection<edt::EditorHooks> &editor_hooks ()
{
return m_editor_hooks;
}
};
}

View File

@ -0,0 +1,536 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 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 "gsiDecl.h"
#include "edtEditorHooks.h"
#include "layObjectInstPath.h"
#include "layLayoutView.h"
namespace gsi
{
class EditorHooksImpl
: public edt::EditorHooks
{
public:
EditorHooksImpl ()
: edt::EditorHooks ()
{
// .. nothing yet ..
}
virtual void begin_create_shapes (lay::CellViewRef &cv, const lay::LayerProperties &layer)
{
if (f_begin_create_shapes.can_issue ()) {
f_begin_create_shapes.issue<edt::EditorHooks, lay::CellViewRef &, const lay::LayerProperties &> (&edt::EditorHooks::begin_create_shapes, cv, layer);
} else {
edt::EditorHooks::begin_create_shapes (cv, layer);
}
}
virtual void begin_new_shapes ()
{
if (f_begin_new_shapes.can_issue ()) {
f_begin_new_shapes.issue<edt::EditorHooks> (&edt::EditorHooks::begin_new_shapes);
} else {
edt::EditorHooks::begin_new_shapes ();
}
}
virtual void create_shape (const db::Shape &shape, const db::CplxTrans &view_trans)
{
if (f_create_shape.can_issue ()) {
f_create_shape.issue<edt::EditorHooks, const db::Shape &, const db::CplxTrans &> (&edt::EditorHooks::create_shape, shape, view_trans);
} else {
edt::EditorHooks::create_shape (shape, view_trans);
}
}
virtual void end_new_shapes ()
{
if (f_end_new_shapes.can_issue ()) {
f_end_new_shapes.issue<edt::EditorHooks> (&edt::EditorHooks::end_new_shapes);
} else {
edt::EditorHooks::end_new_shapes ();
}
}
virtual void commit_shapes ()
{
if (f_commit_shapes.can_issue ()) {
f_commit_shapes.issue<edt::EditorHooks> (&edt::EditorHooks::commit_shapes);
} else {
edt::EditorHooks::commit_shapes ();
}
}
virtual void end_create_shapes ()
{
if (f_end_create_shapes.can_issue ()) {
f_end_create_shapes.issue<edt::EditorHooks> (&edt::EditorHooks::end_create_shapes);
} else {
edt::EditorHooks::end_create_shapes ();
}
}
virtual void begin_create_instances (lay::CellViewRef &cv)
{
if (f_begin_create_instances.can_issue ()) {
f_begin_create_instances.issue<edt::EditorHooks, lay::CellViewRef &> (&edt::EditorHooks::begin_create_instances, cv);
} else {
edt::EditorHooks::begin_create_instances (cv);
}
}
virtual void begin_new_instances ()
{
if (f_begin_new_instances.can_issue ()) {
f_begin_new_instances.issue<edt::EditorHooks> (&edt::EditorHooks::begin_new_instances);
} else {
edt::EditorHooks::begin_new_instances ();
}
}
virtual void create_instance (const db::Instance &object, const db::CplxTrans &view_trans)
{
if (f_create_instance.can_issue ()) {
f_create_instance.issue<edt::EditorHooks, const db::Instance &, const db::CplxTrans &> (&edt::EditorHooks::create_instance, object, view_trans);
} else {
edt::EditorHooks::create_instance (object, view_trans);
}
}
virtual void end_new_instances ()
{
if (f_end_new_instances.can_issue ()) {
f_end_new_instances.issue<edt::EditorHooks> (&edt::EditorHooks::end_new_instances);
} else {
edt::EditorHooks::end_new_instances ();
}
}
virtual void commit_instances ()
{
if (f_commit_instances.can_issue ()) {
f_commit_instances.issue<edt::EditorHooks> (&edt::EditorHooks::commit_instances);
} else {
edt::EditorHooks::commit_instances ();
}
}
virtual void end_create_instances ()
{
if (f_end_create_instances.can_issue ()) {
f_end_create_instances.issue<edt::EditorHooks> (&edt::EditorHooks::end_create_instances);
} else {
edt::EditorHooks::end_create_instances ();
}
}
virtual void begin_edit (lay::CellViewRef &cv_ref)
{
if (f_begin_edit.can_issue ()) {
f_begin_edit.issue<edt::EditorHooks, lay::CellViewRef &> (&edt::EditorHooks::begin_edit, cv_ref);
} else {
edt::EditorHooks::begin_edit (cv_ref);
}
}
virtual void begin_edits ()
{
if (f_begin_edits.can_issue ()) {
f_begin_edits.issue<edt::EditorHooks> (&edt::EditorHooks::begin_edits);
} else {
edt::EditorHooks::begin_edits ();
}
}
virtual void transformed (const lay::ObjectInstPath &object, const db::ICplxTrans &applied_trans, const db::CplxTrans &view_trans)
{
if (f_transformed.can_issue ()) {
f_transformed.issue<edt::EditorHooks, const lay::ObjectInstPath &, const db::ICplxTrans &, const db::CplxTrans &> (&edt::EditorHooks::transformed, object, applied_trans, view_trans);
} else {
edt::EditorHooks::transformed (object, applied_trans, view_trans);
}
}
virtual void modified (const lay::ObjectInstPath &object, const db::Shape &shape, const db::CplxTrans &view_trans)
{
if (f_modified.can_issue ()) {
f_modified.issue<edt::EditorHooks, const lay::ObjectInstPath &, const db::Shape &, const db::CplxTrans &> (&edt::EditorHooks::modified, object, shape, view_trans);
} else {
edt::EditorHooks::modified (object, shape, view_trans);
}
}
virtual void end_edits ()
{
if (f_end_edits.can_issue ()) {
f_end_edits.issue<edt::EditorHooks> (&edt::EditorHooks::end_edits);
} else {
edt::EditorHooks::end_edits ();
}
}
virtual void commit_edit ()
{
if (f_commit_edit.can_issue ()) {
f_commit_edit.issue<edt::EditorHooks> (&edt::EditorHooks::commit_edit);
} else {
edt::EditorHooks::commit_edit ();
}
}
virtual void end_edit ()
{
if (f_end_edit.can_issue ()) {
f_end_edit.issue<edt::EditorHooks> (&edt::EditorHooks::end_edit);
} else {
edt::EditorHooks::end_edit ();
}
}
gsi::Callback f_begin_create_shapes;
gsi::Callback f_begin_new_shapes;
gsi::Callback f_create_shape;
gsi::Callback f_end_new_shapes;
gsi::Callback f_commit_shapes;
gsi::Callback f_end_create_shapes;
gsi::Callback f_begin_create_instances;
gsi::Callback f_begin_new_instances;
gsi::Callback f_create_instance;
gsi::Callback f_end_new_instances;
gsi::Callback f_commit_instances;
gsi::Callback f_end_create_instances;
gsi::Callback f_begin_edit;
gsi::Callback f_begin_edits;
gsi::Callback f_transformed;
gsi::Callback f_modified;
gsi::Callback f_end_edits;
gsi::Callback f_commit_edit;
gsi::Callback f_end_edit;
};
static void register_editor_hooks (EditorHooksImpl *hooks, const std::string &name)
{
edt::EditorHooks::register_editor_hooks (hooks, name);
}
gsi::Class<EditorHooksImpl> decl_EditorHooks ("lay", "EditorHooks",
gsi::callback ("begin_create_shapes", &EditorHooksImpl::begin_create_shapes, &EditorHooksImpl::f_begin_create_shapes, gsi::arg ("cellview"), gsi::arg ("layer"),
"@brief Shape creation protocol - begin session\n"
"This method is called to initiate a shape creation session. The session is ended with "
"\\end_create_shapes. Between these calls, new objects are announced with \\begin_new_shapes, "
"\\create_shape and \\end_new_shapes calls. These calls are repeated to indicate changes in the objects "
"created.\n"
"\n"
"\\commit_shapes is called once before \\end_create_shapes to indicate that the last set of "
"objects is committed to the database."
) +
gsi::callback ("begin_new_shapes", &EditorHooksImpl::begin_new_shapes, &EditorHooksImpl::f_begin_new_shapes,
"@brief Shape creation protocol - begin new shapes\n"
"See \\begin_create_shapes for a description of the protocol."
) +
gsi::callback ("create_shape", &EditorHooksImpl::create_shape, &EditorHooksImpl::f_create_shape, gsi::arg ("shape"), gsi::arg ("view_trans"),
"@brief Shape creation protocol - indicate a new object\n"
"See \\begin_create_shapes for a description of the protocol."
) +
gsi::callback ("end_new_shapes", &EditorHooksImpl::end_new_shapes, &EditorHooksImpl::f_end_new_shapes,
"@brief Shape creation protocol - finish list of new shapes\n"
"See \\begin_create_shapes for a description of the protocol."
) +
gsi::callback ("commit_shapes", &EditorHooksImpl::commit_shapes, &EditorHooksImpl::f_commit_shapes,
"@brief Shape creation protocol - commit new objects\n"
"See \\begin_create_shapes for a description of the protocol."
) +
gsi::callback ("end_create_shapes", &EditorHooksImpl::end_create_shapes, &EditorHooksImpl::f_end_create_shapes,
"@brief Shape creation protocol - finish session\n"
"See \\begin_create for a description of the protocol."
) +
gsi::callback ("begin_create_instances", &EditorHooksImpl::begin_create_instances, &EditorHooksImpl::f_begin_create_instances, gsi::arg ("cellview"),
"@brief Instance creation protocol - begin session\n"
"This method is called to initiate an instance creation session. The session is ended with "
"\\end_create_instances. Between these calls, new objects are announced with \\begin_new_instances, "
"\\create_instance and \\end_new_instances calls. These calls are repeated to indicate changes in the objects "
"created.\n"
"\n"
"\\commit_instances is called once before \\end_create_instances to indicate that the last set of "
"objects is committed to the database."
) +
gsi::callback ("begin_new_instances", &EditorHooksImpl::begin_new_instances, &EditorHooksImpl::f_begin_new_instances,
"@brief Instance creation protocol - begin new instances\n"
"See \\begin_create_instances for a description of the protocol."
) +
gsi::callback ("create_instance", &EditorHooksImpl::create_instance, &EditorHooksImpl::f_create_instance, gsi::arg ("instance"), gsi::arg ("view_trans"),
"@brief Instance creation protocol - indicate a new object\n"
"See \\begin_create_instances for a description of the protocol."
) +
gsi::callback ("end_new_instances", &EditorHooksImpl::end_new_instances, &EditorHooksImpl::f_end_new_instances,
"@brief Instance creation protocol - finish list of new instances\n"
"See \\begin_create_instances for a description of the protocol."
) +
gsi::callback ("commit_instances", &EditorHooksImpl::commit_instances, &EditorHooksImpl::f_commit_instances,
"@brief Instance creation protocol - commit new objects\n"
"See \\begin_create_instances for a description of the protocol."
) +
gsi::callback ("end_create_instances", &EditorHooksImpl::end_create_instances, &EditorHooksImpl::f_end_create_instances,
"@brief Instance creation protocol - finish session\n"
"See \\begin_create for a description of the protocol."
) +
gsi::callback ("begin_edit", &EditorHooksImpl::begin_edit, &EditorHooksImpl::f_begin_edit, gsi::arg ("cellview"),
"@brief Editing protocol - begin session\n"
"This method is called to initiate an object editing session. The session is ended with "
"\\end_edit. Between these calls, edits are announced with \\begin_edits, "
"\\transformed or \\modified and \\end_edits calls. These calls are repeated to indicate changes in the objects "
"modified while moving the mouse for example.\n"
"\n"
"\\commit_edit is called once before \\end_edit to indicate that the last set of "
"objects are committed to the database."
) +
gsi::callback ("begin_edits", &EditorHooksImpl::begin_edits, &EditorHooksImpl::f_begin_edits,
"@brief Editing protocol - begin edits\n"
"See \\begin_edit for a description of the protocol."
) +
gsi::callback ("transformed", &EditorHooksImpl::transformed, &EditorHooksImpl::f_transformed, gsi::arg ("object"), gsi::arg ("applied_trans"), gsi::arg ("view_trans"),
"@brief Editing protocol - indicate an object transformation\n"
"See \\begin_edit for a description of the protocol.\n"
"\n"
"@param object A path to the modified object\n"
"@param applied_trans The DBU-space of the transformation applied to the object\n"
"@param view_trans The combined transformation of DBU space to view space\n"
"\n"
"Note that 'object' is the original, unmodified objects to which 'applied_trans' will be applied upon commit."
) +
gsi::callback ("modified", &EditorHooksImpl::modified, &EditorHooksImpl::f_modified, gsi::arg ("object"), gsi::arg ("shape"), gsi::arg ("view_trans"),
"@brief Modification protocol - indicate a modified object\n"
"See \\begin_edit for a description of the protocol."
"\n"
"@param object A path to the modified object\n"
"@param shape The new, modified shape\n"
"@param view_trans The combined transformation of DBU space to view space\n"
"\n"
"Note that 'object' is the original, unmodified objects while 'shape' is the modified shape. This shape object is a synthetic reference "
"and does not exist in the database yet.\n"
) +
gsi::callback ("end_edits", &EditorHooksImpl::end_edits, &EditorHooksImpl::f_end_edits,
"@brief Editing protocol - finish list of edits\n"
"See \\begin_edit for a description of the protocol."
) +
gsi::callback ("commit_edit", &EditorHooksImpl::commit_edit, &EditorHooksImpl::f_commit_edit,
"@brief Editing protocol - commit new objects\n"
"See \\begin_edit for a description of the protocol."
) +
gsi::callback ("end_edit", &EditorHooksImpl::end_edit, &EditorHooksImpl::f_end_edit,
"@brief Editing protocol - finish session\n"
"See \\begin_edit for a description of the protocol."
) +
gsi::method ("technology=", &EditorHooksImpl::set_technology, gsi::arg ("technology"),
"@brief sets the name of the technology the hooks are associated with\n"
"This will clear all technology associations and associate the hooks with that technology only.\n"
) +
gsi::method ("clear_technologies", &EditorHooksImpl::clear_technologies,
"@brief Clears the list of technologies the hooks are associated with.\n"
"See also \\add_technology.\n"
) +
gsi::method ("add_technology", &EditorHooksImpl::add_technology, gsi::arg ("tech"),
"@brief Additionally associates the hooks with the given technology.\n"
"See also \\clear_technologies.\n"
) +
gsi::method ("is_for_technology", &EditorHooksImpl::is_for_technology, gsi::arg ("tech"),
"@brief Returns a value indicating whether the hooks are associated with the given technology.\n"
) +
gsi::method ("for_technologies", &EditorHooksImpl::for_technologies,
"@brief Returns a value indicating whether the hooks are associated with any technology.\n"
"The method is equivalent to checking whether the \\technologies list is empty.\n"
) +
gsi::method ("technologies", &EditorHooksImpl::get_technologies,
"@brief Gets the list of technologies these hooks are associated with.\n"
) +
gsi::method ("name", &EditorHooksImpl::name,
"@brief Gets the name of the hooks object.\n"
"This is the name, the object was registered under in the system."
) +
gsi::method_ext ("register", &register_editor_hooks, gsi::arg ("name"),
"@brief Registers the hooks in the system.\n"
"The hooks will not be active before they are registered in the system. Registration will "
"also transfer object ownership to the system.\n"
"\n"
"The name is arbitary, but should be unique. Upon registration, this hooks object will "
"replace others with the same name already registered in the system. This will simplify "
"debugging as you can re-run the same code, without accumulating hooks.\n"
),
"@brief An implementation base class for editor hooks\n"
"\n"
"Editor hooks allow implementing technology-specific callbacks into the editor "
"for example to implement visual feedback about DRC rules.\n"
"\n"
"This class provides the basic interface. To implement callbacks, use the \\EditorHooks class. "
"You should not need to instantiate this class.\n"
"\n"
"The following is an excample for editor hooks that add DRC space indicators for polygon-alike shapes,\n"
"It implements the shape creation protocol to capture new shapes and the modification protocol to "
"capture shape manipulations. It displays a halo following hard-coded DRC rules to indicate the "
"forbidden zones around the shapes:\n"
"\n"
"@code\n"
"class MyEditorHooks < RBA::EditorHooks\n"
"\n"
" def initialize()\n"
" \n"
" register(\"editor_hooks_demo\")\n"
"\n"
" cleanup \n"
"\n"
" # some demo values \n"
" @spaces = {\n"
" RBA::LayerInfo::new(1, 0) => [ 0.2, RBA::Region::Euclidian ],\n"
" RBA::LayerInfo::new(2, 0) => [ 0.5, RBA::Region::Projection ]\n"
" }\n"
" \n"
" end\n"
"\n"
" # Utilities\n"
" \n"
" # pick the space value from layer or set to nil\n"
" def set_space_from_layer(layer_index)\n"
" lp = @layout.get_info(layer_index)\n"
" if @spaces[lp]\n"
" (s, m) = @spaces[lp]\n"
" @space = s / @layout.dbu\n"
" @metrics = m\n"
" else\n"
" @space = nil\n"
" end\n"
" end\n"
" \n"
" def add_marker_from_shape(shape, trans)\n"
" \n"
" if !@space\n"
" return # no space value set\n"
" end\n"
" \n"
" p = shape.polygon\n"
" if !p\n"
" return # not a polygon-like object\n"
" end\n"
" \n"
" r = RBA::Region::new\n"
" # maintain 2-point polygons for the first edge drawn\n"
" r.merged_semantics = (p.num_points != 2)\n"
" r.insert(p)\n"
" \n"
" # compute DRC hull and prepare markers\n"
" r.drc_hull(@metrics, @space).each do |pp|\n"
" m = RBA::Marker::new(@view)\n"
" m.line_style = 2\n"
" m.vertex_size = 0\n"
" m.set_polygon(trans * pp)\n"
" @markers.append(m)\n"
" end\n"
" \n"
" end\n"
" \n"
" # setup session\n"
" def start(cv)\n"
" cleanup\n"
" @view = cv.view\n"
" @layout = cv.layout\n"
" end\n"
" \n"
" # end session\n"
" def cleanup\n"
" @space = nil\n"
" @view = nil\n"
" @layout = nil\n"
" clear_markers\n"
" end\n"
" \n"
" def clear_markers\n"
" @markers && @markers.each do |m|\n"
" # this is how a marker gets removed in Ruby:\n"
" m._destroy\n"
" end\n"
" @markers = []\n"
" end\n"
" \n"
" # Shape creation protocol\n"
" \n"
" def begin_create_shapes(cv, layer)\n"
" start(cv)\n"
" set_space_from_layer(layer.layer_index)\n"
" end\n"
"\n"
" def begin_new_shapes\n"
" clear_markers\n"
" end\n"
"\n"
" def create_shape(shape, trans)\n"
" add_marker_from_shape(shape, trans)\n"
" end\n"
" \n"
" def end_create_shapes\n"
" cleanup\n"
" end\n"
" \n"
" # Modification protocol\n"
" \n"
" def begin_edit(cv)\n"
" start(cv)\n"
" end\n"
"\n"
" def begin_edits\n"
" # create new markers\n"
" clear_markers\n"
" end\n"
"\n"
" # transformation of a shape or instance\n"
" def transformed(path, applied, trans)\n"
" if path.shape\n"
" set_space_from_layer(path.layer)\n"
" add_marker_from_shape(path.shape, trans * applied)\n"
" end\n"
" end\n"
"\n"
" # modification of a shape\n"
" def modified(path, shape, trans)\n"
" set_space_from_layer(path.layer)\n"
" add_marker_from_shape(shape, trans)\n"
" end\n"
"\n"
" def end_edit\n"
" cleanup\n"
" end\n"
" \n"
"end\n"
"\n"
"# instantiation of the hooks object\n"
"MyEditorHooks::new\n"
"@/code\n"
"\n"
"The EditorHooks class has been introduced in version 0.29.1."
);
}

View File

@ -99,6 +99,7 @@ void run_pythontest (tl::TestBase *_this, const std::string &fn)
PYTHONTEST (kwargs, "kwargs.py")
PYTHONTEST (dbLayoutTest, "dbLayoutTest.py")
PYTHONTEST (dbRegionTest, "dbRegionTest.py")
PYTHONTEST (dbShapesTest, "dbShapesTest.py")
PYTHONTEST (dbReaders, "dbReaders.py")
PYTHONTEST (dbPCellsTest, "dbPCells.py")
PYTHONTEST (dbPolygonTest, "dbPolygonTest.py")

View File

@ -116,12 +116,10 @@ public:
/**
* @brief Post-decrement
*/
weak_or_shared_collection_iterator<T, Holder, Shared> operator-- (int n)
weak_or_shared_collection_iterator<T, Holder, Shared> operator-- (int)
{
weak_or_shared_collection_iterator<T, Holder, Shared> ret = *this;
while (n-- > 0) {
operator-- ();
}
operator-- ();
return ret;
}
@ -138,12 +136,10 @@ public:
/**
* @brief Post-increment
*/
weak_or_shared_collection_iterator<T, Holder, Shared> operator++ (int n)
weak_or_shared_collection_iterator<T, Holder, Shared> operator++ (int)
{
weak_or_shared_collection_iterator<T, Holder, Shared> ret = *this;
while (n-- > 0) {
operator++ ();
}
operator++ ();
return ret;
}
@ -176,38 +172,36 @@ public:
: public weak_or_shared_ptr<T, Shared>
{
public:
holder_type (weak_or_shared_collection<T, Shared> *collection)
: weak_or_shared_ptr<T, Shared> (), next (0), prev (0), mp_collection (collection)
holder_type (weak_or_shared_collection<T, Shared> *_collection)
: weak_or_shared_ptr<T, Shared> (), next (0), prev (0), collection (_collection)
{
// .. nothing yet ..
}
holder_type (weak_or_shared_collection<T, Shared> *collection, T *t)
: weak_or_shared_ptr<T, Shared> (t), next (0), prev (0), mp_collection (collection)
holder_type (weak_or_shared_collection<T, Shared> *_collection, T *t)
: weak_or_shared_ptr<T, Shared> (t), next (0), prev (0), collection (_collection)
{
// .. nothing yet ..
}
holder_type (weak_or_shared_collection<T, Shared> *collection, const weak_or_shared_ptr<T, Shared> &d)
: weak_or_shared_ptr<T, Shared> (d), next (0), prev (0), mp_collection (collection)
holder_type (weak_or_shared_collection<T, Shared> *_collection, const weak_or_shared_ptr<T, Shared> &d)
: weak_or_shared_ptr<T, Shared> (d), next (0), prev (0), collection (_collection)
{
// .. nothing yet ..
}
holder_type *next, *prev;
weak_or_shared_collection<T, Shared> *collection;
protected:
virtual void reset_object ()
{
weak_or_shared_ptr<T, Shared>::reset_object ();
if (mp_collection) {
if (collection) {
// Caution: this will probably delete "this"!
mp_collection->remove_element (this);
collection->remove_element (this);
}
}
private:
weak_or_shared_collection<T, Shared> *mp_collection;
};
typedef weak_or_shared_collection_iterator<T, holder_type, Shared> iterator;
@ -224,6 +218,24 @@ public:
{
}
/**
* @brief The copy constructor
*/
weak_or_shared_collection (const weak_or_shared_collection<T, Shared> &other)
: mp_first (0), mp_last (0), m_size (0)
{
operator= (other);
}
/**
* @brief The move constructor
*/
weak_or_shared_collection (weak_or_shared_collection<T, Shared> &&other)
: mp_first (0), mp_last (0), m_size (0)
{
swap (other);
}
/**
* @brief Destructor
*/
@ -234,6 +246,37 @@ public:
}
}
/**
* @brief Assignment
*/
weak_or_shared_collection &operator= (const weak_or_shared_collection<T, Shared> &other)
{
if (this != &other) {
clear ();
for (auto i = other.begin (); i != other.end (); ++i) {
push_back (const_cast<T *> (i.operator-> ()));
}
}
return *this;
}
/**
* @brief Swap
*/
void swap (weak_or_shared_collection<T, Shared> &other)
{
std::swap (mp_first, other.mp_first);
std::swap (mp_last, other.mp_last);
std::swap (m_size, other.m_size);
for (holder_type *h = mp_first; h; h = h->next) {
h->collection = this;
}
for (holder_type *h = other.mp_first; h; h = h->next) {
h->collection = &other;
}
}
/**
* @brief Returns a value indicating whether the collection is empty
*/

View File

@ -399,6 +399,9 @@ TEST(22)
EXPECT_EQ (sc.empty (), false);
EXPECT_EQ (sc.front ()->attr (), 1);
EXPECT_EQ ((++sc.begin ())->attr (), 1);
auto i = sc.begin ();
i++;
EXPECT_EQ (i->attr (), 1);
sc.pop_back ();
EXPECT_EQ (int (sc.size ()), 1);
@ -418,6 +421,9 @@ TEST(22)
EXPECT_EQ (sc.empty (), false);
EXPECT_EQ (sc.front ()->attr (), 2);
EXPECT_EQ ((++sc.begin ())->attr (), 2);
i = sc.begin ();
i++;
EXPECT_EQ (i->attr (), 2);
}
EXPECT_EQ (MyClass::instances (), 0);
@ -516,6 +522,48 @@ TEST(24)
EXPECT_EQ (MyClass::instances (), 0);
}
TEST(25)
{
MyClass::reset_instance_counter ();
MyClass *o1 = new MyClass (1);
MyClass *o2 = new MyClass (2);
EXPECT_EQ (MyClass::instances (), 2);
tl::shared_collection<MyClass> sc1, sc2;
sc1.push_back (o1);
sc1.push_back (o2);
EXPECT_EQ (sc1.size (), size_t (2));
EXPECT_EQ (sc2.size (), size_t (0));
EXPECT_EQ (sc1.front () == o1, true);
sc1.swap (sc2);
EXPECT_EQ (sc1.size (), size_t (0));
EXPECT_EQ (sc2.size (), size_t (2));
EXPECT_EQ (sc2.front () == o1, true);
EXPECT_EQ (MyClass::instances (), 2);
sc1 = sc2;
EXPECT_EQ (sc1.size (), size_t (2));
EXPECT_EQ (sc2.size (), size_t (2));
EXPECT_EQ (sc1.front () == o1, true);
EXPECT_EQ (sc2.front () == o1, true);
EXPECT_EQ (MyClass::instances (), 2);
delete o1;
EXPECT_EQ (sc1.size (), size_t (1));
EXPECT_EQ (sc2.size (), size_t (1));
EXPECT_EQ (sc1.front () == o2, true);
EXPECT_EQ (sc2.front () == o2, true);
EXPECT_EQ (MyClass::instances (), 1);
sc1.clear ();
sc2.clear ();
EXPECT_EQ (sc1.size (), size_t (0));
EXPECT_EQ (sc2.size (), size_t (0));
EXPECT_EQ (MyClass::instances (), 0);
}
TEST(30)
{
MyClass::reset_instance_counter ();

59
testdata/python/dbShapesTest.py vendored Normal file
View File

@ -0,0 +1,59 @@
# KLayout Layout Viewer
# Copyright (C) 2006-2024 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
import pya
import unittest
import sys
import os
class DBShapesTest(unittest.TestCase):
# Shape objects as hashes
def test_12(self):
s = pya.Shapes()
s1 = s.insert(pya.Box(1, 2, 3, 4))
s2 = s.insert(pya.Polygon(pya.Box(1, 2, 3, 4)))
s3 = s.insert(pya.SimplePolygon(pya.Box(1, 2, 3, 4)))
self.assertEqual(s1.hash != s2.hash, True) # let's hope so ...
self.assertEqual(s1.hash != s3.hash, True)
self.assertEqual(s2.hash != s3.hash, True)
self.assertEqual(s1 < s2 or s2 < s1, True)
self.assertEqual(s1 < s3 or s3 < s1, True)
self.assertEqual(s2 < s3 or s3 < s2, True)
h = {}
h[s1] = 1
h[s2] = 2
h[s3] = 3
self.assertEqual(len(h), 3)
self.assertEqual(h[s1], 1)
self.assertEqual(h[s2], 2)
self.assertEqual(h[s3], 3)
# run unit tests
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(DBShapesTest)
if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
sys.exit(1)

View File

@ -1452,6 +1452,25 @@ class DBRegion_TestClass < TestBase
end
# DRC hull
def test_drc_hull
r = RBA::Region::new()
r.insert(RBA::Polygon::new([[0, 0], [1000, 1000], [1500, 0]]))
assert_equal(r.drc_hull(RBA::Region::Euclidian, 200, 8).merged.to_s, "(-83,-200;-200,-83;-200,83;-141,141;859,1141;950,1211;1114,1184;1179,1089;1679,89;1714,-35;1627,-176;1500,-200)")
assert_equal(r.drc_hull(RBA::Region::Square, 200).merged.to_s, "(-200,-200;-200,-82;-283,0;1000,1283;1039,1243;1089,1268;1768,-89;1700,-123;1700,-200)")
assert_equal(r.drc_hull(RBA::Region::Projection, 200).merged.to_s, "(0,-200;0,0;-141,141;859,1141;1000,1000;1179,1089;1679,89;1500,0;1500,-200)")
r = RBA::Region::new()
r.merged_semantics = false
r.insert(RBA::Polygon::new([[0, 0], [1000, 1000]], true))
assert_equal(r.drc_hull(RBA::Region::Euclidian, 200, 8).merged.to_s, "(-83,-200;-200,-83;-200,83;-141,141;859,1141;917,1200;1083,1200;1200,1083;1200,917;1141,859;141,-141;83,-200)")
assert_equal(r.drc_hull(RBA::Region::Square, 200, 8).merged.to_s, "(0,-283;-141,-141;-283,0;1000,1283;1141,1141;1283,1000)")
assert_equal(r.drc_hull(RBA::Region::Projection, 200, 8).merged.to_s, "(141,-141;-141,141;859,1141;1141,859)")
end
end
load("test_epilogue.rb")

View File

@ -1627,6 +1627,35 @@ class DBShapes_TestClass < TestBase
end
# Shape objects as hashes
def test_12
s = RBA::Shapes::new
s1 = s.insert(RBA::Box::new(1, 2, 3, 4))
s2 = s.insert(RBA::Polygon::new(RBA::Box::new(1, 2, 3, 4)))
s3 = s.insert(RBA::SimplePolygon::new(RBA::Box::new(1, 2, 3, 4)))
assert_equal(s1.hash != s2.hash, true) # let's hope so ...
assert_equal(s1.hash != s3.hash, true)
assert_equal(s2.hash != s3.hash, true)
assert_equal(s1 < s2 || s2 < s1, true)
assert_equal(s1 < s3 || s3 < s1, true)
assert_equal(s2 < s3 || s3 < s2, true)
h = {}
h[s1] = 1
h[s2] = 2
h[s3] = 3
assert_equal(h.size, 3)
assert_equal(h[s1], 1)
assert_equal(h[s2], 2)
assert_equal(h[s3], 3)
end
end
load("test_epilogue.rb")