Refactoring of edt services, bug fixes

This commit is contained in:
Matthias Koefferlein 2025-08-23 00:41:37 +02:00
parent ee0b5e3bd9
commit ee89bc43c4
19 changed files with 3632 additions and 3205 deletions

View File

@ -35,26 +35,40 @@ DEFINES += MAKE_EDT_LIBRARY
# Disabled without Qt:
HEADERS = \
edtBoxService.h \
edtDialogs.h \
edtEditorHooks.h \
edtEditorOptionsPages.h \
edtInstPropertiesPage.h \
edtInstService.h \
edtMoveTrackerService.h \
edtPCellParametersPage.h \
edtPathService.h \
edtPointService.h \
edtPolygonService.h \
edtPropertiesPages.h \
edtPropertiesPageUtils.h \
edtRecentConfigurationPage.h
edtRecentConfigurationPage.h \
edtShapeService.h \
edtTextService.h
SOURCES = \
edtBoxService.cc \
edtDialogs.cc \
edtEditorHooks.cc \
edtEditorOptionsPages.cc \
edtInstPropertiesPage.cc \
edtInstService.cc \
edtMoveTrackerService.cc \
edtPCellParametersPage.cc \
edtPathService.cc \
edtPointService.cc \
edtPolygonService.cc \
edtPropertiesPages.cc \
edtPropertiesPageUtils.cc \
edtRecentConfigurationPage.cc \
edtShapeService.cc \
edtTextService.cc \
gsiDeclEdtEditorHooks.cc
# Enabled without Qt:
@ -66,7 +80,6 @@ HEADERS += \
edtPartialService.h \
edtPlugin.h \
edtService.h \
edtServiceImpl.h \
edtUtils.h \
edtCommon.h \
edtDistribute.h \
@ -78,7 +91,6 @@ SOURCES += \
edtPartialService.cc \
edtPlugin.cc \
edtService.cc \
edtServiceImpl.cc \
edtUtils.cc \
gsiDeclEdt.cc \
edtDistribute.cc \

View File

@ -0,0 +1,145 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtBoxService.h"
#include "layLayoutViewBase.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// BoxService implementation
BoxService::BoxService (db::Manager *manager, lay::LayoutViewBase *view)
: ShapeEditService (manager, view, db::ShapeIterator::Boxes)
{
// .. nothing yet ..
}
#if defined(HAVE_QT)
std::vector<lay::PropertiesPage *>
BoxService::properties_pages (db::Manager *manager, QWidget *parent)
{
std::vector<lay::PropertiesPage *> pages;
pages.push_back (new edt::BoxPropertiesPage (this, manager, parent));
return pages;
}
#endif
void
BoxService::do_begin_edit (const db::DPoint &p)
{
get_edit_layer ();
db::DPoint pp = snap2 (p);
m_p1 = m_p2 = pp;
open_editor_hooks ();
set_edit_marker (new lay::Marker (view (), cv_index ()));
update_marker ();
}
db::Box
BoxService::get_box () const
{
return db::Box (trans () * m_p1, trans () * m_p2);
}
void
BoxService::update_marker ()
{
lay::Marker *marker = dynamic_cast<lay::Marker *> (edit_marker ());
if (marker) {
marker->set (get_box (), db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
view ()->message (std::string ("lx: ") +
tl::micron_to_string (m_p2.x () - m_p1.x ()) +
std::string (" ly: ") +
tl::micron_to_string (m_p2.y () - m_p1.y ()));
}
// 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
BoxService::do_mouse_move_inactive (const db::DPoint &p)
{
lay::PointSnapToObjectResult snap_details = snap2_details (p);
mouse_cursor_from_snap_details (snap_details);
}
void
BoxService::do_mouse_move (const db::DPoint &p)
{
do_mouse_move_inactive (p);
set_cursor (lay::Cursor::cross);
m_p2 = snap2 (p);
update_marker ();
}
bool
BoxService::do_mouse_click (const db::DPoint &p)
{
do_mouse_move (p);
return true;
}
void
BoxService::do_finish_edit ()
{
deliver_shape (get_box ());
commit_recent ();
close_editor_hooks (true);
}
void
BoxService::do_cancel_edit ()
{
close_editor_hooks (false);
}
bool
BoxService::selection_applies (const lay::ObjectInstPath &sel) const
{
return !sel.is_cell_inst () && sel.shape ().is_box ();
}
} // namespace edt

View File

@ -0,0 +1,62 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtBoxService
#define HDR_edtBoxService
#include "edtShapeService.h"
namespace edt
{
/**
* @brief Implementation of edt::Service for box editing
*/
class BoxService
: public ShapeEditService
{
public:
BoxService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
private:
db::DPoint m_p1, m_p2;
void update_marker ();
db::Box get_box () const;
};
}
#endif

View File

@ -0,0 +1,900 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtInstService.h"
#include "layLayoutViewBase.h"
#include "layDragDropData.h"
#include "layBusy.h"
#include "dbLibraryManager.h"
#if defined(HAVE_QT)
# include "edtInstPropertiesPage.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// InstService implementation
InstService::InstService (db::Manager *manager, lay::LayoutViewBase *view)
: edt::Service (manager, view),
m_angle (0.0), m_scale (1.0),
m_mirror (false), m_is_pcell (false),
m_array (false), m_rows (1), m_columns (1),
m_row_x (0.0), m_row_y (0.0), m_column_x (0.0), m_column_y (0.0),
m_place_origin (false), m_reference_transaction_id (0),
m_needs_update (true), m_parameters_changed (false), m_has_valid_cell (false), m_in_drag_drop (false),
m_current_cell (0), mp_current_layout (0), mp_pcell_decl (0), m_cv_index (-1)
{
// .. nothing yet ..
}
#if defined(HAVE_QT)
std::vector<lay::PropertiesPage *>
InstService::properties_pages (db::Manager *manager, QWidget *parent)
{
std::vector<lay::PropertiesPage *> pages;
pages.push_back (new edt::InstPropertiesPage (this, manager, parent));
return pages;
}
#endif
bool
InstService::do_activated ()
{
m_cv_index = view ()->active_cellview_index ();
m_has_valid_cell = false;
return true; // start editing immediately
}
tl::Variant
InstService::get_default_layer_for_pcell ()
{
lay::LayerPropertiesConstIterator cl = view ()->current_layer ();
if (! cl.is_null () && ! cl->has_children () && (cl->source (true).cv_index() < 0 || cl->source (true).cv_index () == view ()->active_cellview_index ())) {
db::LayerProperties lp = cl->source (true).layer_props ();
if (! lp.is_null ()) {
return tl::Variant (lp);
}
}
return tl::Variant ();
}
#if defined(HAVE_QT)
bool
InstService::drag_enter_event (const db::DPoint &p, const lay::DragDropDataBase *data)
{
const lay::CellDragDropData *cd = dynamic_cast <const lay::CellDragDropData *> (data);
if (view ()->is_editable () && cd && (cd->layout () == & view ()->active_cellview ()->layout () || cd->library ())) {
view ()->cancel ();
set_edit_marker (0);
bool switch_parameters = true;
// configure from the drag/drop data
if (cd->library ()) {
// Reject drag & drop if the target technology does not match
if (cd->library ()->for_technologies () && view ()->cellview (view ()->active_cellview_index ()).is_valid ()) {
if (! cd->library ()->is_for_technology (view ()->cellview (view ()->active_cellview_index ())->tech_name ())) {
return false;
}
}
if (m_lib_name != cd->library ()->get_name ()) {
m_lib_name = cd->library ()->get_name ();
}
} else {
m_lib_name.clear ();
}
if (cd->is_pcell ()) {
const db::PCellDeclaration *pcell_decl = cd->layout ()->pcell_declaration (cd->cell_index ());
if (! pcell_decl) {
return false;
}
if (m_cell_or_pcell_name != pcell_decl->name ()) {
m_cell_or_pcell_name = pcell_decl->name ();
}
if (! cd->pcell_params ().empty ()) {
m_pcell_parameters = pcell_decl->named_parameters (cd->pcell_params ());
switch_parameters = false;
}
} else if (cd->layout ()->is_valid_cell_index (cd->cell_index ())) {
m_cell_or_pcell_name = cd->layout ()->cell_name (cd->cell_index ());
} else {
return false;
}
switch_cell_or_pcell (switch_parameters);
sync_to_config ();
m_in_drag_drop = true;
view ()->switch_mode (plugin_declaration ()->id ());
do_begin_edit (p);
// action taken.
return true;
}
return false;
}
bool
InstService::drag_move_event (const db::DPoint &p, const lay::DragDropDataBase * /*data*/)
{
if (m_in_drag_drop) {
do_mouse_move (p);
return true;
} else {
return false;
}
}
void
InstService::drag_leave_event ()
{
if (m_in_drag_drop) {
set_edit_marker (0);
do_cancel_edit ();
}
}
bool
InstService::drop_event (const db::DPoint & /*p*/, const lay::DragDropDataBase * /*data*/)
{
m_in_drag_drop = false;
return false;
}
#endif
bool
InstService::selection_applies (const lay::ObjectInstPath &sel) const
{
return sel.is_cell_inst ();
}
void
InstService::sync_to_config ()
{
// push the current setup to configuration so the instance dialog will take these as default
// and "apply" of these instance properties doesn't fail because of insistency.
dispatcher ()->config_set (cfg_edit_inst_lib_name, m_lib_name);
dispatcher ()->config_set (cfg_edit_inst_cell_name, m_cell_or_pcell_name);
if (m_is_pcell) {
dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, pcell_parameters_to_string (m_pcell_parameters));
} else {
dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, std::string ());
}
dispatcher ()->config_end ();
}
void
InstService::do_begin_edit (const db::DPoint &p)
{
m_has_valid_cell = false;
m_disp = snap (p);
const lay::CellView &cv = view ()->cellview (m_cv_index);
if (! cv.is_valid ()) {
return;
}
if (cv.cell ()->is_proxy ()) {
throw tl::Exception (tl::to_string (tr ("Cannot put an instance into a PCell or library cell")));
}
m_trans = cv.context_trans ();
std::pair<bool, db::cell_index_type> ci = make_cell (cv);
if (ci.first) {
// use the snapped lower left corner of the bbox unless the origin is inside the bbox
db::Box cell_bbox = cv->layout ().cell (ci.second).bbox_with_empty ();
if (! m_place_origin && ! cell_bbox.contains (db::Point ())) {
db::CplxTrans ct (1.0, m_angle, m_mirror, db::DVector ());
m_disp = db::DPoint () + (m_disp - snap (cell_bbox.transformed (ct).lower_left () * cv->layout ().dbu ()));
}
}
// compute the transformation variants
// TODO: this is duplicated code
// TODO: from this computed vector we take just the first one!
std::vector<db::DCplxTrans> tv;
for (lay::LayerPropertiesConstIterator l = view ()->begin_layers (); !l.at_end (); ++l) {
if (! l->has_children ()) {
int cvi = (l->cellview_index () >= 0) ? l->cellview_index () : 0;
if (cvi == m_cv_index) {
tv.insert (tv.end (), l->trans ().begin (), l->trans ().end ());
}
}
}
std::sort (tv.begin (), tv.end ());
tv.erase (std::unique (tv.begin (), tv.end ()), tv.end ());
if (! tv.empty ()) {
m_trans = db::VCplxTrans (1.0 / cv->layout ().dbu ()) * tv [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ();
}
open_editor_hooks ();
update_marker ();
}
std::pair<bool, db::cell_index_type>
InstService::make_cell (const lay::CellView &cv)
{
if (m_has_valid_cell) {
return std::make_pair (true, m_current_cell);
}
#if defined(HAVE_QT)
// prevents recursion
lay::BusySection busy;
#endif
// NOTE: do this at the beginning: creating a transaction might delete transactions behind the
// head transaction, hence releasing (thus: deleting) cells. To prevert interference, create
// the transaction at the beginning.
db::Transaction transaction (manager (), tl::to_string (tr ("Create reference cell")), m_reference_transaction_id);
lay::LayerState layer_state = view ()->layer_snapshot ();
db::Library *lib = db::LibraryManager::instance ().lib_ptr_by_name (m_lib_name, cv->tech_name ());
// find the layout the cell has to be looked up: that is either the layout of the current instance or
// the library selected
if (lib) {
mp_current_layout = &lib->layout ();
} else {
mp_current_layout = &cv->layout ();
}
std::pair<bool, db::cell_index_type> ci (false, db::cell_index_type (0));
std::pair<bool, db::pcell_id_type> pci (false, db::pcell_id_type (0));
if (! m_is_pcell) {
ci = mp_current_layout->cell_by_name (m_cell_or_pcell_name.c_str ());
} else {
pci = mp_current_layout->pcell_by_name (m_cell_or_pcell_name.c_str ());
}
if (! ci.first && ! pci.first) {
return std::pair<bool, db::cell_index_type> (false, 0);
}
db::cell_index_type inst_cell_index = ci.second;
mp_pcell_decl = 0;
// instantiate the PCell
if (pci.first) {
std::vector<tl::Variant> pv;
mp_pcell_decl = mp_current_layout->pcell_declaration (pci.second);
if (mp_pcell_decl) {
pv = mp_pcell_decl->map_parameters (m_pcell_parameters);
// make the parameters fit (i.e. PCells may not define consistent default parameters)
mp_pcell_decl->coerce_parameters (*mp_current_layout, pv);
}
inst_cell_index = mp_current_layout->get_pcell_variant (pci.second, pv);
}
// reference the library
if (lib) {
mp_current_layout = & cv->layout ();
inst_cell_index = mp_current_layout->get_lib_proxy (lib, inst_cell_index);
// remove unused references
std::set<db::cell_index_type> keep;
keep.insert (inst_cell_index);
mp_current_layout->cleanup (keep);
}
view ()->add_new_layers (layer_state);
m_has_valid_cell = true;
m_current_cell = inst_cell_index;
if (! transaction.is_empty ()) {
m_reference_transaction_id = transaction.id ();
}
return std::pair<bool, db::cell_index_type> (true, inst_cell_index);
}
void
InstService::do_mouse_move_inactive (const db::DPoint &p)
{
clear_mouse_cursors ();
add_mouse_cursor (snap (p));
}
void
InstService::do_mouse_move (const db::DPoint &p)
{
do_mouse_move_inactive (p);
set_cursor (lay::Cursor::cross);
const lay::CellView &cv = view ()->cellview (m_cv_index);
if (! cv.is_valid ()) {
return;
}
m_disp = snap (p);
std::pair<bool, db::cell_index_type> ci = make_cell (cv);
if (ci.first) {
// use the snapped lower left corner of the bbox unless the origin is inside the bbox
db::Box cell_bbox = cv->layout ().cell (ci.second).bbox_with_empty ();
if (! m_place_origin && ! cell_bbox.contains (db::Point ())) {
db::CplxTrans ct (1.0, m_angle, m_mirror, db::DVector ());
m_disp = db::DPoint () + (m_disp - snap (cell_bbox.transformed (ct).lower_left () * cv->layout ().dbu ()));
}
}
update_marker ();
}
void
InstService::do_mouse_transform (const db::DPoint &p, db::DFTrans trans)
{
db::DCplxTrans ct (1.0, m_angle, m_mirror, db::DVector ());
ct *= db::DCplxTrans (trans);
m_angle = ct.angle ();
m_mirror = ct.is_mirror ();
db::DPoint r (m_row_x, m_row_y);
r.transform (trans);
m_row_x = r.x ();
m_row_y = r.y ();
db::DPoint c (m_column_x, m_column_y);
c.transform (trans);
m_column_x = c.x ();
m_column_y = c.y ();
dispatcher ()->config_set (cfg_edit_inst_angle, m_angle);
dispatcher ()->config_set (cfg_edit_inst_mirror, m_mirror);
dispatcher ()->config_set (cfg_edit_inst_row_x, m_row_x);
dispatcher ()->config_set (cfg_edit_inst_row_y, m_row_y);
dispatcher ()->config_set (cfg_edit_inst_column_x, m_column_x);
dispatcher ()->config_set (cfg_edit_inst_column_y, m_column_y);
dispatcher ()->config_end ();
// honour the new transformation
do_mouse_move (p);
}
bool
InstService::do_mouse_click (const db::DPoint &p)
{
do_mouse_move (p);
return true;
}
void
InstService::do_finish_edit ()
{
try {
db::CellInstArray inst;
if (get_inst (inst)) {
// check for recursive hierarchy
const lay::CellView &cv = view ()->cellview (m_cv_index);
std::set <db::cell_index_type> called, callers;
cv->layout ().cell (inst.object ().cell_index ()).collect_called_cells (called);
called.insert (inst.object ().cell_index ());
cv.cell ()->collect_caller_cells (callers);
callers.insert (cv.cell_index ());
std::vector <db::cell_index_type> intersection;
std::set_intersection (called.begin (), called.end (), callers.begin (), callers.end (), std::back_inserter (intersection));
if (! intersection.empty ()) {
throw tl::Exception (tl::to_string (tr ("Inserting this instance would create a recursive hierarchy")));
}
if (manager ()) {
manager ()->transaction (tl::to_string (tr ("Create instance")), m_reference_transaction_id);
}
m_reference_transaction_id = 0;
db::Instance i = cv.cell ()->insert (inst);
cv->layout ().cleanup ();
if (manager ()) {
manager ()->commit ();
}
commit_recent ();
if (m_in_drag_drop) {
lay::ObjectInstPath sel;
sel.set_cv_index (m_cv_index);
sel.set_topcell (cv.cell_index ());
sel.add_path (db::InstElement (i, db::CellInstArray::iterator ()));
add_selection (sel);
}
}
m_has_valid_cell = false;
m_in_drag_drop = false;
close_editor_hooks (true);
} catch (...) {
m_has_valid_cell = false;
m_in_drag_drop = false;
close_editor_hooks (false);
throw;
}
}
void
InstService::do_cancel_edit ()
{
// Undo "create reference" transactions which basically unfinished "create instance" transactions
if (m_reference_transaction_id > 0 && manager ()->transaction_id_for_undo () == m_reference_transaction_id) {
manager ()->undo ();
}
m_reference_transaction_id = 0;
m_has_valid_cell = false;
m_in_drag_drop = false;
set_edit_marker (0);
// clean up any proxy cells created so far
const lay::CellView &cv = view ()->cellview (m_cv_index);
if (cv.is_valid ()) {
cv->layout ().cleanup ();
}
close_editor_hooks (false);
}
void
InstService::service_configuration_changed ()
{
m_needs_update = true;
}
bool
InstService::configure (const std::string &name, const std::string &value)
{
if (name == cfg_edit_inst_cell_name) {
if (value != m_cell_or_pcell_name) {
m_cell_or_pcell_name = value;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_lib_name) {
if (value != m_lib_name) {
m_lib_name_previous = m_lib_name;
m_lib_name = value;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_pcell_parameters) {
std::map<std::string, tl::Variant> pcp = pcell_parameters_from_string (value);
if (pcp != m_pcell_parameters) {
m_pcell_parameters = pcp;
m_is_pcell = ! value.empty ();
m_needs_update = true;
m_parameters_changed = true;
}
return true; // taken
}
if (name == cfg_edit_inst_place_origin) {
bool f;
tl::from_string (value, f);
if (f != m_place_origin) {
m_place_origin = f;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_scale) {
double s;
tl::from_string (value, s);
if (fabs (s - m_scale) > 1e-10) {
m_scale = s;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_angle) {
double a;
tl::from_string (value, a);
if (fabs (a - m_angle) > 1e-10) {
m_angle = a;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_mirror) {
bool f;
tl::from_string (value, f);
if (f != m_mirror) {
m_mirror = f;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_array) {
bool f;
tl::from_string (value, f);
if (f != m_array) {
m_array = f;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_rows) {
unsigned int v;
tl::from_string (value, v);
if (v != m_rows) {
m_rows = v;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_row_x) {
double v;
tl::from_string (value, v);
if (! db::coord_traits<double>::equal (m_row_x, v)) {
m_row_x = v;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_row_y) {
double v;
tl::from_string (value, v);
if (! db::coord_traits<double>::equal (m_row_y, v)) {
m_row_y = v;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_columns) {
unsigned int v;
tl::from_string (value, v);
if (v != m_columns) {
m_columns = v;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_column_x) {
double v;
tl::from_string (value, v);
if (! db::coord_traits<double>::equal (m_column_x, v)) {
m_column_x = v;
m_needs_update = true;
}
return true; // taken
}
if (name == cfg_edit_inst_column_y) {
double v;
tl::from_string (value, v);
if (! db::coord_traits<double>::equal (m_column_y, v)) {
m_column_y = v;
m_needs_update = true;
}
return true; // taken
}
return edt::Service::configure (name, value);
}
void
InstService::switch_cell_or_pcell (bool switch_parameters)
{
// if the library or cell name has changed, store the current pcell parameters and try to reuse
// an existing parameter set
if (! m_cell_or_pcell_name_previous.empty () && (m_cell_or_pcell_name_previous != m_cell_or_pcell_name || m_lib_name_previous != m_lib_name)) {
m_stored_pcell_parameters[std::make_pair (m_cell_or_pcell_name_previous, m_lib_name_previous)] = m_pcell_parameters;
if (switch_parameters) {
std::map<std::pair<std::string, std::string>, std::map<std::string, tl::Variant> >::const_iterator p = m_stored_pcell_parameters.find (std::make_pair (m_cell_or_pcell_name, m_lib_name));
if (p != m_stored_pcell_parameters.end ()) {
m_pcell_parameters = p->second;
} else {
m_pcell_parameters.clear ();
}
}
}
const lay::CellView &cv = view ()->cellview (m_cv_index);
db::Library *lib = 0;
if (cv.is_valid ()) {
lib = db::LibraryManager::instance ().lib_ptr_by_name (m_lib_name, cv->tech_name ());
} else {
lib = db::LibraryManager::instance ().lib_ptr_by_name (m_lib_name);
}
// find the layout the cell has to be looked up: that is either the layout of the current instance or
// the library selected
const db::Layout *layout = 0;
if (lib) {
layout = &lib->layout ();
} else if (cv.is_valid ()) {
layout = &cv->layout ();
}
if (layout) {
m_is_pcell = layout->pcell_by_name (m_cell_or_pcell_name.c_str ()).first;
} else {
m_is_pcell = false;
}
// remember the current cell or library name
m_cell_or_pcell_name_previous = m_cell_or_pcell_name;
m_lib_name_previous = m_lib_name;
}
void
InstService::config_finalize ()
{
if (m_needs_update) {
// don't switch parameters if they have been updated explicitly
// since the last "config_finalize". This means the sender of the configuration events
// wants the parameters to be set in a specific way. Don't interfere.
bool switch_parameters = ! m_parameters_changed;
switch_cell_or_pcell (switch_parameters);
m_has_valid_cell = false;
update_marker ();
if (switch_parameters) {
// Reflects any changes in PCell parameters in the configuration
// TODO: it's somewhat questionable to do this inside "config_finalize" as this method is supposed
// to reflect changes rather than induce some.
if (m_is_pcell) {
dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, pcell_parameters_to_string (m_pcell_parameters));
} else {
dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, std::string ());
}
}
}
m_needs_update = false;
m_parameters_changed = false;
edt::Service::config_finalize ();
}
void
InstService::update_marker ()
{
if (editing ()) {
lay::Marker *marker = new lay::Marker (view (), m_cv_index, ! show_shapes_of_instances (), show_shapes_of_instances () ? max_shapes_of_instances () : 0);
marker->set_vertex_shape (lay::ViewOp::Cross);
marker->set_vertex_size (9 /*cross vertex size*/);
set_edit_marker (marker);
db::CellInstArray inst;
if (get_inst (inst)) {
marker->set (inst, m_trans);
} else {
marker->set ();
}
} 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
InstService::get_inst (db::CellInstArray &inst)
{
const lay::CellView &cv = view ()->cellview (m_cv_index);
if (cv.is_valid ()) {
std::pair<bool, db::cell_index_type> ci = make_cell (cv);
if (ci.first) {
// compute the instance's transformation
db::VCplxTrans pt = (db::CplxTrans (cv->layout ().dbu ()) * m_trans).inverted ();
db::ICplxTrans trans = db::ICplxTrans (m_scale, m_angle, m_mirror, pt * m_disp - db::Point ());
if (m_array && m_rows > 0 && m_columns > 0) {
db::Vector row = db::Vector (pt * db::DVector (m_row_x, m_row_y));
db::Vector column = db::Vector (pt * db::DVector (m_column_x, m_column_y));
inst = db::CellInstArray (db::CellInst (ci.second), trans, row, column, m_rows, m_columns);
} else {
inst = db::CellInstArray (db::CellInst (ci.second), trans);
}
return true;
}
}
return false;
}
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

@ -0,0 +1,116 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtInstService
#define HDR_edtInstService
#include "edtService.h"
#include "edtEditorHooks.h"
namespace lay
{
class CellView;
class DragDropDataBase;
}
namespace edt
{
/**
* @brief Implementation of edt::Service for instance editing
*/
class InstService
: public edt::Service
{
public:
InstService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_mouse_transform (const db::DPoint &p, db::DFTrans trans);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool do_activated ();
#if defined(HAVE_QT)
virtual bool drag_enter_event (const db::DPoint &p, const lay::DragDropDataBase *data);
virtual bool drag_move_event (const db::DPoint &p, const lay::DragDropDataBase *data);
virtual void drag_leave_event ();
virtual bool drop_event (const db::DPoint &p, const lay::DragDropDataBase *data);
#endif
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
bool configure (const std::string &name, const std::string &value);
void service_configuration_changed ();
void config_finalize ();
private:
double m_angle;
double m_scale;
bool m_mirror;
db::DPoint m_disp;
std::string m_cell_or_pcell_name, m_lib_name;
std::string m_cell_or_pcell_name_previous, m_lib_name_previous;
std::map<std::string, tl::Variant> m_pcell_parameters;
std::map<std::pair<std::string, std::string>, std::map<std::string, tl::Variant> > m_stored_pcell_parameters;
bool m_is_pcell;
bool m_array;
unsigned int m_rows, m_columns;
double m_row_x, m_row_y, m_column_x, m_column_y;
bool m_place_origin;
db::Manager::transaction_id_t m_reference_transaction_id;
bool m_needs_update, m_parameters_changed;
bool m_has_valid_cell;
bool m_in_drag_drop;
db::cell_index_type m_current_cell;
db::Layout *mp_current_layout;
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);
std::pair<bool, db::cell_index_type> make_cell (const lay::CellView &cv);
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;
}
};
}
#endif

View File

@ -36,7 +36,12 @@
#include "edtPlugin.h"
#include "edtMainService.h"
#include "edtService.h"
#include "edtServiceImpl.h"
#include "edtInstService.h"
#include "edtPolygonService.h"
#include "edtPathService.h"
#include "edtTextService.h"
#include "edtBoxService.h"
#include "edtPointService.h"
#include "edtConfig.h"
#include "edtDistribute.h"

View File

@ -0,0 +1,717 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtPathService.h"
#include "layLayoutViewBase.h"
#include "layFinder.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.h"
# include "layLayoutView.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// PathService implementation
PathService::PathService (db::Manager *manager, lay::LayoutViewBase *view)
: ShapeEditService (manager, view, db::ShapeIterator::Paths),
m_width (0.1), m_bgnext (0.0), m_endext (0.0), m_type (Flush), m_needs_update (true)
{
// .. nothing yet ..
}
PathService::~PathService ()
{
// .. nothing yet ..
}
#if defined(HAVE_QT)
std::vector<lay::PropertiesPage *>
PathService::properties_pages (db::Manager *manager, QWidget *parent)
{
std::vector<lay::PropertiesPage *> pages;
if (view ()->is_editable ()) {
pages.push_back (new edt::EditablePathPropertiesPage (this, manager, parent));
} else {
pages.push_back (new edt::PathPropertiesPage (this, manager, parent));
}
return pages;
}
#endif
void
PathService::do_begin_edit (const db::DPoint &p)
{
get_edit_layer ();
m_previous_segments.clear ();
db::DPoint pp = snap2 (p);
m_last = pp;
m_points.clear ();
m_points.push_back (pp);
m_points.push_back (pp);
open_editor_hooks ();
set_edit_marker (new lay::Marker (view (), cv_index ()));
update_marker ();
}
bool
PathService::do_activated ()
{
return false; // don't start editing immediately
}
void
PathService::set_last_point (const db::DPoint &p)
{
m_points.back () = snap2 (p, m_last);
// for manhattan polygons allow some movement of the projected edge
if (m_points.size () >= 3 && connect_ac () == lay::AC_Ortho) {
db::DPoint p_grid = snap2 (p);
std::pair<bool, db::DPoint> ip = interpolate (m_points.end ()[-3], m_last, p_grid);
if (ip.first) {
m_points.end ()[-2] = ip.second;
m_points.back () = p_grid;
}
} else if (m_points.size () >= 2) {
m_points.end ()[-2] = m_last;
}
}
void
PathService::do_mouse_move_inactive (const db::DPoint &p)
{
lay::PointSnapToObjectResult snap_details = snap2_details (p);
mouse_cursor_from_snap_details (snap_details);
}
void
PathService::do_mouse_move (const db::DPoint &p)
{
do_mouse_move_inactive (p);
set_cursor (lay::Cursor::cross);
if (m_points.size () >= 2) {
set_last_point (p);
}
update_marker ();
update_via ();
}
bool
PathService::do_mouse_click (const db::DPoint &p)
{
if (m_points.size () >= 1) {
m_last = m_points.back ();
m_points.push_back (db::DPoint ());
set_last_point (p);
}
return false;
}
void
PathService::do_delete ()
{
if (m_points.size () > 2) {
m_points.erase (m_points.end () - 2);
m_last = m_points.end()[-2];
update_marker ();
update_via ();
} else if (! m_previous_segments.empty ()) {
pop_segment ();
}
}
void
PathService::do_finish_edit ()
{
// one point is reserved for the "current one"
if (m_points.size () < 3) {
throw tl::Exception (tl::to_string (tr ("A path must have at least 2 points")));
}
m_points.pop_back ();
deliver_shape (get_path ());
commit_recent ();
close_editor_hooks (true);
}
void
PathService::update_marker ()
{
lay::Marker *marker = dynamic_cast<lay::Marker *> (edit_marker ());
if (marker) {
db::Path path (get_path ());
marker->set (path, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
if (m_points.size () >= 2) {
view ()->message (std::string ("lx: ") +
tl::micron_to_string (m_points.back ().x () - m_points.end () [-2].x ()) +
std::string (" ly: ") +
tl::micron_to_string (m_points.back ().y () - m_points.end () [-2].y ()) +
std::string (" l: ") +
tl::micron_to_string (m_points.back ().distance (m_points.end () [-2])));
}
}
// 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
PathService::get_path () const
{
db::Path path;
std::vector<db::Point> points_dbu;
points_dbu.reserve (m_points.size ());
for (std::vector<db::DPoint>::const_iterator p = m_points.begin (); p != m_points.end (); ++p) {
points_dbu.push_back (trans () * *p);
}
path.width (trans ().ctrans (m_width));
path.round (m_type == Round);
if (m_type == Flush) {
path.bgn_ext (0);
path.end_ext (0);
} else if (m_type == Square || m_type == Round) {
path.bgn_ext (path.width () / 2);
path.end_ext (path.width () / 2);
} else {
path.bgn_ext (trans ().ctrans (m_bgnext));
path.end_ext (trans ().ctrans (m_endext));
}
path.assign (points_dbu.begin (), points_dbu.end ());
return path;
}
void
PathService::do_cancel_edit ()
{
close_editor_hooks (false);
}
bool
PathService::selection_applies (const lay::ObjectInstPath &sel) const
{
return !sel.is_cell_inst () && sel.shape ().is_path ();
}
void
PathService::via (int dir)
{
// see TODO below
#if defined(HAVE_QT)
if (combine_mode () != CM_Add) {
throw tl::Exception (tl::to_string (tr ("Vias are only available in 'Add' combination mode")));
}
if (editing ()) {
via_editing (dir);
} else {
via_initial (dir);
}
#endif
}
bool
PathService::get_via_for (const db::LayerProperties &lp, unsigned int cv_index, int dir, db::SelectedViaDefinition &via_def)
{
const lay::CellView &cv = view ()->cellview (cv_index);
if (! cv.is_valid ()) {
return false;
}
std::vector<db::SelectedViaDefinition> via_defs = db::find_via_definitions_for (cv->layout ().technology_name (), lp, dir);
if (via_defs.size () == 0) {
return false;
} else if (via_defs.size () == 1) {
via_def = via_defs.front ();
} else if (via_defs.size () > 1) {
#if defined(HAVE_QT)
// present a menu with the available vias.
// TODO: what to do here in Qt-less case?
QWidget *view_widget = lay::widget_from_view (view ());
if (! view_widget) {
return false;
}
std::unique_ptr<QMenu> menu (new QMenu (view_widget));
menu->show ();
db::DPoint mp_local = view ()->canvas ()->mouse_position ();
QPoint mp = view ()->canvas ()->widget ()->mapToGlobal (QPoint (mp_local.x (), mp_local.y ()));
for (auto i = via_defs.begin (); i != via_defs.end (); ++i) {
QAction *a = menu->addAction (tl::to_qstring (i->via_type.description.empty () ? i->via_type.name : i->via_type.description));
a->setData (int (i - via_defs.begin ()));
}
QAction *action = menu->exec (mp);
if (! action) {
return false;
}
via_def = via_defs [action->data ().toInt ()];
#endif
}
return true;
}
db::Instance
PathService::make_via (const db::SelectedViaDefinition &via_def, double w_bottom, double h_bottom, double w_top, double h_top, const db::DPoint &via_pos)
{
if (! via_def.via_type.cut.is_null ()) {
edt::set_or_request_current_layer (view (), via_def.via_type.cut, cv_index (), false /*don't make current*/);
}
std::map<std::string, tl::Variant> params;
params.insert (std::make_pair ("via", tl::Variant (via_def.via_type.name)));
params.insert (std::make_pair ("w_bottom", tl::Variant (w_bottom)));
params.insert (std::make_pair ("w_top", tl::Variant (w_top)));
params.insert (std::make_pair ("h_bottom", tl::Variant (h_bottom)));
params.insert (std::make_pair ("h_top", tl::Variant (h_top)));
auto via_lib_cell = via_def.lib->layout ().get_pcell_variant_dict (via_def.pcell, params);
auto via_cell = layout ().get_lib_proxy (via_def.lib, via_lib_cell);
return cell ().insert (db::CellInstArray (db::CellInst (via_cell), db::Trans (trans () * via_pos - db::Point ())));
}
void
PathService::via_initial (int dir)
{
if (! mouse_in_view ()) {
return;
}
// compute search box
double l = catch_distance ();
db::DPoint pos = mouse_pos ();
db::DBox search_box = db::DBox (pos, pos).enlarged (db::DVector (l, l));
lay::ShapeFinder finder (true, false, db::ShapeIterator::Regions);
// go through all visible layers of all cellviews
finder.find (view (), search_box);
// collect the founds from the finder
lay::ShapeFinder::iterator r = finder.begin ();
if (r == finder.end ()) {
return;
}
const lay::CellView &cv = view ()->cellview (r->cv_index ());
if (! cv.is_valid ()) {
return;
}
db::LayerProperties lp = cv->layout ().get_properties (r->layer ());
db::SelectedViaDefinition via_def;
if (! get_via_for (lp, r->cv_index (), dir, via_def)) {
return;
}
set_layer (lp, r->cv_index ());
bool is_bottom = via_def.via_type.bottom.log_equal (lp);
db::LayerProperties lp_new = is_bottom ? via_def.via_type.top : via_def.via_type.bottom;
{
db::Transaction transaction (manager (), tl::to_string (tr ("Create path segment")));
change_edit_layer (lp_new);
begin_edit (pos);
// create the via cell
// (using 0.0 for all dimensions to indicate "place here")
db::Instance via_instance = make_via (via_def, 0.0, 0.0, 0.0, 0.0, m_last);
push_segment (db::Shape (), via_instance, via_def.via_type, transaction.id ());
}
}
void
PathService::compute_via_wh (double &w, double &h, const db::DVector &dwire, double var_ext, double grid)
{
w = 0.0, h = 0.0;
if (m_type == Round) {
// a square sitting in the circle at the end
w = h = sqrt (0.5) * m_width;
} else {
double ext = 0.0;
if (m_type == Square) {
ext = m_width * 0.5;
} else if (m_type == Variable) {
ext = var_ext;
}
double vl = dwire.length ();
if (vl < db::epsilon || ext < -db::epsilon) {
// no specific dimension
} else if (ext < db::epsilon) {
// a rectangle enclosing the flush end edge
db::DVector l = dwire * (m_width / vl);
w = std::abs (l.y ());
h = std::abs (l.x ());
} else if (std::fabs (dwire.x ()) < db::epsilon) {
// vertical path
w = m_width;
h = ext * 2.0;
} else if (std::fabs (dwire.y ()) < db::epsilon) {
// horizontal path
h = m_width;
w = ext * 2.0;
} else {
// compute dimension of max. inscribed box
db::DVector v = db::DVector (std::abs (dwire.x ()) / vl, std::abs (dwire.y ()) / vl);
double e = ext, en = m_width * 0.5;
bool swap_xy = false;
if (e > en) {
std::swap (e, en);
v = db::DVector (v.y (), v.x ());
swap_xy = true;
}
double vd = v.y () * v.y () - v.x () * v.x ();
double vp = v.x () * v.y ();
double l = e * 0.5 * vd / vp;
if (std::abs (vd) > db::epsilon) {
double l1 = (en - 2 * e * vp) / vd;
double l2 = (-en - 2 * e * vp) / vd;
l = std::max (l, std::min (l1, l2));
l = std::min (l, std::max (l1, l2));
}
db::DVector a = v * e + db::DVector (v.y (), -v.x ()) * l;
w = a.x () * 2.0;
h = a.y () * 2.0;
if (swap_xy) {
std::swap (w, h);
}
}
}
// round to grid or DBU
if (grid < db::epsilon) {
grid = layout ().dbu ();
}
w = floor (w / grid + db::epsilon) * grid;
h = floor (h / grid + db::epsilon) * grid;
}
void
PathService::via_editing (int dir)
{
// not enough points to form a path
if (m_points.size () < 2) {
return;
}
db::LayerProperties lp = layout ().get_properties (layer ());
db::SelectedViaDefinition via_def;
if (! get_via_for (lp, cv_index (), dir, via_def)) {
return;
}
commit_recent ();
bool is_bottom = via_def.via_type.bottom.log_equal (lp);
db::LayerProperties lp_new = is_bottom ? via_def.via_type.top : via_def.via_type.bottom;
// compute the via parameters
db::DVector dwire = m_points.back () - m_points [m_points.size () - 2];
double w = 0.0, h = 0.0;
compute_via_wh (w, h, dwire, m_endext, is_bottom ? via_def.via_type.bottom_grid : via_def.via_type.top_grid);
double w_bottom = 0.0, h_bottom = 0.0, w_top = 0.0, h_top = 0.0;
(is_bottom ? w_bottom : w_top) = w;
(is_bottom ? h_bottom : h_top) = h;
// create the path and via
db::DPoint via_pos = m_points.back ();
{
db::Transaction transaction (manager (), tl::to_string (tr ("Create path segment")));
db::Shape path_shape = cell ().shapes (layer ()).insert (get_path ());
db::Instance via_instance = make_via (via_def, w_bottom, h_bottom, w_top, h_top, via_pos);
push_segment (path_shape, via_instance, via_def.via_type, transaction.id ());
change_edit_layer (lp_new);
}
m_points.clear ();
m_points.push_back (via_pos);
m_points.push_back (via_pos);
m_last = m_points.back ();
update_marker ();
update_via ();
}
void
PathService::update_via ()
{
if (! editing () || m_points.size () < 2) {
return;
}
if (m_previous_segments.empty () || m_previous_segments.back ().via_instance.is_null ()) {
return;
}
PathSegment &ps = m_previous_segments.back ();
if (! ps.via_instance.instances ()) {
return;
}
db::Cell *via_parent_cell = ps.via_instance.instances ()->cell ();
// Compute the parameters to change
db::LayerProperties lp = layout ().get_properties (layer ());
bool is_bottom = ps.via_type.bottom.log_equal (lp);
double w = 0.0, h = 0.0;
compute_via_wh (w, h, m_points [1] - m_points [0], m_bgnext, is_bottom ? ps.via_type.bottom_grid : ps.via_type.top_grid);
std::map<std::string, tl::Variant> params;
if (is_bottom) {
params.insert (std::make_pair ("w_bottom", tl::Variant (w)));
params.insert (std::make_pair ("h_bottom", tl::Variant (h)));
} else {
params.insert (std::make_pair ("w_top", tl::Variant (w)));
params.insert (std::make_pair ("h_top", tl::Variant (h)));
}
// change the via PCell
{
db::Transaction transaction (manager () && ! manager ()->transacting () ? manager () : 0, std::string (), ps.transaction_id);
ps.via_instance = via_parent_cell->change_pcell_parameters (ps.via_instance, params);
layout ().cleanup ();
}
}
void
PathService::push_segment (const db::Shape &shape, const db::Instance &instance, const db::ViaType &via_type, db::Manager::transaction_id_t transaction_id)
{
m_previous_segments.push_back (PathSegment ());
PathSegment &ps = m_previous_segments.back ();
ps.points = m_points;
ps.last_point = m_last;
ps.path_shape = shape;
ps.via_instance = instance;
ps.via_type = via_type;
ps.layer = layout ().get_properties (layer ());
ps.cv_index = cv_index ();
ps.transaction_id = transaction_id;
static std::string path_config_keys [] = {
cfg_edit_path_width,
cfg_edit_path_ext_var_begin,
cfg_edit_path_ext_var_end,
cfg_edit_path_ext_type
};
for (unsigned int i = 0; i < sizeof (path_config_keys) / sizeof (path_config_keys[0]); ++i) {
ps.config.push_back (std::make_pair (path_config_keys [i], std::string ()));
dispatcher ()->config_get (ps.config.back ().first, ps.config.back ().second);
}
}
void
PathService::pop_segment ()
{
PathSegment ps = m_previous_segments.back ();
m_previous_segments.pop_back ();
if (manager () && manager ()->transaction_id_for_undo () == ps.transaction_id) {
// should remove shape and via instance
manager ()->undo ();
// empties the undo queue, so we don't keep objects there and spoil subsequent "update_via" actions
// TODO: is there a better way to do this?
manager ()->transaction (std::string ());
manager ()->cancel ();
} else {
// fallback without using undo
db::Transaction transaction (manager (), tl::to_string (tr ("Undo path segment")));
if (! ps.path_shape.is_null () && ps.path_shape.shapes ()) {
ps.path_shape.shapes ()->erase_shape (ps.path_shape);
}
if (! ps.via_instance.is_null () && ps.via_instance.instances ()) {
ps.via_instance.instances ()->erase (ps.via_instance);
}
}
set_layer (ps.layer, ps.cv_index);
m_points = ps.points;
m_last = ps.last_point;
for (auto i = ps.config.begin (); i != ps.config.end (); ++i) {
dispatcher ()->config_set (i->first, i->second);
}
// avoids update_via() which might spoil the via we just recovered
m_needs_update = false;
dispatcher ()->config_end ();
update_marker ();
}
bool
PathService::configure (const std::string &name, const std::string &value)
{
if (name == cfg_edit_path_width) {
tl::from_string (value, m_width);
m_needs_update = true;
return true; // taken
}
if (name == cfg_edit_path_ext_var_begin) {
tl::from_string (value, m_bgnext);
m_needs_update = true;
return true; // taken
}
if (name == cfg_edit_path_ext_var_end) {
tl::from_string (value, m_endext);
m_needs_update = true;
return true; // taken
}
if (name == cfg_edit_path_ext_type) {
m_type = Flush;
if (value == "square") {
m_type = Square;
} else if (value == "round") {
m_type = Round;
} else if (value == "variable") {
m_type = Variable;
}
m_needs_update = true;
return true; // taken
}
return ShapeEditService::configure (name, value);
}
void
PathService::config_finalize ()
{
if (m_needs_update) {
update_marker ();
update_via ();
m_needs_update = false;
}
ShapeEditService::config_finalize ();
}
} // namespace edt

View File

@ -0,0 +1,99 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtPathService
#define HDR_edtPathService
#include "edtShapeService.h"
namespace edt
{
/**
* @brief Implementation of edt::Service for path editing
*/
class PathService
: public ShapeEditService
{
public:
PathService (db::Manager *manager, lay::LayoutViewBase *view);
~PathService ();
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual void do_delete ();
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool do_activated ();
virtual void via (int dir);
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
bool configure (const std::string &name, const std::string &value);
void config_finalize ();
private:
struct PathSegment
{
PathSegment () : cv_index (0), transaction_id (0) { }
db::LayerProperties layer;
int cv_index;
std::list<std::pair<std::string, std::string> > config;
std::vector<db::DPoint> points;
db::DPoint last_point;
db::Shape path_shape;
db::Instance via_instance;
db::ViaType via_type;
db::Manager::transaction_id_t transaction_id;
};
std::vector<db::DPoint> m_points;
double m_width, m_bgnext, m_endext;
enum { Flush = 0, Square, Variable, Round } m_type;
bool m_needs_update;
db::DPoint m_last;
std::list<PathSegment> m_previous_segments;
void update_marker ();
db::Path get_path () const;
void set_last_point (const db::DPoint &p);
void update_via ();
void compute_via_wh (double &w, double &h, const db::DVector &dwire, double var_ext, double grid);
db::Instance make_via (const db::SelectedViaDefinition &via_def, double w_bottom, double h_bottom, double w_top, double h_top, const db::DPoint &via_pos);
void via_initial (int dir);
void via_editing (int dir);
bool get_via_for (const db::LayerProperties &lp, unsigned int cv_index, int dir, db::SelectedViaDefinition &via_def);
void push_segment (const db::Shape &shape, const db::Instance &instance, const db::ViaType &via_type, db::Manager::transaction_id_t transaction_id);
void pop_segment ();
};
}
#endif

View File

@ -31,7 +31,12 @@
#include "edtPlugin.h"
#include "edtConfig.h"
#include "edtService.h"
#include "edtServiceImpl.h"
#include "edtPolygonService.h"
#include "edtPathService.h"
#include "edtTextService.h"
#include "edtBoxService.h"
#include "edtPointService.h"
#include "edtInstService.h"
#include "edtMainService.h"
#include "edtPartialService.h"
#include "edtMoveTrackerService.h"

View File

@ -0,0 +1,146 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtPointService.h"
#include "layLayoutViewBase.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// PointService implementation
PointService::PointService (db::Manager *manager, lay::LayoutViewBase *view)
: ShapeEditService (manager, view, db::ShapeIterator::Points)
{
// .. nothing yet ..
}
#if defined(HAVE_QT)
std::vector<lay::PropertiesPage *>
PointService::properties_pages (db::Manager *manager, QWidget *parent)
{
std::vector<lay::PropertiesPage *> pages;
pages.push_back (new edt::PointPropertiesPage (this, manager, parent));
return pages;
}
#endif
void
PointService::do_begin_edit (const db::DPoint &p)
{
get_edit_layer ();
db::DPoint pp = snap2 (p);
m_p = pp;
open_editor_hooks ();
set_edit_marker (new lay::Marker (view (), cv_index ()));
update_marker ();
}
db::Point
PointService::get_point () const
{
return db::Point (trans () * m_p);
}
void
PointService::update_marker ()
{
lay::Marker *marker = dynamic_cast<lay::Marker *> (edit_marker ());
if (marker) {
db::Point pt = get_point ();
marker->set (db::Box (pt, pt), db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
view ()->message (std::string ("x: ") +
tl::micron_to_string (m_p.x ()) +
std::string (" y: ") +
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
PointService::do_mouse_move_inactive (const db::DPoint &p)
{
lay::PointSnapToObjectResult snap_details = snap2_details (p);
mouse_cursor_from_snap_details (snap_details);
}
void
PointService::do_mouse_move (const db::DPoint &p)
{
do_mouse_move_inactive (p);
set_cursor (lay::Cursor::cross);
m_p = snap2 (p);
update_marker ();
}
bool
PointService::do_mouse_click (const db::DPoint &p)
{
do_mouse_move (p);
return true;
}
void
PointService::do_finish_edit ()
{
deliver_shape (get_point ());
commit_recent ();
close_editor_hooks (true);
}
void
PointService::do_cancel_edit ()
{
close_editor_hooks (false);
}
bool
PointService::selection_applies (const lay::ObjectInstPath &sel) const
{
return !sel.is_cell_inst () && sel.shape ().is_point ();
}
} // namespace edt

View File

@ -0,0 +1,62 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtPointService
#define HDR_edtPointService
#include "edtShapeService.h"
namespace edt
{
/**
* @brief Implementation of edt::Service for point editing
*/
class PointService
: public ShapeEditService
{
public:
PointService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
private:
db::DPoint m_p;
void update_marker ();
db::Point get_point () const;
};
}
#endif

View File

@ -0,0 +1,386 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtPolygonService.h"
#include "layLayoutViewBase.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// PolygonService implementation
PolygonService::PolygonService (db::Manager *manager, lay::LayoutViewBase *view)
: ShapeEditService (manager, view, db::ShapeIterator::Polygons),
m_closure_set (false), m_closure ()
{
// .. nothing yet ..
}
#if defined(HAVE_QT)
std::vector<lay::PropertiesPage *>
PolygonService::properties_pages (db::Manager *manager, QWidget *parent)
{
std::vector<lay::PropertiesPage *> pages;
pages.push_back (new edt::PolygonPropertiesPage (this, manager, parent));
return pages;
}
#endif
void
PolygonService::do_begin_edit (const db::DPoint &p)
{
get_edit_layer ();
db::DPoint pp = snap2 (p);
m_last = pp;
m_points.clear ();
m_points.push_back (pp);
m_points.push_back (pp);
m_closure_set = false;
open_editor_hooks ();
update_marker ();
}
void
PolygonService::set_last_point (const db::DPoint &p)
{
m_points.back () = snap2 (p, m_last);
// for manhattan polygons allow some movement of the projected edge
if (m_points.size () >= 3 && connect_ac () == lay::AC_Ortho) {
db::DPoint p_grid = snap2 (p);
std::pair<bool, db::DPoint> ip = interpolate (m_points.end ()[-3], m_last, p_grid);
if (ip.first) {
m_points.end ()[-2] = ip.second;
m_points.back () = p_grid;
}
} else if (m_points.size () >= 2) {
m_points.end ()[-2] = m_last;
}
}
void
PolygonService::do_mouse_move_inactive (const db::DPoint &p)
{
lay::PointSnapToObjectResult snap_details = snap2_details (p);
mouse_cursor_from_snap_details (snap_details);
}
void
PolygonService::do_delete ()
{
if (m_points.size () > 2) {
m_points.erase (m_points.end () - 2);
m_last = m_points.end()[-2];
update_marker ();
}
}
void
PolygonService::do_mouse_move (const db::DPoint &p)
{
do_mouse_move_inactive (p);
set_cursor (lay::Cursor::cross);
if (m_points.size () >= 2) {
set_last_point (p);
}
add_closure ();
update_marker ();
}
bool
PolygonService::do_mouse_click (const db::DPoint &p)
{
if (m_points.size () >= 1) {
m_last = m_points.back ();
m_points.push_back (db::DPoint ());
set_last_point (p);
}
// do not do a add_closure here - this will not work since we may have two identical points on top.
return false;
}
void
PolygonService::do_finish_edit ()
{
deliver_shape (get_polygon (false));
commit_recent ();
close_editor_hooks (true);
}
db::Polygon
PolygonService::get_polygon (bool editing) const
{
db::Polygon poly;
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 () + 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 (), !editing /*compress*/, !editing /*remove reflected*/);
if (! editing && poly.hull ().size () < 3) {
throw tl::Exception (tl::to_string (tr ("A polygon must have at least 3 effective points")));
}
return poly;
}
void
PolygonService::do_cancel_edit ()
{
close_editor_hooks (false);
}
bool
PolygonService::selection_applies (const lay::ObjectInstPath &sel) const
{
return !sel.is_cell_inst () && sel.shape ().is_polygon ();
}
void
PolygonService::add_closure ()
{
if (connect_ac () == lay::AC_Any || m_points.size () < 3) {
m_closure_set = false;
} else {
std::vector <db::DVector> delta;
delta.reserve (4);
// Even for diagonal mode, we try to do manhattan closing
delta.push_back (db::DVector (1.0, 0.0));
delta.push_back (db::DVector (0.0, 1.0));
// TODO: for Diagonal mode, this scheme does not work pretty well.
#if 0
if (connect_ac () == lay::AC_Diagonal) {
delta.push_back (db::DVector (1.0, -1.0));
delta.push_back (db::DVector (1.0, 1.0));
}
#endif
// Determine the closing point by determining the one of the possible closing points
// (given the angle constraints) that is closest to the current one.
m_closure = db::DPoint ();
m_closure_set = false;
std::vector <db::DPoint>::const_iterator pi;
db::DPoint p1, pl;
pi = m_points.begin () + 1;
while (pi != m_points.end () - 1 && *pi == m_points [0]) {
++pi;
}
p1 = *pi;
pi = m_points.end () - 2;
while (pi != m_points.begin () + 1 && *pi == m_points.back ()) {
--pi;
}
pl = *pi;
// first try a direct cut between last and first segment ..
db::DEdge e1 (m_points [0], m_points [1]);
db::DEdge e2 (m_points.end ()[-2], m_points.back ());
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
if (cp.first &&
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) + 1e-6 &&
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ()) + 1e-6) {
m_closure = cp.second;
m_closure_set = true;
}
// if that is not working out, try to keep one edge any vary the possible edges emerging from
// the other point
if ( ! m_closure_set) {
for (std::vector <db::DVector>::const_iterator d1 = delta.begin (); d1 != delta.end (); ++d1) {
db::DEdge e1 (m_points [0], m_points [0] + *d1);
db::DEdge e2 (m_points.end ()[-2], m_points.back ());
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
if (cp.first && (! m_closure_set || cp.second.sq_distance (m_points.back ()) < m_closure.sq_distance (m_points.back ())) &&
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) &&
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ())) {
m_closure = cp.second;
m_closure_set = true;
}
}
}
if ( ! m_closure_set) {
for (std::vector <db::DVector>::const_iterator d2 = delta.begin (); d2 != delta.end (); ++d2) {
db::DEdge e1 (m_points [0], m_points [1]);
db::DEdge e2 (m_points.back (), m_points.back () + *d2);
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
if (cp.first && (! m_closure_set || cp.second.sq_distance (m_points.back ()) < m_closure.sq_distance (m_points.back ())) &&
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) &&
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ())) {
m_closure = cp.second;
m_closure_set = true;
}
}
}
// if that is not working out, try each possible variations of edges from start and end point
if ( ! m_closure_set) {
for (std::vector <db::DVector>::const_iterator d1 = delta.begin (); d1 != delta.end (); ++d1) {
for (std::vector <db::DVector>::const_iterator d2 = delta.begin (); d2 != delta.end (); ++d2) {
db::DEdge e1 (m_points [0], m_points [0] + *d1);
db::DEdge e2 (m_points.back (), m_points.back () + *d2);
std::pair <bool, db::DPoint> cp = e1.cut_point (e2);
if (cp.first && (! m_closure_set || cp.second.sq_distance (m_points.back ()) < m_closure.sq_distance (m_points.back ())) &&
db::sprod (p1 - m_points [0], cp.second - m_points [0]) < 0.99 * p1.distance (m_points [0]) * cp.second.distance (m_points [0]) &&
db::sprod (pl - m_points.back (), cp.second - m_points.back ()) < 0.99 * pl.distance (m_points.back ()) * cp.second.distance (m_points.back ())) {
m_closure = cp.second;
m_closure_set = true;
}
}
}
}
}
}
void
PolygonService::update_marker ()
{
if (m_points.size () == 2) {
db::Edge edge (trans () * m_points [0], trans () * m_points [1]);
lay::Marker *marker = new lay::Marker (view (), cv_index ());
marker->set (edge, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
set_edit_marker (marker);
} else if (m_points.size () > 2) {
std::vector<db::Point> points_dbu;
points_dbu.reserve (m_points.size () + 1);
for (std::vector<db::DPoint>::const_iterator p = m_points.begin (); p != m_points.end (); ++p) {
points_dbu.push_back (trans () * *p);
}
db::Path path (points_dbu.begin (), points_dbu.end (), 0);
lay::Marker *marker;
marker = new lay::Marker (view (), cv_index ());
marker->set (path, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
set_edit_marker (marker);
db::DPoint pl = m_points.back ();
if (m_closure_set) {
db::Edge edge (trans () * pl, trans () * m_closure);
marker = new lay::Marker (view (), cv_index ());
if (std::abs (edge.dy ()) < std::abs (edge.dx ())) {
marker->set_frame_pattern (34);
} else {
marker->set_frame_pattern (39);
}
marker->set (edge, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
add_edit_marker (marker);
pl = m_closure;
}
db::Edge edge (trans () * pl, trans () * m_points.front ());
marker = new lay::Marker (view (), cv_index ());
if (std::abs (edge.dy ()) < std::abs (edge.dx ())) {
marker->set_frame_pattern (34);
} else {
marker->set_frame_pattern (39);
}
marker->set (edge, db::VCplxTrans (1.0 / layout ().dbu ()) * trans ().inverted ());
add_edit_marker (marker);
} else {
set_edit_marker (0);
}
if (m_points.size () >= 2) {
view ()->message (std::string ("lx: ") +
tl::micron_to_string (m_points.back ().x () - m_points.end () [-2].x ()) +
std::string (" ly: ") +
tl::micron_to_string (m_points.back ().y () - m_points.end () [-2].y ()) +
std::string (" l: ") +
tl::micron_to_string (m_points.back ().distance (m_points.end () [-2])));
}
// 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);
}
}
} // namespace edt

View File

@ -0,0 +1,68 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtPolygonService
#define HDR_edtPolygonService
#include "edtShapeService.h"
namespace edt
{
/**
* @brief Implementation of edt::Service for polygon editing
*/
class PolygonService
: public ShapeEditService
{
public:
PolygonService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_delete ();
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
private:
std::vector <db::DPoint> m_points;
bool m_closure_set;
db::DPoint m_closure;
db::DPoint m_last;
void update_marker ();
db::Polygon get_polygon (bool editing) const;
void add_closure ();
void set_last_point (const db::DPoint &p);
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,364 +0,0 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtServiceImpl
#define HDR_edtServiceImpl
#include "edtService.h"
#include "edtConfig.h"
#include "edtEditorHooks.h"
#include <memory>
namespace lay
{
class CellView;
class LayoutViewBase;
class LayerPropertiesConstIterator;
}
namespace edt
{
/**
* @brief Implementation of the edt::Service for generic shape editing
*/
class ShapeEditService
: public edt::Service
{
public:
ShapeEditService (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIterator::flags_type shape_types);
protected:
void get_edit_layer ();
void change_edit_layer (const db::LayerProperties &lp);
const db::VCplxTrans &trans () const { return m_trans; }
unsigned int layer () const { return m_layer; }
unsigned int cv_index () const { return m_cv_index; }
db::Cell &cell () const { return *mp_cell; }
db::Layout &layout () const { return *mp_layout; }
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual void tap (const db::DPoint &initial);
virtual bool configure (const std::string &name, const std::string &value);
virtual void activated ();
protected:
std::pair <bool, db::DPoint> interpolate (const db::DPoint &m, const db::DPoint &o, const db::DPoint &p) const;
void deliver_shape (const db::Polygon &poly);
void deliver_shape (const db::Path &path);
void deliver_shape (const db::Box &box);
void deliver_shape (const db::Point &point);
void set_layer (const db::LayerProperties &lp, unsigned int cv_index);
void open_editor_hooks ();
template <class Shape>
void deliver_shape_to_hooks (const Shape &shape);
void close_editor_hooks (bool with_commit);
combine_mode_type combine_mode () const { return m_combine_mode; }
void config_recent_for_layer (const db::LayerProperties &lp, int cv_index);
const tl::weak_collection<edt::EditorHooks> &editor_hooks ()
{
return m_editor_hooks;
}
virtual void current_layer_changed () { }
private:
db::VCplxTrans m_trans;
unsigned int m_layer;
unsigned int m_cv_index;
db::Cell *mp_cell;
db::Layout *mp_layout;
combine_mode_type m_combine_mode;
tl::weak_collection<edt::EditorHooks> m_editor_hooks;
bool m_update_edit_layer_enabled;
void update_edit_layer (const lay::LayerPropertiesConstIterator &iter);
};
/**
* @brief Implementation of edt::Service for polygon editing
*/
class PolygonService
: public ShapeEditService
{
public:
PolygonService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_delete ();
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
private:
std::vector <db::DPoint> m_points;
bool m_closure_set;
db::DPoint m_closure;
db::DPoint m_last;
void update_marker ();
db::Polygon get_polygon (bool editing) const;
void add_closure ();
void set_last_point (const db::DPoint &p);
};
/**
* @brief Implementation of edt::Service for box editing
*/
class BoxService
: public ShapeEditService
{
public:
BoxService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
private:
db::DPoint m_p1, m_p2;
void update_marker ();
db::Box get_box () const;
};
/**
* @brief Implementation of edt::Service for point editing
*/
class PointService
: public ShapeEditService
{
public:
PointService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
private:
db::DPoint m_p;
void update_marker ();
db::Point get_point () const;
};
/**
* @brief Implementation of edt::Service for text editing
*/
class TextService
: public ShapeEditService
{
public:
TextService (db::Manager *manager, lay::LayoutViewBase *view);
~TextService ();
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_transform (const db::DPoint &p, db::DFTrans trans);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool do_activated ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
virtual bool configure (const std::string &name, const std::string &value);
private:
db::DText m_text;
unsigned int m_rot;
void update_marker ();
db::Text get_text () const;
};
/**
* @brief Implementation of edt::Service for path editing
*/
class PathService
: public ShapeEditService
{
public:
PathService (db::Manager *manager, lay::LayoutViewBase *view);
~PathService ();
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual void do_delete ();
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool do_activated ();
virtual void via (int dir);
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
bool configure (const std::string &name, const std::string &value);
void config_finalize ();
private:
struct PathSegment
{
PathSegment () : cv_index (0), transaction_id (0) { }
db::LayerProperties layer;
int cv_index;
std::list<std::pair<std::string, std::string> > config;
std::vector<db::DPoint> points;
db::DPoint last_point;
db::Shape path_shape;
db::Instance via_instance;
db::ViaType via_type;
db::Manager::transaction_id_t transaction_id;
};
std::vector<db::DPoint> m_points;
double m_width, m_bgnext, m_endext;
enum { Flush = 0, Square, Variable, Round } m_type;
bool m_needs_update;
db::DPoint m_last;
std::list<PathSegment> m_previous_segments;
void update_marker ();
db::Path get_path () const;
void set_last_point (const db::DPoint &p);
void update_via ();
void compute_via_wh (double &w, double &h, const db::DVector &dwire, double var_ext, double grid);
db::Instance make_via (const db::SelectedViaDefinition &via_def, double w_bottom, double h_bottom, double w_top, double h_top, const db::DPoint &via_pos);
void via_initial (int dir);
void via_editing (int dir);
bool get_via_for (const db::LayerProperties &lp, unsigned int cv_index, int dir, db::SelectedViaDefinition &via_def);
void push_segment (const db::Shape &shape, const db::Instance &instance, const db::ViaType &via_type, db::Manager::transaction_id_t transaction_id);
void pop_segment ();
};
/**
* @brief Implementation of edt::Service for instance editing
*/
class InstService
: public edt::Service
{
public:
InstService (db::Manager *manager, lay::LayoutViewBase *view);
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual void do_mouse_move (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_mouse_transform (const db::DPoint &p, db::DFTrans trans);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool do_activated ();
#if defined(HAVE_QT)
virtual bool drag_enter_event (const db::DPoint &p, const lay::DragDropDataBase *data);
virtual bool drag_move_event (const db::DPoint &p, const lay::DragDropDataBase *data);
virtual void drag_leave_event ();
virtual bool drop_event (const db::DPoint &p, const lay::DragDropDataBase *data);
#endif
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
bool configure (const std::string &name, const std::string &value);
void service_configuration_changed ();
void config_finalize ();
private:
double m_angle;
double m_scale;
bool m_mirror;
db::DPoint m_disp;
std::string m_cell_or_pcell_name, m_lib_name;
std::string m_cell_or_pcell_name_previous, m_lib_name_previous;
std::map<std::string, tl::Variant> m_pcell_parameters;
std::map<std::pair<std::string, std::string>, std::map<std::string, tl::Variant> > m_stored_pcell_parameters;
bool m_is_pcell;
bool m_array;
unsigned int m_rows, m_columns;
double m_row_x, m_row_y, m_column_x, m_column_y;
bool m_place_origin;
db::Manager::transaction_id_t m_reference_transaction_id;
bool m_needs_update, m_parameters_changed;
bool m_has_valid_cell;
bool m_in_drag_drop;
db::cell_index_type m_current_cell;
db::Layout *mp_current_layout;
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);
std::pair<bool, db::cell_index_type> make_cell (const lay::CellView &cv);
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;
}
};
}
#endif

View File

@ -0,0 +1,491 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtShapeService.h"
#include "edtMainService.h"
#include "layLayoutViewBase.h"
#include "dbEdgeProcessor.h"
#include "dbPolygonTools.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.h"
# include "layLayoutView.h"
# include "layTipDialog.h"
# include "layEditorOptionsPages.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// ShapeEditService implementation
ShapeEditService::ShapeEditService (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIterator::flags_type shape_types)
: edt::Service (manager, view, shape_types),
m_layer (0), m_cv_index (0), mp_cell (0), mp_layout (0), m_combine_mode (CM_Add), m_update_edit_layer_enabled (true)
{
view->current_layer_changed_event.add (this, &ShapeEditService::update_edit_layer);
}
bool
ShapeEditService::configure (const std::string &name, const std::string &value)
{
if (name == cfg_edit_combine_mode) {
CMConverter ().from_string (value, m_combine_mode);
return false; // pass to other plugins
} else {
return edt::Service::configure (name, value);
}
}
void
ShapeEditService::activated ()
{
edt::Service::activated ();
if (view () == lay::LayoutView::current ()) {
lay::LayerPropertiesConstIterator cl = view ()->current_layer ();
update_edit_layer (cl);
}
}
void
ShapeEditService::config_recent_for_layer (const db::LayerProperties &lp, int cv_index)
{
if (lp.is_null ()) {
return;
}
#if defined(HAVE_QT)
lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages ();
if (!eo_pages) {
return;
}
for (std::vector<lay::EditorOptionsPage *>::const_iterator op = eo_pages->pages ().begin (); op != eo_pages->pages ().end (); ++op) {
if ((*op)->plugin_declaration () == plugin_declaration ()) {
(*op)->config_recent_for_layer (dispatcher (), lp, cv_index);
}
}
#endif
}
void
ShapeEditService::get_edit_layer ()
{
lay::LayerPropertiesConstIterator cl = view ()->current_layer ();
if (cl.is_null ()) {
throw tl::Exception (tl::to_string (tr ("Please select a layer first")));
} else if (! cl->valid (true)) {
throw tl::Exception (tl::to_string (tr ("The selected layer is not valid")));
}
#if defined(HAVE_QT)
if (! cl->visible (true)) {
lay::TipDialog td (QApplication::activeWindow (),
tl::to_string (tr ("You are now drawing on a hidden layer. The result won't be visible.")),
"drawing-on-invisible-layer");
td.exec_dialog ();
}
#endif
int cv_index = cl->cellview_index ();
const lay::CellView &cv = view ()->cellview (cv_index);
if (cv_index < 0 || ! cv.is_valid ()) {
throw tl::Exception (tl::to_string (tr ("Please select a cell first")));
}
int layer = cl->layer_index ();
if (layer < 0 || ! cv->layout ().is_valid_layer ((unsigned int) layer)) {
if (cl->has_children ()) {
throw tl::Exception (tl::to_string (tr ("Please select a valid drawing layer first")));
} else {
// create this layer now
const lay::ParsedLayerSource &source = cl->source (true /*real*/);
db::LayerProperties db_lp = source.layer_props ();
cv->layout ().insert_layer (db_lp);
// update the layer index inside the layer view
cl->realize_source ();
// Hint: we could have taken the new index from insert_layer, but this
// is a nice test:
layer = cl->layer_index ();
tl_assert (layer >= 0);
}
}
if (cv.cell ()->is_proxy ()) {
throw tl::Exception (tl::to_string (tr ("Cannot put a shape into a PCell or library cell")));
}
m_layer = (unsigned int) layer;
m_cv_index = (unsigned int) cv_index;
m_trans = (cl->trans ().front () * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ()).inverted ();
mp_layout = &(cv->layout ());
mp_cell = cv.cell ();
// fetches the last configuration for the given layer
view ()->set_active_cellview_index (cv_index);
}
void
ShapeEditService::change_edit_layer (const db::LayerProperties &lp)
{
if (! mp_layout) {
return;
}
int layer = mp_layout->get_layer_maybe (lp);
if (layer < 0) {
layer = mp_layout->insert_layer (lp);
}
m_layer = (unsigned int) layer;
edt::set_or_request_current_layer (view (), lp, m_cv_index);
// fetches the last configuration for the given layer
view ()->set_active_cellview_index (m_cv_index);
config_recent_for_layer (lp, m_cv_index);
}
void
ShapeEditService::set_layer (const db::LayerProperties &lp, unsigned int cv_index)
{
const lay::CellView &cv = view ()->cellview (cv_index);
if (! cv.is_valid ()) {
return;
}
int layer = cv->layout ().get_layer_maybe (lp);
if (layer < 0) {
layer = cv->layout ().insert_layer (lp);
}
m_layer = (unsigned int) layer;
m_cv_index = cv_index;
mp_layout = &(cv->layout ());
mp_cell = cv.cell ();
m_update_edit_layer_enabled = false;
try {
auto cl = view ()->find_layer (cv_index, lp);
if (! cl.is_null ()) {
view ()->set_current_layer (cl);
m_trans = (cl->trans ().front () * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ()).inverted ();
} else {
m_trans = (db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ()).inverted ();
}
m_update_edit_layer_enabled = true;
} catch (...) {
m_update_edit_layer_enabled = true;
throw;
}
}
void
ShapeEditService::update_edit_layer (const lay::LayerPropertiesConstIterator &cl)
{
if (! m_update_edit_layer_enabled) {
return;
}
if (cl.is_null () || cl->has_children ()) {
return;
}
int cv_index = cl->cellview_index ();
const lay::CellView &cv = view ()->cellview (cv_index);
if (cv_index < 0 || ! cv.is_valid ()) {
return;
}
view ()->set_active_cellview_index (cv_index);
const lay::ParsedLayerSource &source = cl->source (true /*real*/);
int layer = cl->layer_index ();
db::LayerProperties db_lp = source.layer_props ();
if (! editing ()) {
if (layer < 0 || ! cv->layout ().is_valid_layer ((unsigned int) layer)) {
config_recent_for_layer (db_lp, cv_index);
} else {
config_recent_for_layer (cv->layout ().get_properties ((unsigned int) layer), cv_index);
}
} else {
if (layer < 0 || ! cv->layout ().is_valid_layer ((unsigned int) layer)) {
// create this layer now
cv->layout ().insert_layer (db_lp);
// update the layer index inside the layer view
cl->realize_source ();
// Hint: we could have taken the new index from insert_layer, but this
// is a nice test:
layer = cl->layer_index ();
tl_assert (layer >= 0);
}
m_layer = (unsigned int) layer;
m_cv_index = (unsigned int) cv_index;
m_trans = (cl->trans ().front () * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ()).inverted ();
mp_layout = &(cv->layout ());
mp_cell = cv.cell ();
// fetches the last configuration for the given layer
config_recent_for_layer (cv->layout ().get_properties ((unsigned int) layer), cv_index);
current_layer_changed ();
}
}
void
ShapeEditService::tap (const db::DPoint &initial)
{
if (editing ()) {
get_edit_layer ();
} else {
begin_edit (initial);
}
}
/**
* @brief Deliver a good interpolation between two points m and p
*
* This method uses an intermediate point o to determine the edge that is emerged from point m.
* An edge is searched that emerges from p and intersects with the m->o edge in a way that the intersection
* point is closest to o.
*
* This method returns the intersection point ("new o") and a flag if the search was successful (.first of return value).
*/
std::pair <bool, db::DPoint>
ShapeEditService::interpolate (const db::DPoint &m, const db::DPoint &o, const db::DPoint &p) const
{
if (fabs (m.x () - o.x ()) < 1e-6 && fabs (m.y () - o.y ()) < 1e-6) {
return std::pair <bool, db::DPoint> (false, db::DPoint ());
}
std::vector <db::DVector> delta;
delta.reserve (4);
delta.push_back (db::DVector (1.0, 0.0));
delta.push_back (db::DVector (0.0, 1.0));
if (connect_ac () == lay::AC_Diagonal) {
delta.push_back (db::DVector (1.0, -1.0));
delta.push_back (db::DVector (1.0, 1.0));
}
bool c_set = false;
db::DPoint c;
for (std::vector <db::DVector>::const_iterator d = delta.begin (); d != delta.end (); ++d) {
std::pair <bool, db::DPoint> ip = db::DEdge (m, o).cut_point (db::DEdge (p - *d, p));
if (ip.first && (! c_set || o.sq_distance (ip.second) < o.sq_distance (c))) {
c = ip.second;
c_set = true;
}
}
return std::make_pair (c_set, c);
}
void
ShapeEditService::do_mouse_move_inactive (const db::DPoint &p)
{
// display the next (snapped) position where editing would start
db::DPoint pp = snap (p);
std::string pos = std::string ("x: ") + tl::micron_to_string (pp.x ()) +
std::string (" y: ") + tl::micron_to_string (pp.y ());
view ()->message (pos);
}
void
ShapeEditService::deliver_shape (const db::Polygon &poly)
{
if (m_combine_mode == CM_Add) {
db::Transaction transaction (manager (), tl::to_string (tr ("Create polygon")));
cell ().shapes (layer ()).insert (poly);
} else {
std::vector<db::Shape> shapes;
std::vector<db::Polygon> result;
std::vector<db::Polygon> input;
input.push_back (poly);
std::vector<db::Polygon> input_left;
if (m_combine_mode == CM_Diff) {
input_left = input;
}
db::EdgeProcessor ep;
bool any = false;
db::ShapeIterator s = cell ().shapes (layer ()).begin_touching (poly.box (), db::ShapeIterator::Polygons | db::ShapeIterator::Paths | db::ShapeIterator::Boxes);
while (! s.at_end ()) {
std::vector<db::Polygon> subject;
subject.push_back (db::Polygon ());
s->polygon (subject.back ());
if (db::interact_pp (poly, subject.back ())) {
any = true;
if (m_combine_mode == CM_Merge) {
ep.boolean (subject, input, result, db::BooleanOp::Or);
input = result;
input_left.clear ();
input_left.swap (result);
} else if (m_combine_mode == CM_Erase) {
ep.boolean (subject, input, result, db::BooleanOp::ANotB);
} else if (m_combine_mode == CM_Mask) {
ep.boolean (subject, input, result, db::BooleanOp::And);
} else if (m_combine_mode == CM_Diff) {
ep.boolean (subject, input, result, db::BooleanOp::ANotB);
std::vector<db::Polygon> l;
ep.boolean (input_left, subject, l, db::BooleanOp::ANotB);
l.swap (input_left);
}
shapes.push_back (*s);
}
++s;
}
// If nothing was found, simply pass the input to the result
if (! any && (m_combine_mode == CM_Merge || m_combine_mode == CM_Diff)) {
result = input;
}
db::Transaction transaction (manager (), tl::to_string (tr ("Combine shape with background")));
// Erase existing shapes
for (std::vector<db::Shape>::const_iterator s = shapes.begin (); s != shapes.end (); ++s) {
cell ().shapes (layer ()).erase_shape (*s);
}
// Add new shapes
for (std::vector<db::Polygon>::const_iterator p = result.begin (); p != result.end (); ++p) {
cell ().shapes (layer ()).insert (*p);
}
for (std::vector<db::Polygon>::const_iterator p = input_left.begin (); p != input_left.end (); ++p) {
cell ().shapes (layer ()).insert (*p);
}
}
}
void
ShapeEditService::deliver_shape (const db::Path &path)
{
if (m_combine_mode == CM_Add) {
db::Transaction transaction (manager (), tl::to_string (tr ("Create path")));
cell ().shapes (layer ()).insert (path);
} else {
deliver_shape (path.polygon ());
}
}
void
ShapeEditService::deliver_shape (const db::Box &box)
{
if (m_combine_mode == CM_Add) {
db::Transaction transaction (manager (), tl::to_string (tr ("Create box")));
cell ().shapes (layer ()).insert (box);
} else {
deliver_shape (db::Polygon (box));
}
}
void
ShapeEditService::deliver_shape (const db::Point &point)
{
if (m_combine_mode == CM_Add) {
db::Transaction transaction (manager (), tl::to_string (tr ("Create point")));
cell ().shapes (layer ()).insert (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 &);
} // namespace edt

View File

@ -0,0 +1,95 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtShapeService
#define HDR_edtShapeService
#include "edtService.h"
#include "edtEditorHooks.h"
namespace edt
{
/**
* @brief Implementation of the edt::Service for generic shape editing
*/
class ShapeEditService
: public edt::Service
{
public:
ShapeEditService (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIterator::flags_type shape_types);
protected:
void get_edit_layer ();
void change_edit_layer (const db::LayerProperties &lp);
const db::VCplxTrans &trans () const { return m_trans; }
unsigned int layer () const { return m_layer; }
unsigned int cv_index () const { return m_cv_index; }
db::Cell &cell () const { return *mp_cell; }
db::Layout &layout () const { return *mp_layout; }
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual void tap (const db::DPoint &initial);
virtual bool configure (const std::string &name, const std::string &value);
virtual void activated ();
protected:
std::pair <bool, db::DPoint> interpolate (const db::DPoint &m, const db::DPoint &o, const db::DPoint &p) const;
void deliver_shape (const db::Polygon &poly);
void deliver_shape (const db::Path &path);
void deliver_shape (const db::Box &box);
void deliver_shape (const db::Point &point);
void set_layer (const db::LayerProperties &lp, unsigned int cv_index);
void open_editor_hooks ();
template <class Shape>
void deliver_shape_to_hooks (const Shape &shape);
void close_editor_hooks (bool with_commit);
combine_mode_type combine_mode () const { return m_combine_mode; }
void config_recent_for_layer (const db::LayerProperties &lp, int cv_index);
const tl::weak_collection<edt::EditorHooks> &editor_hooks ()
{
return m_editor_hooks;
}
virtual void current_layer_changed () { }
private:
db::VCplxTrans m_trans;
unsigned int m_layer;
unsigned int m_cv_index;
db::Cell *mp_cell;
db::Layout *mp_layout;
combine_mode_type m_combine_mode;
tl::weak_collection<edt::EditorHooks> m_editor_hooks;
bool m_update_edit_layer_enabled;
void update_edit_layer (const lay::LayerPropertiesConstIterator &iter);
};
}
#endif

View File

@ -0,0 +1,249 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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 "edtTextService.h"
#include "layLayoutViewBase.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.h"
# include "layTipDialog.h"
#endif
namespace edt
{
// -----------------------------------------------------------------------------
// TextService implementation
TextService::TextService (db::Manager *manager, lay::LayoutViewBase *view)
: ShapeEditService (manager, view, db::ShapeIterator::Texts),
m_rot (0)
{
// .. nothing yet ..
}
TextService::~TextService ()
{
// .. nothing yet ..
}
#if defined(HAVE_QT)
std::vector<lay::PropertiesPage *>
TextService::properties_pages (db::Manager *manager, QWidget *parent)
{
std::vector<lay::PropertiesPage *> pages;
pages.push_back (new edt::TextPropertiesPage (this, manager, parent));
return pages;
}
#endif
void
TextService::do_begin_edit (const db::DPoint &p)
{
get_edit_layer ();
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*/);
set_edit_marker (marker);
update_marker ();
}
void
TextService::update_marker ()
{
lay::DMarker *marker = dynamic_cast<lay::DMarker *> (edit_marker ());
if (marker) {
marker->set (m_text);
std::string pos = std::string ("x: ") +
tl::micron_to_string (m_text.trans ().disp ().x ()) +
std::string (" y: ") +
tl::micron_to_string (m_text.trans ().disp ().y ());
if (m_text.trans ().rot () != 0) {
pos += std::string (" ") + ((const db::DFTrans &) m_text.trans ()).to_string ();
}
view ()->message (pos);
}
// 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
TextService::do_activated ()
{
m_rot = 0;
return true; // start editing immediately
}
void
TextService::do_mouse_move_inactive (const db::DPoint &p)
{
lay::PointSnapToObjectResult snap_details = snap2_details (p);
mouse_cursor_from_snap_details (snap_details);
}
void
TextService::do_mouse_move (const db::DPoint &p)
{
do_mouse_move_inactive (p);
set_cursor (lay::Cursor::cross);
m_text.trans (db::DTrans (m_rot, snap2 (p) - db::DPoint ()));
update_marker ();
}
void
TextService::do_mouse_transform (const db::DPoint &p, db::DFTrans trans)
{
m_rot = (db::DFTrans (m_rot) * trans).rot ();
m_text.trans (db::DTrans (m_rot, p - db::DPoint ()));
update_marker ();
}
bool
TextService::do_mouse_click (const db::DPoint &p)
{
do_mouse_move (p);
return true;
}
db::Text
TextService::get_text () const
{
db::Point p_dbu = trans () * (db::DPoint () + m_text.trans ().disp ());
return db::Text (m_text.string (), db::Trans (m_text.trans ().rot (), p_dbu - db::Point ()), db::coord_traits<db::Coord>::rounded (trans ().ctrans (m_text.size ())), db::NoFont, m_text.halign (), m_text.valign ());
}
void
TextService::do_finish_edit ()
{
get_edit_layer ();
if (manager ()) {
manager ()->transaction (tl::to_string (tr ("Create text")));
}
cell ().shapes (layer ()).insert (get_text ());
if (manager ()) {
manager ()->commit ();
}
commit_recent ();
#if defined(HAVE_QT)
if (! view ()->text_visible ()) {
lay::TipDialog td (QApplication::activeWindow (),
tl::to_string (tr ("A text object is created but texts are disabled for drawing and are not visible. Do you want to enable drawing of texts?\n\nChoose \"Yes\" to enable text drawing now.")),
"text-created-but-not-visible",
lay::TipDialog::yesno_buttons);
lay::TipDialog::button_type button = lay::TipDialog::null_button;
td.exec_dialog (button);
if (button == lay::TipDialog::yes_button) {
view ()->text_visible (true);
}
}
#endif
close_editor_hooks (true);
}
void
TextService::do_cancel_edit ()
{
close_editor_hooks (false);
}
bool
TextService::selection_applies (const lay::ObjectInstPath &sel) const
{
return !sel.is_cell_inst () && sel.shape ().is_text ();
}
bool
TextService::configure (const std::string &name, const std::string &value)
{
if (name == cfg_edit_text_size) {
double size (0);
tl::from_string (value, size);
if (m_text.size () != size) {
m_text.size (size);
update_marker ();
}
return true; // taken
}
if (name == cfg_edit_text_halign) {
db::HAlign ha = db::HAlignLeft;
HAlignConverter hac;
hac.from_string (value, ha);
if (m_text.halign () != ha) {
m_text.halign (ha);
update_marker ();
}
return true; // taken
}
if (name == cfg_edit_text_valign) {
db::VAlign va = db::VAlignBottom;
VAlignConverter vac;
vac.from_string (value, va);
if (m_text.valign () != va) {
m_text.valign (va);
update_marker ();
}
return true; // taken
}
if (name == cfg_edit_text_string) {
if (m_text.string () != value) {
m_text.string (value);
update_marker ();
}
return true; // taken
}
return ShapeEditService::configure (name, value);
}
} // namespace edt

View File

@ -0,0 +1,69 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 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_edtTextService
#define HDR_edtTextService
#include "edtShapeService.h"
namespace edt
{
/**
* @brief Implementation of edt::Service for text editing
*/
class TextService
: public ShapeEditService
{
public:
TextService (db::Manager *manager, lay::LayoutViewBase *view);
~TextService ();
#if defined(HAVE_QT)
virtual std::vector<lay::PropertiesPage *> properties_pages (db::Manager *manager, QWidget *parent);
#endif
virtual void do_begin_edit (const db::DPoint &p);
virtual void do_mouse_transform (const db::DPoint &p, db::DFTrans trans);
virtual void do_mouse_move (const db::DPoint &p);
virtual void do_mouse_move_inactive (const db::DPoint &p);
virtual bool do_mouse_click (const db::DPoint &p);
virtual void do_finish_edit ();
virtual void do_cancel_edit ();
virtual bool do_activated ();
virtual bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
virtual bool configure (const std::string &name, const std::string &value);
private:
db::DText m_text;
unsigned int m_rot;
void update_marker ();
db::Text get_text () const;
};
}
#endif