diff --git a/Changelog b/Changelog index 323f705d4..57b377c30 100644 --- a/Changelog +++ b/Changelog @@ -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 diff --git a/src/db/db/dbRegionProcessors.cc b/src/db/db/dbRegionProcessors.cc index 63fcdea3b..40f515b1e 100644 --- a/src/db/db/dbRegionProcessors.cc +++ b/src/db/db/dbRegionProcessors.cc @@ -346,4 +346,156 @@ TriangulationProcessor::process (const db::Polygon &poly, std::vector &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 &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 &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 &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 &result) const +{ + db::EdgeProcessor ep; + std::vector 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); +} + } diff --git a/src/db/db/dbRegionProcessors.h b/src/db/db/dbRegionProcessors.h index 08553ca25..de017aa7c 100644 --- a/src/db/db/dbRegionProcessors.h +++ b/src/db/db/dbRegionProcessors.h @@ -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 &result) const; + +private: + db::Coord m_d; + db::metrics_type m_metrics; + size_t m_n_circle; +}; + + } #endif diff --git a/src/db/db/dbShape.cc b/src/db/db/dbShape.cc index e5c1d7820..648221066 100644 --- a/src/db/db/dbShape.cc +++ b/src/db/db/dbShape.cc @@ -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::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 { diff --git a/src/db/db/dbShape.h b/src/db/db/dbShape.h index dd003b47e..469351376 100644 --- a/src/db/db/dbShape.h +++ b/src/db/db/dbShape.h @@ -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 + template <> + struct hash + { + size_t operator() (const db::Shape &s) const + { + return s.hash_value (); + } + }; + +} // namespace std #endif diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index c5d6c6570..013c43154 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -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 (e)); @@ -2782,6 +2787,17 @@ Class 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" diff --git a/src/db/db/gsiDeclDbShape.cc b/src/db/db/gsiDeclDbShape.cc index 68f6ff4fb..708a52f5e 100644 --- a/src/db/db/gsiDeclDbShape.cc +++ b/src/db/db/gsiDeclDbShape.cc @@ -2131,6 +2131,21 @@ Class 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" diff --git a/src/edt/edt/edt.pro b/src/edt/edt/edt.pro index 477e14ff4..fe31cbc66 100644 --- a/src/edt/edt/edt.pro +++ b/src/edt/edt/edt.pro @@ -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: diff --git a/src/edt/edt/edtEditorHooks.cc b/src/edt/edt/edtEditorHooks.cc new file mode 100644 index 000000000..49b866769 --- /dev/null +++ b/src/edt/edt/edtEditorHooks.cc @@ -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 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 + get_editor_hooks (const std::string &for_technology) + { + tl::weak_collection 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 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::get_editor_hooks (const std::string &for_technology) +{ + if (EditorHooksManager::instance ()) { + return EditorHooksManager::instance ()->get_editor_hooks (for_technology); + } else { + return tl::weak_collection (); + } +} + +} diff --git a/src/edt/edt/edtEditorHooks.h b/src/edt/edt/edtEditorHooks.h new file mode 100644 index 000000000..6762219ac --- /dev/null +++ b/src/edt/edt/edtEditorHooks.h @@ -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 +#include + +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 &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 get_editor_hooks (const std::string &for_technology); + +private: + std::set 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 &hooks, void (EditorHooks::*meth) ()) +{ + for (auto h = hooks.begin (); h != hooks.end (); ++h) { + try { + if (h.operator-> ()) { + (const_cast (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 +inline +void call_editor_hooks (const tl::weak_collection &hooks, void (EditorHooks::*meth) (A1), A1 a1) +{ + for (auto h = hooks.begin (); h != hooks.end (); ++h) { + try { + if (h.operator-> ()) { + (const_cast (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 +inline +void call_editor_hooks (const tl::weak_collection &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 (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 +inline +void call_editor_hooks (const tl::weak_collection &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 (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 + diff --git a/src/edt/edt/edtMoveTrackerService.cc b/src/edt/edt/edtMoveTrackerService.cc new file mode 100644 index 000000000..14a42e4ed --- /dev/null +++ b/src/edt/edt/edtMoveTrackerService.cc @@ -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 services = view ()->get_plugins (); + std::vector 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 *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 (m_editor_hooks, &edt::EditorHooks::transformed, *r, applied, gt); + + } + + } + + // make the Partial Edit Service issue "modify" events + + std::vector partial_services = view ()->get_plugins (); + + 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 (m_editor_hooks, &edt::EditorHooks::begin_edit, (lay::CellViewRef &) cv_ref); +} + +} // namespace edt + + diff --git a/src/edt/edt/edtMoveTrackerService.h b/src/edt/edt/edtMoveTrackerService.h new file mode 100644 index 000000000..2981bf48f --- /dev/null +++ b/src/edt/edt/edtMoveTrackerService.h @@ -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 m_editor_hooks; + + void move_cancel (); + void open_editor_hooks (); + void issue_edit_events (); +}; + +} + +#endif + diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index c614d7334..b1861d969 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -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 (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 &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 > 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 *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 ())).first->second.push_back (r); + } + } + } + + db::Shapes tmp_shapes; + + for (std::map >::iterator sps = sel_per_shape.begin (); sps != sel_per_shape.end (); ++sps) { + + db::Shape shape = tmp_shapes.insert (sps->first); + for (std::vector::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) { + + std::map new_edges; + std::map new_points; + + shape = modify_shape (tv, shape, (*rr)->first, (*rr)->second, move_trans, new_edges, new_points); + + } + + for (std::vector::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 *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 (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::vector > 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 ())).first->second.push_back (r); + } + } + + for (std::map , std::vector >::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 *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 (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 &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &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 *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 ::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 ::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 *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 new_edges; std::map 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 ::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 ::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::const_iterator l = needs_cleanup.begin (); l != needs_cleanup.end (); ++l) { (*l)->cleanup (); diff --git a/src/edt/edt/edtPartialService.h b/src/edt/edt/edtPartialService.h index 645267a30..d39fe7332 100644 --- a/src/edt/edt/edtPartialService.h +++ b/src/edt/edt/edtPartialService.h @@ -34,6 +34,7 @@ #include "tlDeferredExecution.h" #include "edtUtils.h" #include "edtConfig.h" +#include "edtEditorHooks.h" #if defined(HAVE_QT) # include @@ -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 &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 m_editor_hooks; + // Deferred method to update the selection tl::DeferredMethod 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 &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points); + + void open_editor_hooks (); + void close_editor_hooks (bool commit); }; } diff --git a/src/edt/edt/edtPlugin.cc b/src/edt/edt/edtPlugin.cc index c0a12ee26..fe0e239c9 100644 --- a/src/edt/edt/edtPlugin.cc +++ b/src/edt/edt/edtPlugin.cc @@ -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 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 config_decl40 ( + new MoveTrackerPluginDeclaration (), + 4100, + "edt::MoveTrackerService" +); + } diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index 1f8f87999..0890d9bc7 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -381,6 +381,22 @@ public: */ std::pair 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 diff --git a/src/edt/edt/edtServiceImpl.cc b/src/edt/edt/edtServiceImpl.cc index 2fec150e4..dc3d2228c 100644 --- a/src/edt/edt/edtServiceImpl.cc +++ b/src/edt/edt/edtServiceImpl.cc @@ -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 (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 +void +ShapeEditService::deliver_shape_to_hooks (const Shape &shape) +{ + db::Shapes tmp (true); + db::Shape s = tmp.insert (shape); + call_editor_hooks (m_editor_hooks, &edt::EditorHooks::create_shape, s, trans ().inverted ()); +} + +// explicit instantiations +template void ShapeEditService::deliver_shape_to_hooks (const db::Polygon &); +template void ShapeEditService::deliver_shape_to_hooks (const db::Path &); +template void ShapeEditService::deliver_shape_to_hooks (const db::Box &); +template void ShapeEditService::deliver_shape_to_hooks (const db::Point &); +template void ShapeEditService::deliver_shape_to_hooks (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 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::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 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 (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 (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 diff --git a/src/edt/edt/edtServiceImpl.h b/src/edt/edt/edtServiceImpl.h index d799aa8ee..e2c31ae5d 100644 --- a/src/edt/edt/edtServiceImpl.h +++ b/src/edt/edt/edtServiceImpl.h @@ -26,6 +26,7 @@ #include "edtService.h" #include "edtConfig.h" +#include "edtEditorHooks.h" #include @@ -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 + void deliver_shape_to_hooks (const Shape &shape); + void close_editor_hooks (bool with_commit); + + const tl::weak_collection &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 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 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 &editor_hooks () + { + return m_editor_hooks; + } }; } diff --git a/src/edt/edt/gsiDeclEdtEditorHooks.cc b/src/edt/edt/gsiDeclEdtEditorHooks.cc new file mode 100644 index 000000000..009d19f7c --- /dev/null +++ b/src/edt/edt/gsiDeclEdtEditorHooks.cc @@ -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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::end_edits); + } else { + edt::EditorHooks::end_edits (); + } + } + + virtual void commit_edit () + { + if (f_commit_edit.can_issue ()) { + f_commit_edit.issue (&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::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 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", ®ister_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." +); + +} + diff --git a/src/pya/unit_tests/pyaTests.cc b/src/pya/unit_tests/pyaTests.cc index d14c393cd..7fb7d0366 100644 --- a/src/pya/unit_tests/pyaTests.cc +++ b/src/pya/unit_tests/pyaTests.cc @@ -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") diff --git a/src/tl/tl/tlObjectCollection.h b/src/tl/tl/tlObjectCollection.h index 878e09dab..362868125 100644 --- a/src/tl/tl/tlObjectCollection.h +++ b/src/tl/tl/tlObjectCollection.h @@ -116,12 +116,10 @@ public: /** * @brief Post-decrement */ - weak_or_shared_collection_iterator operator-- (int n) + weak_or_shared_collection_iterator operator-- (int) { weak_or_shared_collection_iterator ret = *this; - while (n-- > 0) { - operator-- (); - } + operator-- (); return ret; } @@ -138,12 +136,10 @@ public: /** * @brief Post-increment */ - weak_or_shared_collection_iterator operator++ (int n) + weak_or_shared_collection_iterator operator++ (int) { weak_or_shared_collection_iterator ret = *this; - while (n-- > 0) { - operator++ (); - } + operator++ (); return ret; } @@ -176,38 +172,36 @@ public: : public weak_or_shared_ptr { public: - holder_type (weak_or_shared_collection *collection) - : weak_or_shared_ptr (), next (0), prev (0), mp_collection (collection) + holder_type (weak_or_shared_collection *_collection) + : weak_or_shared_ptr (), next (0), prev (0), collection (_collection) { // .. nothing yet .. } - holder_type (weak_or_shared_collection *collection, T *t) - : weak_or_shared_ptr (t), next (0), prev (0), mp_collection (collection) + holder_type (weak_or_shared_collection *_collection, T *t) + : weak_or_shared_ptr (t), next (0), prev (0), collection (_collection) { // .. nothing yet .. } - holder_type (weak_or_shared_collection *collection, const weak_or_shared_ptr &d) - : weak_or_shared_ptr (d), next (0), prev (0), mp_collection (collection) + holder_type (weak_or_shared_collection *_collection, const weak_or_shared_ptr &d) + : weak_or_shared_ptr (d), next (0), prev (0), collection (_collection) { // .. nothing yet .. } holder_type *next, *prev; + weak_or_shared_collection *collection; protected: virtual void reset_object () { weak_or_shared_ptr::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 *mp_collection; }; typedef weak_or_shared_collection_iterator iterator; @@ -224,6 +218,24 @@ public: { } + /** + * @brief The copy constructor + */ + weak_or_shared_collection (const weak_or_shared_collection &other) + : mp_first (0), mp_last (0), m_size (0) + { + operator= (other); + } + + /** + * @brief The move constructor + */ + weak_or_shared_collection (weak_or_shared_collection &&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 &other) + { + if (this != &other) { + clear (); + for (auto i = other.begin (); i != other.end (); ++i) { + push_back (const_cast (i.operator-> ())); + } + } + return *this; + } + + /** + * @brief Swap + */ + void swap (weak_or_shared_collection &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 */ diff --git a/src/tl/unit_tests/tlObjectTests.cc b/src/tl/unit_tests/tlObjectTests.cc index d4b5653d3..ae8493723 100644 --- a/src/tl/unit_tests/tlObjectTests.cc +++ b/src/tl/unit_tests/tlObjectTests.cc @@ -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 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 (); diff --git a/testdata/python/dbShapesTest.py b/testdata/python/dbShapesTest.py new file mode 100644 index 000000000..f5a2482e8 --- /dev/null +++ b/testdata/python/dbShapesTest.py @@ -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) + diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 8c59d587c..cd4546c7c 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -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") diff --git a/testdata/ruby/dbShapesTest.rb b/testdata/ruby/dbShapesTest.rb index bca7672cf..61b8c9243 100644 --- a/testdata/ruby/dbShapesTest.rb +++ b/testdata/ruby/dbShapesTest.rb @@ -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")