Merge branch 'master' into devel

This commit is contained in:
Matthias Koefferlein 2025-08-31 21:53:43 +02:00
commit 53a7414757
57 changed files with 5656 additions and 2824 deletions

View File

@ -112,6 +112,7 @@ SOURCES = \
dbUserObject.cc \
dbUtils.cc \
dbVector.cc \
dbVia.cc \
dbWriter.cc \
dbWriterTools.cc \
dbVariableWidthPath.cc \
@ -239,7 +240,8 @@ SOURCES = \
dbNetShape.cc \
dbShapeCollection.cc \
gsiDeclDbShapeCollection.cc \
dbShapeCollectionUtils.cc
dbShapeCollectionUtils.cc \
gsiDeclDbVia.cc
HEADERS = \
dbArray.h \
@ -354,6 +356,7 @@ HEADERS = \
dbUserObject.h \
dbUtils.h \
dbVector.h \
dbVia.h \
dbWriter.h \
dbWriterTools.h \
dbGlyphs.h \

View File

@ -777,6 +777,7 @@ Cell::get_pcell_parameters (const instance_type &ref) const
Cell::instance_type
Cell::change_pcell_parameters (const instance_type &ref, const std::vector<tl::Variant> &new_parameters)
{
tl_assert (mp_layout != 0);
cell_index_type new_cell_index = mp_layout->get_pcell_variant_cell (ref.cell_index (), new_parameters);
if (new_cell_index != ref.cell_index ()) {
@ -790,6 +791,61 @@ Cell::change_pcell_parameters (const instance_type &ref, const std::vector<tl::V
}
}
Cell::instance_type
Cell::change_pcell_parameters (const instance_type &ref, const std::map<std::string, tl::Variant> &map)
{
tl_assert (mp_layout != 0);
const db::PCellDeclaration *pcd = pcell_declaration_of_inst (ref);
if (! pcd) {
return Cell::instance_type ();
}
const std::vector<db::PCellParameterDeclaration> &pcp = pcd->parameter_declarations ();
std::vector<tl::Variant> p = get_pcell_parameters (ref);
bool needs_update = false;
for (size_t i = 0; i < pcp.size () && i < p.size (); ++i) {
std::map<std::string, tl::Variant>::const_iterator pm = map.find (pcp [i].get_name ());
if (pm != map.end () && p [i] != pm->second) {
p [i] = pm->second;
needs_update = true;
}
}
if (needs_update) {
return change_pcell_parameters (ref, p);
} else {
return ref;
}
}
const db::PCellDeclaration *
Cell::pcell_declaration_of_inst (const db::Cell::instance_type &ref) const
{
tl_assert (mp_layout != 0);
return mp_layout->cell (ref.cell_index ()).pcell_declaration ();
}
const db::PCellDeclaration *
Cell::pcell_declaration () const
{
tl_assert (mp_layout != 0);
std::pair<bool, db::pcell_id_type> pc = mp_layout->is_pcell_instance (cell_index ());
if (pc.first) {
db::Library *lib = mp_layout->defining_library (cell_index ()).first;
if (lib) {
return lib->layout ().pcell_declaration (pc.second);
} else {
return mp_layout->pcell_declaration (pc.second);
}
} else {
return 0;
}
}
void
Cell::sort_inst_tree (bool force)
{

View File

@ -57,6 +57,7 @@ class Library;
class ImportLayerMapping;
class CellMapping;
class LayerMapping;
class PCellDeclaration;
/**
* @brief The cell object
@ -473,6 +474,27 @@ public:
*/
instance_type change_pcell_parameters (const instance_type &ref, const std::vector<tl::Variant> &new_parameters);
/**
* @brief Changes the PCell parameters of a PCell instance using a dict
*
* @return A reference to the new instance. The original reference may be invalid.
*/
instance_type change_pcell_parameters (const instance_type &ref, const std::map<std::string, tl::Variant> &new_parameters);
/**
* @brief Gets the PCellDeclaration object of the instance if the instance is a PCell instance
*
* If the instance is not a PCell instance, 0 is returned.
*/
const db::PCellDeclaration *pcell_declaration_of_inst (const db::Cell::instance_type &ref) const;
/**
* @brief Gets the PCellDeclaration object of the cell is the cell is a PCell variant
*
* If the cell is not a PCell variant, 0 is returned.
*/
const db::PCellDeclaration *pcell_declaration () const;
/**
* @brief The cell index accessor method
*

View File

@ -122,7 +122,9 @@ Manager::transaction (const std::string &description, transaction_id_t join_with
tl_assert (! m_replay);
if (! m_transactions.empty () && reinterpret_cast<transaction_id_t> (& m_transactions.back ()) == join_with) {
m_transactions.back ().second = description;
if (! description.empty ()) {
m_transactions.back ().second = description;
}
} else {
// delete all following transactions and add a new one
erase_transactions (m_current, m_transactions.end ());

View File

@ -28,6 +28,7 @@
#include "gsiObject.h"
#include "dbLayout.h"
#include "dbVia.h"
#include "tlVariant.h"
#include "tlObject.h"
#include "tlOptional.h"
@ -672,6 +673,16 @@ public:
return std::string ();
}
/**
* @brief Returns the description text of the PCell
*
* The description text is a for human interpretation only. By default, the name will be returned.
*/
virtual std::string get_description () const
{
return m_name;
}
/**
* @brief Returns true, if the PCell can be created from the given shape on the given layer
*
@ -717,6 +728,25 @@ public:
return false;
}
/**
* @brief Returns the via types the PCell can provide
*
* This method returns a list of via types the PCell can support.
* If this list is non-empty, the PCell will be used to implement
* vias of one of the given type.
*
* Such a PCell needs to support the following (maybe hidden) parameters
* * "via" (string): the name of the via type
* * "w_bottom" (float): the bottom wire width in um or 0 if not specified
* * "h_bottom" (float): the bottom wire height in um or 0 if not specified
* * "w_top" (float): the top wire width in um or 0 if not specified
* * "h_top" (float): the top wire height in um or 0 if not specified
*/
virtual std::vector<ViaType> via_types () const
{
return std::vector<ViaType> ();
}
/**
* @brief Gets the Layout object the PCell is registered inside or NULL if it is not registered
*/

81
src/db/db/dbVia.cc Normal file
View File

@ -0,0 +1,81 @@
/*
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 "dbVia.h"
#include "dbLibraryManager.h"
#include "dbPCellDeclaration.h"
namespace db
{
void
ViaType::init ()
{
wbmin = 0.0;
wbmax = -1.0;
hbmin = 0.0;
hbmax = -1.0;
wtmin = 0.0;
wtmax = -1.0;
htmin = 0.0;
htmax = -1.0;
bottom_wired = true;
bottom_grid = 0.0;
top_wired = true;
top_grid = 0.0;
}
// ---------------------------------------------------------------------------------------
std::vector<SelectedViaDefinition>
find_via_definitions_for (const std::string &technology, const db::LayerProperties &layer, int dir)
{
std::vector<SelectedViaDefinition> via_defs;
// Find vias with corresponding top an bottom layers
for (auto l = db::LibraryManager::instance ().begin (); l != db::LibraryManager::instance ().end (); ++l) {
db::Library *lib = db::LibraryManager::instance ().lib (l->second);
if (lib->for_technologies () && ! lib->is_for_technology (technology)) {
continue;
}
for (auto pc = lib->layout ().begin_pcells (); pc != lib->layout ().end_pcells (); ++pc) {
const db::PCellDeclaration *pcell = lib->layout ().pcell_declaration (pc->second);
auto via_types = pcell->via_types ();
for (auto vt = via_types.begin (); vt != via_types.end (); ++vt) {
if ((dir >= 0 && vt->bottom.log_equal (layer) && vt->bottom_wired) ||
(dir <= 0 && vt->top.log_equal (layer) && vt->top_wired)) {
via_defs.push_back (SelectedViaDefinition (lib, pc->second, *vt));
}
}
}
}
return via_defs;
}
}

216
src/db/db/dbVia.h Normal file
View File

@ -0,0 +1,216 @@
/*
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_dbVia
#define HDR_dbVia
#include "dbCommon.h"
#include "dbLayerProperties.h"
#include "dbLibrary.h"
#include <string>
namespace db
{
/**
* @brief A descriptor for a via
*
* This object describes one flavor of a via provided by via PCells.
*/
class DB_PUBLIC ViaType
{
public:
/**
* @brief The default constructor
*/
ViaType ()
{
init ();
}
/**
* @brief The constructor with a name
*/
ViaType (const std::string &_name)
: name (_name)
{
init ();
}
/**
* @brief The constructor with a name and description
*/
ViaType (const std::string &_name, const std::string &_description)
: name (_name), description (_description)
{
init ();
}
/**
* @brief The minimum width of the bottom layer of the via
*/
double wbmin;
/**
* @brief The maximum width of the bottom layer of the via
*
* A negative value means "not specified" or "infinite".
*/
double wbmax;
/**
* @brief The minimum height of the bottom layer of the via
*/
double hbmin;
/**
* @brief The maximum height of the bottom layer of the via
*
* A negative value means "not specified" or "infinite".
*/
double hbmax;
/**
* @brief The minimum width of the top layer of the via
*/
double wtmin;
/**
* @brief The maximum width of the top layer of the via
*
* A negative value means "not specified" or "infinite".
*/
double wtmax;
/**
* @brief The minimum height of the top layer of the via
*/
double htmin;
/**
* @brief The maximum height of the top layer of the via
*
* A negative value means "not specified" or "infinite".
*/
double htmax;
/**
* @brief The bottom layer
*/
db::LayerProperties bottom;
/**
* @brief A flag indicating whether the bottom layer is wired
*
* For example, sheet layers such as diffusion are not wired.
* By default, layers are wired.
*/
bool bottom_wired;
/**
* @brief The grid of the bottom layer
*
* Via dimensions are rounded to this grid on the bottom layer, if non-zero.
*/
double bottom_grid;
/**
* @brief The cut layer
*/
db::LayerProperties cut;
/**
* @brief The top layer
*/
db::LayerProperties top;
/**
* @brief A flag indicating whether the top layer is wired
*
* For example, sheet layers such as diffusion are not wired.
* By default, layers are wired.
*/
bool top_wired;
/**
* @brief The grid of the top layer
*
* Via dimensions are rounded to this grid on the top layer, if non-zero.
*/
double top_grid;
/**
* @brief The name of the via
*
* The name is a formal name to identify the via.
*/
std::string name;
/**
* @brief The description of the via
*
* This is a human-readable description. This attribute is optional.
*/
std::string description;
private:
void init ();
};
/**
* @brief Provides a via definition that is selected by "find_via_definitions_for"
*/
struct SelectedViaDefinition
{
SelectedViaDefinition ()
: lib (0), pcell (0)
{ }
SelectedViaDefinition (db::Library *_lib, db::pcell_id_type _pcell, const db::ViaType &_via_type)
: lib (_lib), pcell (_pcell), via_type (_via_type)
{ }
/**
* @brief The library from which the via is taken
*/
db::Library *lib;
/**
* @brief The PCell from which the via is taken
*/
db::pcell_id_type pcell;
/**
* @brief The selected via type
*/
db::ViaType via_type;
};
DB_PUBLIC std::vector<SelectedViaDefinition>
find_via_definitions_for (const std::string &technology, const db::LayerProperties &layer, int dir);
}
#endif

View File

@ -3321,7 +3321,8 @@ Class<db::Cell> decl_Cell ("db", "Cell",
"\n"
"This method has been introduced in version 0.24.\n"
) +
gsi::method ("change_pcell_parameters", &db::Cell::change_pcell_parameters, gsi::arg ("instance"), gsi::arg ("parameters"),
gsi::method ("change_pcell_parameters", static_cast<db::Cell::instance_type (db::Cell::*) (const db::Cell::instance_type &ref, const std::vector<tl::Variant> &new_parameters)> (&db::Cell::change_pcell_parameters),
gsi::arg ("instance"), gsi::arg ("parameters"),
"@brief Changes the parameters for an individual PCell instance\n"
"@return The new instance (the old may be invalid)\n"
"If necessary, this method creates a new variant and replaces the given instance\n"

View File

@ -287,6 +287,8 @@ Class<db::PCellDeclaration> decl_PCellDeclaration_Native ("db", "PCellDeclaratio
gsi::method ("parameters_from_shape", &db::PCellDeclaration::parameters_from_shape, gsi::arg ("layout"), gsi::arg ("shape"), gsi::arg ("layer")) +
gsi::method ("transformation_from_shape", &db::PCellDeclaration::transformation_from_shape, gsi::arg ("layout"), gsi::arg ("shape"), gsi::arg ("layer")) +
gsi::method ("wants_lazy_evaluation", &db::PCellDeclaration::wants_lazy_evaluation) +
gsi::method ("via_types", &db::PCellDeclaration::via_types) +
gsi::method ("description", &db::PCellDeclaration::get_description) +
gsi::method ("display_text", &db::PCellDeclaration::get_display_name, gsi::arg ("parameters")) +
gsi::method ("layout", &db::PCellDeclaration::layout,
"@brief Gets the Layout object the PCell is registered in or nil if it is not registered yet.\n"
@ -537,6 +539,34 @@ public:
}
}
std::string get_description_fb () const
{
return db::PCellDeclaration::get_description ();
}
virtual std::string get_description () const
{
if (cb_get_description.can_issue ()) {
return cb_get_description.issue<db::PCellDeclaration, std::string> (&db::PCellDeclaration::get_description);
} else {
return db::PCellDeclaration::get_description ();
}
}
std::vector<db::ViaType> via_types_fb () const
{
return db::PCellDeclaration::via_types ();
}
virtual std::vector<db::ViaType> via_types () const
{
if (cb_via_types.can_issue ()) {
return cb_via_types.issue<db::PCellDeclaration, std::vector<db::ViaType>> (&db::PCellDeclaration::via_types);
} else {
return db::PCellDeclaration::via_types ();
}
}
std::string get_display_name_fb (const db::pcell_parameters_type &parameters) const
{
return db::PCellDeclaration::get_display_name (parameters);
@ -561,6 +591,8 @@ public:
gsi::Callback cb_coerce_parameters;
gsi::Callback cb_callback;
gsi::Callback cb_get_display_name;
gsi::Callback cb_get_description;
gsi::Callback cb_via_types;
};
Class<PCellDeclarationImpl> decl_PCellDeclaration (decl_PCellDeclaration_Native, "db", "PCellDeclaration",
@ -573,6 +605,8 @@ Class<PCellDeclarationImpl> decl_PCellDeclaration (decl_PCellDeclaration_Native,
gsi::method ("transformation_from_shape", &PCellDeclarationImpl::transformation_from_shape_fb, "@hide") +
gsi::method ("display_text", &PCellDeclarationImpl::get_display_name_fb, "@hide") +
gsi::method ("wants_lazy_evaluation", &PCellDeclarationImpl::wants_lazy_evaluation_fb, "@hide") +
gsi::method ("description", &PCellDeclarationImpl::get_description_fb, "@hide") +
gsi::method ("via_types", &PCellDeclarationImpl::via_types_fb, "@hide") +
gsi::callback ("get_layers", &PCellDeclarationImpl::get_layer_declarations_impl, &PCellDeclarationImpl::cb_get_layer_declarations, gsi::arg ("parameters"),
"@brief Returns a list of layer declarations\n"
"Reimplement this method to return a list of layers this PCell wants to create.\n"
@ -670,6 +704,29 @@ Class<PCellDeclarationImpl> decl_PCellDeclaration (decl_PCellDeclaration_Native,
"\n"
"This method has been added in version 0.27.6.\n"
) +
gsi::callback ("description", &PCellDeclarationImpl::get_description, &PCellDeclarationImpl::cb_get_description,
"@brief Gets the PCell description\n"
"The description string is a text that gives a human-readable description of the PCell. By default, the "
"PCell name is used for the description.\n"
"\n"
"This method has been added in version 0.30.4.\n"
) +
gsi::callback ("via_types", &PCellDeclarationImpl::via_types, &PCellDeclarationImpl::cb_via_types,
"@brief Gets the via types the PCell supports - if it is a via PCell\n"
"If the returned list is non-empty, the PCell may be used as a via PCell. This method is supposed "
"to deliver a list of via types the PCell supports. If the PCell supports vias, it is expected to "
"accept the following parameters:\n"
"\n"
"@ul\n"
"@li 'via' (string): the name of the via type requested @/li\n"
"@li 'w_bottom' (float): the bottom wire width in um or 0 if not specified @/li\n"
"@li 'h_bottom' (float): the bottom wire height in um or 0 if not specified @/li\n"
"@li 'w_top' (float): the top wire width in um or 0 if not specified @/li\n"
"@li 'h_top' (float): the top wire height in um or 0 if not specified @/li\n"
"@/ul\n"
"\n"
"This method has been added in version 0.30.4.\n"
) +
gsi::callback ("display_text", &PCellDeclarationImpl::get_display_name, &PCellDeclarationImpl::cb_get_display_name, gsi::arg ("parameters"),
"@brief Returns the display text for this PCell given a certain parameter set\n"
"Reimplement this method to create a distinct display text for a PCell variant with \n"

170
src/db/db/gsiDeclDbVia.cc Normal file
View File

@ -0,0 +1,170 @@
/*
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 "gsiDecl.h"
#include "dbVia.h"
namespace gsi
{
// TODO: this is generic. Move it to gsiDecl?
template <class T, class R, R (T::*member)>
struct getter_def
{
static const R &impl (const T *t)
{
return t->*member;
}
};
template <class T, class R, R (T::*member)>
struct setter_def
{
static void impl (T *t, const R &r)
{
t->*member = r;
}
};
template <class T, class R, R (T::*member)>
gsi::Methods make_getter_setter (const std::string &name, const std::string &doc)
{
return gsi::method_ext (name, &getter_def<T, R, member>::impl, doc) +
gsi::method_ext (name + "=", &setter_def<T, R, member>::impl, gsi::arg ("value"), doc);
}
static db::ViaType *new_via_type (const std::string &name, const std::string &description)
{
return new db::ViaType (name, description);
}
Class<db::ViaType> decl_dbViaType ("db", "ViaType",
gsi::constructor ("new", &new_via_type, gsi::arg ("name"), gsi::arg ("description", std::string ()),
"@brief Creates a new via type object with the given name and description."
) +
make_getter_setter<db::ViaType, std::string, &db::ViaType::name> ("name",
"@brief The formal name of the via type.\n"
"The name should be unique and identify the via type in the context of the "
"via declaration."
) +
make_getter_setter<db::ViaType, std::string, &db::ViaType::description> ("description",
"@brief The description of the via type.\n"
"The description is an optional free-style text that describes the via type for a human."
) +
make_getter_setter<db::ViaType, double, &db::ViaType::wbmin> ("wbmin",
"@brief The minimum bottom-layer width of the via.\n"
"This values specifies the minimum width of the bottom layer in micrometers. "
"The default is zero."
) +
#if 0 // TODO: not used currently
make_getter_setter<db::ViaType, double, &db::ViaType::wbmax> ("wbmax",
"@brief The maximum bottom-layer width of the via.\n"
"This values specifies the maximum width of the bottom layer in micrometers. "
"A negative value indicates that no specific upper limit is given. "
"The default is 'unspecified'."
) +
#endif
make_getter_setter<db::ViaType, double, &db::ViaType::wtmin> ("wtmin",
"@brief The minimum top-layer width of the via.\n"
"This values specifies the minimum width of the top layer in micrometers. "
"The default is zero."
) +
#if 0 // TODO: not used currently
make_getter_setter<db::ViaType, double, &db::ViaType::wtmax> ("wtmax",
"@brief The maximum top-layer width of the via.\n"
"This values specifies the maximum width of the top layer in micrometers. "
"A negative value indicates that no specific upper limit is given. "
"The default is 'unspecified'."
) +
#endif
make_getter_setter<db::ViaType, double, &db::ViaType::hbmin> ("hbmin",
"@brief The minimum bottom-layer height of the via.\n"
"This values specifies the minimum height of the bottom layer in micrometers. "
"The default is zero."
) +
#if 0 // TODO: not used currently
make_getter_setter<db::ViaType, double, &db::ViaType::hbmax> ("hbmax",
"@brief The maximum bottom-layer height of the via.\n"
"This values specifies the maximum height of the bottom layer in micrometers. "
"A negative value indicates that no specific upper limit is given. "
"The default is 'unspecified'."
) +
#endif
make_getter_setter<db::ViaType, double, &db::ViaType::htmin> ("htmin",
"@brief The minimum top-layer height of the via.\n"
"This values specifies the minimum height of the top layer in micrometers. "
"The default is zero."
) +
#if 0 // TODO: not used currently
make_getter_setter<db::ViaType, double, &db::ViaType::htmax> ("htmax",
"@brief The maximum top-layer height of the via.\n"
"This values specifies the maximum height of the top layer in micrometers. "
"A negative value indicates that no specific upper limit is given. "
"The default is 'unspecified'."
) +
#endif
make_getter_setter<db::ViaType, db::LayerProperties, &db::ViaType::bottom> ("bottom",
"@brief The bottom layer of the via.\n"
) +
make_getter_setter<db::ViaType, db::LayerProperties, &db::ViaType::cut> ("cut",
"@brief The cut layer of the via.\n"
) +
make_getter_setter<db::ViaType, db::LayerProperties, &db::ViaType::top> ("top",
"@brief The top layer of the via.\n"
) +
#if 0 // TODO: not used currently
make_getter_setter<db::ViaType, bool, &db::ViaType::bottom_wired> ("bottom_wired",
"@brief A flag indicating that the bottom layer is a wiring layer.\n"
"If false, the bottom layer is assume to be a sheet layer, such as diffusion. "
"In this case, changing the routing layer will not continue drawing a path. "
"If true (the default), drawing will continue on the bottom layer as a path."
) +
#endif
#if 0 // TODO: not used currently
make_getter_setter<db::ViaType, bool, &db::ViaType::top_wired> ("top_wired",
"@brief A flag indicating that the top layer is a wiring layer.\n"
"If false, the bottom layer is assume to be a sheet layer, such as diffusion. "
"In this case, changing the routing layer will not continue drawing a path. "
"If true (the default), drawing will continue on the bottom layer as a path."
) +
#endif
make_getter_setter<db::ViaType, double, &db::ViaType::bottom_grid> ("bottom_grid",
"@brief If non-zero, the bottom layer's dimensions will be rounded to this grid.\n"
) +
make_getter_setter<db::ViaType, double, &db::ViaType::top_grid> ("top_grid",
"@brief If non-zero, the top layer's dimensions will be rounded to this grid.\n"
),
"@brief Describes a via type\n"
"These objects are used by PCellDeclaration#via_types to specify the via types a "
"via PCell is able to provide.\n"
"\n"
"The basic parameters of a via type are bottom and top layers (the layers that are "
"connected by the via) and width and height. Width and height are the dimensions of the "
"core via area - that is the part where bottom and top layers overlap. The actual "
"layout may exceed these dimensions if different enclosure rules require so for example.\n"
"\n"
"This class has been introduced in version 0.30."
);
}

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 "dbLibraryManager.h"
#if defined(HAVE_QT)
# include "edtInstPropertiesPage.h"
# include "layBusy.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"
@ -211,6 +216,12 @@ MainService::menu_activated (const std::string &symbol)
cm_make_array ();
} else if (symbol == "edt::sel_make_cell_variants") {
cm_make_cell_variants ();
} else if (symbol == "edt::via") {
cm_via ();
} else if (symbol == "edt::via_up") {
cm_via_up ();
} else if (symbol == "edt::via_down") {
cm_via_down ();
}
}
@ -2439,7 +2450,49 @@ MainService::cm_tap ()
#endif
}
void
void
MainService::cm_via ()
{
via_impl (0);
}
void
MainService::cm_via_up ()
{
via_impl (1);
}
void
MainService::cm_via_down ()
{
via_impl (-1);
}
void
MainService::via_impl (int dir)
{
#if ! defined(HAVE_QT)
tl_assert (false); // see TODO
#endif
#if defined(HAVE_QT)
QWidget *view_widget = lay::widget_from_view (view ());
if (! view_widget) {
return;
}
#endif
if (! view ()->canvas ()->mouse_in_window ()) {
return;
}
edt::Service *es = dynamic_cast<edt::Service *> (view ()->canvas ()->active_service ());
if (es) {
es->via (dir);
}
}
void
MainService::cm_change_layer ()
{
tl_assert (view ()->is_editable ());

View File

@ -200,7 +200,22 @@ public:
*/
void cm_tap ();
/**
/**
* @brief Via operation (up or down)
*/
void cm_via ();
/**
* @brief Via operation (down)
*/
void cm_via_down ();
/**
* @brief Via operation (up)
*/
void cm_via_up ();
/**
* @brief "paste" operation
*/
virtual void paste ();
@ -242,6 +257,7 @@ private:
edt::MakeArrayOptionsDialog *mp_make_array_options_dialog;
#endif
void via_impl (int dir);
void boolean_op (int mode);
void check_no_guiding_shapes ();
void descend (bool make_new_top);

View File

@ -387,6 +387,7 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
delete mp_parameters_area;
}
mp_groups.clear ();
m_widgets.clear ();
m_icon_widgets.clear ();
m_all_widgets.clear ();

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"
@ -43,6 +48,7 @@
#if defined(HAVE_QT)
# include <QApplication>
# include <QLayout>
# include <QMessageBox>
#endif
namespace edt
@ -379,6 +385,11 @@ public:
menu_entries.push_back (lay::menu_item ("edt::sel_area_perimeter", "area_perimeter", "edit_menu.selection_menu.end", tl::to_string (tr ("Area and Perimeter"))));
menu_entries.push_back (lay::menu_item ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end_modes", tl::to_string (tr ("Combine{Select background combination mode}"))));
// Key binding only
menu_entries.push_back (lay::menu_item ("edt::via", "via:edit_mode", "@secrets.end", tl::to_string (tr ("Via")) + "(V)"));
menu_entries.push_back (lay::menu_item ("edt::via_up", "via_up:edit_mode", "@secrets.end", tl::to_string (tr ("Via up"))));
menu_entries.push_back (lay::menu_item ("edt::via_down", "via_down:edit_mode", "@secrets.end", tl::to_string (tr ("Via down"))));
}
bool configure (const std::string &name, const std::string &value)
@ -503,23 +514,6 @@ private:
static tl::RegisteredClass<lay::PluginDeclaration> config_decl_main (new edt::MainPluginDeclaration (tl::to_string (tr ("Instances and shapes"))), 4000, "edt::MainService");
void
commit_recent (lay::LayoutViewBase *view)
{
#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)->active ()) {
(*op)->commit_recent (view);
}
}
#endif
}
class PartialPluginDeclaration
: public PluginDeclarationBase
{

View File

@ -24,6 +24,7 @@
#ifndef HDR_edtPlugin
#define HDR_edtPlugin
#include "edtCommon.h"
#include "layPlugin.h"
#include <vector>
@ -57,11 +58,6 @@ namespace edt
bool points_enabled ();
bool texts_enabled ();
bool instances_enabled ();
/**
* @brief Commits the current configuration for the recently used configuration list
*/
void commit_recent (lay::LayoutViewBase *view);
}
#endif

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

View File

@ -141,7 +141,7 @@ lp_iter_from_string (lay::LayoutViewBase *view, const std::string &s)
db::LayerProperties lp;
tl::Extractor ex (s.c_str ());
lp.read (ex);
int cv_index = 0;
int cv_index = view->active_cellview_index ();
if (ex.test ("@")) {
ex.read (cv_index);
}
@ -365,40 +365,6 @@ RecentConfigurationPage::update_list (const std::list<std::vector<std::string> >
mp_tree_widget->header ()->resizeSections (QHeaderView::ResizeToContents);
}
static bool
set_or_request_current_layer (QWidget *parent, lay::LayoutViewBase *view, unsigned int cv_index, const db::LayerProperties &lp)
{
bool ok = view->set_current_layer (cv_index, lp);
if (ok) {
return true;
}
if (! view->control_panel ()) {
return false;
}
const lay::CellView &cv = view->cellview (cv_index);
if (! cv.is_valid ()) {
return false;
}
if (QMessageBox::question (parent, tr ("Create Layer"), tr ("Layer %1 does not exist yet. Create it now?").arg (tl::to_qstring (lp.to_string ()))) == QMessageBox::Yes) {
lay::LayerPropertiesNode lpn;
lpn.set_source (lay::ParsedLayerSource (lp, cv_index));
view->init_layer_properties (lpn);
view->transaction (tl::to_string (QObject::tr ("Create new layer")));
view->set_current_layer (lay::LayerPropertiesConstIterator (& view->insert_layer (view->end_layers (), lpn)));
view->commit ();
return true;
}
return false;
}
void
RecentConfigurationPage::item_clicked (QTreeWidgetItem *item)
{
@ -413,12 +379,12 @@ RecentConfigurationPage::item_clicked (QTreeWidgetItem *item)
db::LayerProperties lp;
tl::Extractor ex (v.c_str ());
lp.read (ex);
int cv_index = 0;
int cv_index = view ()->active_cellview_index ();
if (ex.test ("@")) {
ex.read (cv_index);
}
set_or_request_current_layer (item->treeWidget (), view (), cv_index, lp);
edt::set_or_request_current_layer (view (), lp, cv_index);
} else {
dispatcher ()->config_set (c->cfg_name, v);
@ -447,7 +413,7 @@ RecentConfigurationPage::commit_recent (lay::Dispatcher *root)
int li = view ()->current_layer ()->layer_index ();
if (cv.is_valid () && cv->layout ().is_valid_layer (li)) {
s = cv->layout ().get_properties (li).to_string ();
if (cv_index > 0) {
if (cv_index != view ()->active_cellview_index ()) {
s += "@" + tl::to_string (cv_index);
}
}
@ -482,6 +448,55 @@ RecentConfigurationPage::commit_recent (lay::Dispatcher *root)
update_list (stored_values);
}
void
RecentConfigurationPage::config_recent_for_layer (lay::Dispatcher *root, const db::LayerProperties &lp, int cv_index)
{
std::list<std::vector<std::string> > stored_values = get_stored_values ();
auto v = stored_values.begin ();
for ( ; v != stored_values.end (); ++v) {
bool match = false;
auto vv = v->begin ();
for (std::list<ConfigurationDescriptor>::const_iterator c = m_cfg.begin (); c != m_cfg.end () && ! match && vv != v->end (); ++c, ++vv) {
if (c->rendering == Layer) {
// "getting" a layer means making it current
db::LayerProperties lp_stored;
tl::Extractor ex (vv->c_str ());
lp_stored.read (ex);
int cv_index_stored = view ()->active_cellview_index ();
if (ex.test ("@")) {
ex.read (cv_index_stored);
}
match = (lp.log_equal (lp_stored) && (cv_index < 0 || cv_index_stored == cv_index));
}
}
if (match) {
break;
}
}
if (v != stored_values.end ()) {
auto vv = v->begin ();
for (std::list<ConfigurationDescriptor>::const_iterator c = m_cfg.begin (); c != m_cfg.end () && vv != v->end (); ++c, ++vv) {
if (c->rendering != Layer) {
root->config_set (c->cfg_name, *vv);
}
}
root->config_end ();
}
}
}
#endif

View File

@ -25,6 +25,7 @@
#ifndef HDR_edtRecentConfigurationPage
#define HDR_edtRecentConfigurationPage
#include "edtCommon.h"
#include "layEditorOptionsPage.h"
#include "tlObject.h"
#include "tlDeferredExecution.h"
@ -37,6 +38,11 @@ namespace lay
class LayoutViewBase;
}
namespace db
{
struct LayerProperties;
}
namespace edt
{
@ -47,7 +53,7 @@ class EditorOptionsPages;
/**
* @brief The base class for a object properties page
*/
class RecentConfigurationPage
class EDT_PUBLIC RecentConfigurationPage
: public lay::EditorOptionsPage
{
Q_OBJECT
@ -92,6 +98,7 @@ public:
virtual void apply (lay::Dispatcher * /*root*/) { }
virtual void setup (lay::Dispatcher * /*root*/) { }
virtual void commit_recent (lay::Dispatcher *root);
virtual void config_recent_for_layer (lay::Dispatcher *root, const db::LayerProperties &lp, int cv_index);
private slots:
void item_clicked (QTreeWidgetItem *item);

View File

@ -33,6 +33,9 @@
#include "layFinder.h"
#include "layLayoutView.h"
#include "laySnap.h"
#if defined(HAVE_QT)
# include "layEditorOptionsPages.h"
#endif
#include "tlProgress.h"
#include "tlTimer.h"
@ -46,7 +49,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIter
db::Object (manager),
mp_view (view),
mp_transient_marker (0),
m_editing (false), m_immediate (false),
m_mouse_in_view (false), m_editing (false), m_immediate (false),
m_selection_maybe_invalid (false),
m_cell_inst_service (false),
m_flags (flags),
@ -70,7 +73,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view)
db::Object (manager),
mp_view (view),
mp_transient_marker (0),
m_editing (false), m_immediate (false),
m_mouse_in_view (false), m_editing (false), m_immediate (false),
m_selection_maybe_invalid (false),
m_cell_inst_service (true),
m_flags (db::ShapeIterator::Nothing),
@ -859,6 +862,8 @@ Service::move_cancel ()
bool
Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
m_mouse_pos = p;
if (view ()->is_editable () && prio) {
if (m_editing || m_immediate) {
@ -925,6 +930,20 @@ Service::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio
return false;
}
bool
Service::leave_event (bool /*prio*/)
{
m_mouse_in_view = false;
return false;
}
bool
Service::enter_event (bool /*prio*/)
{
m_mouse_in_view = true;
return false;
}
bool
Service::mouse_double_click_event (const db::DPoint & /*p*/, unsigned int buttons, bool prio)
{
@ -1694,6 +1713,12 @@ Service::tap (const db::DPoint & /*initial*/)
// .. nothing here ..
}
void
Service::via (int)
{
// .. nothing here ..
}
void
Service::geometry_changing ()
{
@ -1969,6 +1994,23 @@ Service::handle_guiding_shape_changes (bool commit)
}
}
void
Service::commit_recent ()
{
#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)->commit_recent (view ());
}
}
#endif
}
// -------------------------------------------------------------
// Implementation of EditableSelectionIterator

View File

@ -336,6 +336,16 @@ public:
*/
virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio);
/**
* @brief Mouse leave event handler
*/
virtual bool leave_event (bool prio);
/**
* @brief Mouse enter event handler
*/
virtual bool enter_event (bool prio);
/**
* @brief Implements the key handler
*/
@ -361,6 +371,13 @@ public:
*/
virtual void tap (const db::DPoint &initial);
/**
* @brief Implements the via feature
*
* "dir" is 0 for up or down, -1 for down and +1 for up.
*/
virtual void via (int dir);
/**
* @brief Delete the selected rulers
*
@ -600,6 +617,26 @@ protected:
return m_editing;
}
bool top_level_sel () const
{
return m_top_level_sel;
}
bool mouse_in_view () const
{
return m_mouse_in_view;
}
const db::DPoint &mouse_pos () const
{
return m_mouse_pos;
}
/**
* @brief Commits the current configuration to the recent attributes list
*/
void commit_recent ();
/**
* @brief Point snapping with detailed return value
*/
@ -620,6 +657,12 @@ private:
// The marker representing the object to be edited
std::vector<lay::ViewObject *> m_edit_markers;
// The last mouse position
db::DPoint m_mouse_pos;
// A flag indicating whether the mouse is inside the view
bool m_mouse_in_view;
// True, if editing is in progress.
bool m_editing;

File diff suppressed because it is too large Load Diff

View File

@ -1,334 +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 ();
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 open_editor_hooks ();
template <class Shape>
void deliver_shape_to_hooks (const Shape &shape);
void close_editor_hooks (bool with_commit);
const tl::weak_collection<edt::EditorHooks> &editor_hooks ()
{
return m_editor_hooks;
}
virtual void current_layer_changed () { }
private:
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;
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 bool selection_applies (const lay::ObjectInstPath &sel) const;
protected:
bool configure (const std::string &name, const std::string &value);
void config_finalize ();
private:
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;
void update_marker ();
db::Path get_path () const;
void set_last_point (const db::DPoint &p);
};
/**
* @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,500 @@
/*
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 "layLayoutView.h"
#include "dbEdgeProcessor.h"
#include "dbPolygonTools.h"
#if defined(HAVE_QT)
# include "edtPropertiesPages.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);
if (editing ()) {
close_editor_hooks (false);
}
// fetches the last configuration for the given layer
view ()->set_active_cellview_index (m_cv_index);
config_recent_for_layer (lp, m_cv_index);
if (editing ()) {
open_editor_hooks ();
}
}
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 ();
close_editor_hooks (false);
// fetches the last configuration for the given layer
config_recent_for_layer (cv->layout ().get_properties ((unsigned int) layer), cv_index);
open_editor_hooks ();
}
}
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,93 @@
/*
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;
}
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

View File

@ -32,6 +32,11 @@
#include "layLayoutViewBase.h"
#include "layEditable.h"
#include "tlException.h"
#include "tlInternational.h"
#if defined(HAVE_QT)
# include <QMessageBox>
#endif
namespace edt {
@ -93,6 +98,42 @@ std::map<std::string, tl::Variant> pcell_parameters_from_string (const std::stri
return pm;
}
bool
set_or_request_current_layer (lay::LayoutViewBase *view, const db::LayerProperties &lp, unsigned int cv_index, bool make_current)
{
// try to find an existing layer
if (make_current) {
if (view->set_current_layer (cv_index, lp)) {
return true;
}
} else {
if (! view->find_layer (cv_index, lp).is_null ()) {
return true;
}
}
const lay::CellView &cv = view->cellview (cv_index);
if (! cv.is_valid ()) {
return false;
}
lay::LayerPropertiesNode lpn;
lpn.set_source (lay::ParsedLayerSource (lp, cv_index));
view->init_layer_properties (lpn);
{
db::Transaction transaction (! view->manager ()->transacting () ? view->manager () : 0, tl::to_string (tr ("Create new layer")));
lay::LayerPropertiesConstIterator lpi = lay::LayerPropertiesConstIterator (& view->insert_layer (view->end_layers (), lpn));
if (make_current) {
view->set_current_layer (lpi);
}
lpi->realize_source ();
}
return true;
}
// -------------------------------------------------------------
// TransformationsVariants implementation
// for a lay::LayoutView

View File

@ -42,6 +42,7 @@
namespace lay
{
class LayoutViewBase;
class Dispatcher;
}
namespace edt {
@ -79,6 +80,13 @@ EDT_PUBLIC std::map<std::string, tl::Variant> pcell_parameters_from_string (cons
EDT_PUBLIC bool
get_parameters_from_pcell_and_guiding_shapes (db::Layout *layout, db::cell_index_type cell_index, db::pcell_parameters_type &parameters_for_pcell);
/**
* @brief Request to make the given layer the current one (asks whether to create the layer if needed)
*/
bool
set_or_request_current_layer (lay::LayoutViewBase *view, const db::LayerProperties &lp, unsigned int cv_index, bool make_current = true);
/**
* @brief A helper class that identifies clipboard data for edt::
*/

View File

@ -1557,6 +1557,9 @@ Object::from_string (const char *str, const char *base_dir)
ex.test ("]");
} else {
// otherwise stop
break;
}
ex.test (";");

View File

@ -206,7 +206,7 @@ MacroController::uninitialize (lay::Dispatcher * /*root*/)
}
bool
MacroController::configure (const std::string &key, const std::string &value)
MacroController::configure (const std::string & /*key*/, const std::string & /*value*/)
{
return false;
}

View File

@ -20,5 +20,9 @@
<file alias="qt_dialog_python.lym">macro_templates/qt_dialog_python.lym</file>
<file alias="pcell_python.lym">macro_templates/pcell_python.lym</file>
<file alias="pcell_sample_python.lym">macro_templates/pcell_sample_python.lym</file>
<file alias="via_pcell_sample_python.lym">macro_templates/via_pcell_sample_python.lym</file>
<file alias="via_pcell_sample.lym">macro_templates/via_pcell_sample.lym</file>
<file alias="editor_hooks_sample_python.lym">macro_templates/editor_hooks_sample_python.lym</file>
<file alias="editor_hooks_sample.lym">macro_templates/editor_hooks_sample.lym</file>
</qresource>
</RCC>

View File

@ -265,7 +265,11 @@ Session::restore (lay::MainWindow &mw)
as.reserve (vd.annotation_shapes.annotation_shapes.size ());
for (std::vector<SessionAnnotationDescriptor>::const_iterator ad = vd.annotation_shapes.annotation_shapes.begin (); ad != vd.annotation_shapes.annotation_shapes.end (); ++ad) {
db::DUserObjectBase *obj = db::DUserObjectFactory::create (ad->class_name.c_str (), ad->value_string.c_str (), ! m_base_dir.empty () ? m_base_dir.c_str () : 0);
as.insert (db::DUserObject (obj));
if (obj) {
as.insert (db::DUserObject (obj));
} else {
tl::warn << tl::to_string (tr ("Unable to restore session user object with unknown class: ")) << ad->class_name;
}
}
view->update_content ();

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>Editor hooks sample (Ruby)\nThis sample shows how to implement editor hooks</description>
<format>general</format>
<autorun>true</autorun>
<autorun-early>false</autorun-early>
<show-in-menu>false</show-in-menu>
<category>macros</category>
<interpreter>ruby</interpreter>
<text>module MyEditorHooks
# This demo installs editor hooks to indicate the forbidding regions around a shape
#
# It follows some hypothetical technology that defines three layers:
#
# 1/0: min space 0.2, euclidian
# 2/0: min space 0.5, projection
# 3/0: min space 1.0, projection
#
# The forbidding region is highlighted while the shapes
# are drawn.
#
# Space between instances: 0.4
class MyEditorHooks &lt; RBA::EditorHooks
def initialize
self.register("MyEditorHooks")
@markers = []
@view = nil
@layout = nil
@space = 0.0
@spaces = {
RBA::LayerInfo::new(1, 0) => [ 0.2, RBA::Region::Euclidian ],
RBA::LayerInfo::new(2, 0) => [ 0.5, RBA::Region::Projection ],
RBA::LayerInfo::new(3, 0) => [ 1.0, RBA::Region::Projection ]
}
@instance_space = 0.4
end
def set_space_from_layer(layer_index)
# pick the space value
lp = @layout.get_info(layer_index)
if @spaces[lp]
(s, m) = @spaces[lp]
@space = s / @layout.dbu
@metrics = m
else
@space = nil
end
end
def add_marker_from_shape(shape, trans)
if !@space
return
end
p = shape.polygon
if p &amp;&amp; p.num_points &lt; 100
r = RBA::Region::new
r.merged_semantics = (p.num_points != 2)
r.insert(p)
r = r.drc_hull(@metrics, @space)
r.each do |pp|
m = RBA::Marker::new(@view)
m.line_style = 2
m.vertex_size = 0
m.set_polygon(trans * pp)
@markers.append(m)
end
end
t = shape.text
if t
m = RBA::Marker::new(@view)
m.set_box(trans * t.bbox.enlarged(@space))
@markers.append(m)
end
end
def clear_markers
@markers.each { |m| m._destroy }
@markers = []
end
def cleanup
self.clear_markers
@view = nil
@layout = nil
end
def setup(cv)
@view = cv.view
@layout = cv.layout
self.clear_markers
end
# Shape creation protocol
def begin_create_shapes(cv, layer)
self.setup(cv)
# pick the space value
self.set_space_from_layer(layer.layer_index)
end
def begin_new_shapes
self.clear_markers
end
def create_shape(shape, trans)
# create a marker with space halo
self.add_marker_from_shape(shape, trans)
end
def end_create_shapes
self.cleanup
end
# Instance creation protocol
def begin_create_instances(cv)
self.setup(cv)
end
def begin_new_instances
self.clear_markers
end
def create_instance(instance, trans)
m = RBA::Marker::new(@view)
m.set_box(trans * instance.bbox.enlarged(@instance_space / @layout.dbu))
@markers.append(m)
end
def end_create_instances
self.cleanup
end
# Modification protocol
def begin_edit(cv)
self.setup(cv)
end
def begin_edits
self.clear_markers
end
def transformed(path, applied, trans)
if !path.shape
m = RBA::Marker::new(@view)
m.set_box(trans * (applied * path.inst.bbox).enlarged(@instance_space / @layout.dbu))
@markers.append(m)
else
self.set_space_from_layer(path.layer)
self.add_marker_from_shape(path.shape, trans * applied)
end
end
def modified(path, shape, trans)
self.set_space_from_layer(path.layer)
self.add_marker_from_shape(shape, trans)
end
def commit_edit
# nothing here.
end
def end_edit
self.cleanup
end
end
MyEditorHooks::new
end
</text>
</klayout-macro>

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>Editor hooks sample (Python)\nThis sample shows how to implement editor hooks</description>
<format>general</format>
<autorun>true</autorun>
<autorun-early>false</autorun-early>
<show-in-menu>false</show-in-menu>
<category>pymacros</category>
<interpreter>python</interpreter>
<text>import pya
"""
This demo installs editor hooks to indicate the forbidding regions around a shape
It follows some hypothetical technology that defines three layers:
1/0: min space 0.2, euclidian
2/0: min space 0.5, projection
3/0: min space 1.0, projection
The forbidding region is highlighted while the shapes
are drawn.
Space between instances: 0.4
"""
class MyEditorHooks(pya.EditorHooks):
def __init__(self):
self.register("MyEditorHooks")
self.markers = []
self.view = None
self.layout = None
self.space = 0.0
self.spaces = {
pya.LayerInfo(1, 0): ( 0.2, pya.Region.Euclidian ),
pya.LayerInfo(2, 0): ( 0.5, pya.Region.Projection ),
pya.LayerInfo(3, 0): ( 1.0, pya.Region.Projection )
}
self.instance_space = 0.4
def set_space_from_layer(self, layer_index):
# pick the space value
lp = self.layout.get_info(layer_index)
if lp in self.spaces:
(s, m) = self.spaces[lp]
self.space = s / self.layout.dbu
self.metrics = m
else:
self.space = None
def add_marker_from_shape(self, shape, trans):
if self.space is None:
return
p = shape.polygon
if p is not None and p.num_points() &lt; 100:
r = pya.Region()
r.merged_semantics = (p.num_points() != 2)
r.insert(p)
r = r.drc_hull(self.metrics, self.space)
for pp in r.each():
m = pya.Marker(self.view)
m.line_style = 2
m.vertex_size = 0
m.set_polygon(trans * pp)
self.markers.append(m)
t = shape.text
if t is not None:
m = pya.Marker(self.view)
m.set_box(trans * t.bbox().enlarged(self.space))
self.markers.append(m)
# Shape creation protocol
def begin_create_shapes(self, cv, layer):
# setup session
self.view = cv.view()
self.layout = cv.layout()
self.markers = []
# pick the space value
self.set_space_from_layer(layer.layer_index())
def begin_new_shapes(self):
# create new markers
self.markers = []
def create_shape(self, shape, trans):
# create a marker with space halo
self.add_marker_from_shape(shape, trans)
def end_create_shapes(self):
# cleanup
self.markers = []
self.view = None
self.layout = None
# Instance creation protocol
def begin_create_instances(self, cv):
# setup session
self.view = cv.view()
self.layout = cv.layout()
self.markers = []
def begin_new_instances(self):
# create new markers
self.markers = []
def create_instance(self, instance, trans):
m = pya.Marker(self.view)
m.set_box(trans * instance.bbox().enlarged(self.instance_space / self.layout.dbu))
self.markers.append(m)
def end_create_instances(self):
# cleanup
self.markers = []
self.view = None
self.layout = None
# Modification protocol
def begin_edit(self, cv):
# setup session
self.view = cv.view()
self.layout = cv.layout()
self.markers = []
def begin_edits(self):
# create new markers
self.markers = []
def transformed(self, path, applied, trans):
if path.shape is None:
m = pya.Marker(self.view)
m.set_box(trans * (applied * path.inst().bbox()).enlarged(self.instance_space / self.layout.dbu))
self.markers.append(m)
else:
self.set_space_from_layer(path.layer)
self.add_marker_from_shape(path.shape, trans * applied)
def modified(self, path, shape, trans):
self.set_space_from_layer(path.layer)
self.add_marker_from_shape(shape, trans)
def commit_edit(self):
pass
def end_edit(self):
# cleanup
self.markers = []
self.view = None
self.layout = None
MyEditorHooks()
</text>
</klayout-macro>

View File

@ -20,6 +20,8 @@ pcell.lym
# Samples
:Samples;;
pcell_sample.lym
via_pcell_sample.lym
editor_hooks_sample.lym
qt_designer.lym
qt_dialog.lym
qt_server.lym
@ -39,6 +41,8 @@ pcell_python.lym
# Samples
:Samples;;
pcell_sample_python.lym
via_pcell_sample_python.lym
editor_hooks_sample_python.lym
qt_designer_python.lym
qt_dialog_python.lym
qt_server_python.lym

View File

@ -6,6 +6,7 @@
<autorun-early>false</autorun-early>
<show-in-menu>false</show-in-menu>
<shortcut></shortcut>
<category>pymacros</category>
<interpreter>python</interpreter>
<text>import pya
import math

View File

@ -0,0 +1,297 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>Via PCell sample (Ruby)\nThis sample provides a via PCell with two via types</description>
<format>general</format>
<autorun>true</autorun>
<autorun-early>false</autorun-early>
<show-in-menu>false</show-in-menu>
<category>macros</category>
<interpreter>ruby</interpreter>
<text># Sample via PCell
#
# This sample PCell implements a library called "MyViaLib" with a single PCell that
# provides two vias for a hypothetical technology with these layers:
#
# 1/0 Metal1
# 2/0 Via1
# 3/0 Metal2
# 4/0 Via2
# 5/0 Metal3
#
# The sample demonstrates how to equip a PCell with the necessary declarations,
# so it can supply vias for the wire (path) tool.
#
# It implements simple rectangular via arrays made from squares with a fixed size,
# pitch and enclosure.
#
# NOTE: after changing the code, the macro needs to be rerun to install the new
# implementation. The macro is also set to "auto run" to install the PCell
# when KLayout is run.
module MyViaLib
include RBA
# Extend the ViaType class with some custom attributes
class ViaTypeEx &lt; RBA::ViaType
def initialize(*args)
super(*args)
end
attr_accessor :enc_bottom
attr_accessor :enc_top
attr_accessor :vwidth
attr_accessor :vspace
end
class ViaPCell &lt; PCellDeclarationHelper
# Constructor: provides the PCell parameter definitions
def initialize
# Important: initialize the super class
super
# Every via PCell should declare these parameters:
#
# via: the name of the via as declared in the via types
# w_bottom: the width of the bottom box in micrometers
# h_bottom: the height of the bottom box in micrometers
# w_top: the width of the top box in micrometers
# h_top: the width of the top box in micrometers
#
# w_bottom etc. can be zero, indicating that the via
# does not have a specific extension in this layer.
# The PCell may chose a dimension in that case.
param(:via, TypeString, "Via type", :hidden =&gt; false)
param(:w_bottom, TypeDouble, "Bottom width", :hidden =&gt; false, :default =&gt; 0.0)
param(:h_bottom, TypeDouble, "Bottom height", :hidden =&gt; false, :default =&gt; 0.0)
param(:w_top, TypeDouble, "Top width", :hidden =&gt; false, :default =&gt; 0.0)
param(:h_top, TypeDouble, "Top height", :hidden =&gt; false, :default =&gt; 0.0)
# Optional additional parameters: here we allow to override
# the computed array dimension by setting these parameters
# to non-zero.
param(:nx, TypeInt, "nx", :default =&gt; 0)
param(:ny, TypeInt, "ny", :default =&gt; 0)
# Build a list of supported vias
@via_type_list = []
# Via1
vt = ViaTypeEx::new("V1", "Via1 (0.2x0.2)")
vt.wbmin = 0.4
vt.hbmin = 0.4
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = RBA::LayerInfo::new(1, 0)
vt.cut = RBA::LayerInfo::new(2, 0)
vt.top = RBA::LayerInfo::new(3, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by RBA, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.15
vt.vwidth = 0.2
vt.vspace = 0.2
@via_type_list.append(vt)
# Via2
vt = ViaTypeEx::new("V2", "Via2 (0.3x0.3)")
vt.wbmin = 0.5
vt.hbmin = 0.5
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = RBA::LayerInfo::new(3, 0)
vt.cut = RBA::LayerInfo::new(4, 0)
vt.top = RBA::LayerInfo::new(5, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by pya, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.1
vt.vwidth = 0.3
vt.vspace = 0.2
@via_type_list.append(vt)
end
# Implements the "via_types" method from the PCellDeclaration
# interface: delivers the vias supported by this PCell.
def via_types
return @via_type_list
end
# Returns the via with the name given by the "via"
# PCell argument
def via_type
@via_type_list.each do |vt|
if vt.name == self.via
return vt
end
end
return nil
end
# PCell interface implementation
def display_text_impl
return "Via(" + self.via + ")"
end
# PCell interface implementation
def coerce_parameters_impl
end
# PCell interface implementation
def can_create_from_shape_impl
return false
end
# PCell interface implementation
def parameters_from_shape_impl
end
# PCell interface implementation
def transformation_from_shape_impl
return RBA::Trans::new
end
# A helper function to compute the minimum
# width or height less the enclosure
# (a = w/h_bottom, b = w/h_top,
# da/b = enclosure for a/b)
def min_dim(a, b, da, db)
if a &lt; 1e-10 &amp;&amp; b &lt; 1e-10
return 0.0
elsif a &lt; 1e-10
return b - 2.0 * [da, db].max
elsif b &lt; 1e-10
return a - 2.0 * [da, db].max
else
return [a - 2.0 * da, b - 2.0 * db].min
end
end
# Computes the nx and ny as mandated by the widths and
# heights
#
# w/h_top/bottom can be zero, indicating no specific
# dimension is requested. The implementation can choose
# what to do in this case. It can also decide to provide
# largher vias than given by w_ or h_ or favor square vias
# over rectangular ones.
def getnxy(vt)
mode = 2 # 1: maximum, 2: minimum
if mode == 1
# This implementation uses the maximum size requested
# by either top or bottom:
w = [[self.w_bottom, vt.wbmin].max - 2 * vt.enc_bottom,
[self.w_top, vt.wtmin].max - 2 * vt.enc_top].max
h = [[self.h_bottom, vt.hbmin].max - 2 * vt.enc_bottom,
[self.h_top, vt.htmin].max - 2 * vt.enc_top].max
elsif mode == 2
# This implementation delivers the minimum via, ignoring
# zero dimensions:
w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top)
h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top)
end
# parameter nx or ny override computed value if &gt; 0
if self.nx &gt; 0
nx = self.nx
else
nx = [1, ((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
end
if self.ny &gt; 0
ny = self.ny
else
ny = [1, ((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
end
return [nx, ny]
end
# Implementation of the PCell interface: generates the layouts
def produce_impl
vt = self.via_type
if !vt
return
end
(nx, ny) = self.getnxy(vt)
wcut = nx * vt.vwidth + (nx - 1) * vt.vspace
hcut = ny * vt.vwidth + (ny - 1) * vt.vspace
wbottom = [[self.w_bottom, vt.wbmin].max, vt.enc_bottom * 2.0 + wcut].max
hbottom = [[self.h_bottom, vt.hbmin].max, vt.enc_bottom * 2.0 + hcut].max
wtop = [[self.w_top, vt.wtmin].max, vt.enc_top * 2.0 + wcut].max
htop = [[self.h_top, vt.htmin].max, vt.enc_top * 2.0 + hcut].max
lbottom = self.layout.layer(vt.bottom)
ltop = self.layout.layer(vt.top)
lcut = self.layout.layer(vt.cut)
self.cell.shapes(lbottom).insert(RBA::DBox::new(wbottom, hbottom))
self.cell.shapes(ltop).insert(RBA::DBox::new(wtop, htop))
scut = self.cell.shapes(lcut)
nx.times do |ix|
x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace)
ny.times do |iy|
y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace)
scut.insert(RBA::DBox::new(vt.vwidth, vt.vwidth).moved(x, y))
end
end
end
end
# A declaration for a test library
class MyViaLib &lt; RBA::Library
def initialize
self.description = "Via Test Library"
self.layout().register_pcell("Via", ViaPCell::new)
self.register("MyViaLib")
end
end
# instantiates the test library
MyViaLib::new
end
</text>
</klayout-macro>

View File

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>Via PCell sample (Python)\nThis sample provides a via PCell with two via types</description>
<format>general</format>
<autorun>true</autorun>
<autorun-early>false</autorun-early>
<show-in-menu>false</show-in-menu>
<category>pymacros</category>
<interpreter>python</interpreter>
<text>import pya
import math
"""
Sample via PCell
This sample PCell implements a library called "MyViaLib" with a single PCell that
provides two vias for a hypothetical technology with these layers:
1/0 Metal1
2/0 Via1
3/0 Metal2
4/0 Via2
5/0 Metal3
The sample demonstrates how to equip a PCell with the necessary declarations,
so it can supply vias for the wire (path) tool.
It implements simple rectangular via arrays made from squares with a fixed size,
pitch and enclosure.
NOTE: after changing the code, the macro needs to be rerun to install the new
implementation. The macro is also set to "auto run" to install the PCell
when KLayout is run.
"""
class ViaPCell(pya.PCellDeclarationHelper):
def __init__(self):
"""
Constructor: provides the PCell parameter definitions
"""
super(ViaPCell, self).__init__()
# Every via PCell should declare these parameters:
#
# via: the name of the via as declared in the via types
# w_bottom: the width of the bottom box in micrometers
# h_bottom: the height of the bottom box in micrometers
# w_top: the width of the top box in micrometers
# h_top: the width of the top box in micrometers
#
# w_bottom etc. can be zero, indicating that the via
# does not have a specific extension in this layer.
# The PCell may chose a dimension in that case.
self.param("via", self.TypeString, "Via type", hidden = False)
self.param("w_bottom", self.TypeDouble, "Bottom width", hidden = False, default = 0.0)
self.param("h_bottom", self.TypeDouble, "Bottom height", hidden = False, default = 0.0)
self.param("w_top", self.TypeDouble, "Top width", hidden = False, default = 0.0)
self.param("h_top", self.TypeDouble, "Top height", hidden = False, default = 0.0)
# Optional additional parameters: here we allow to override
# the computed array dimension by setting these parameters
# to non-zero.
self.param("nx", self.TypeInt, "nx", default = 0)
self.param("ny", self.TypeInt, "ny", default = 0)
# Build a list of supported vias
self.via_type_list = []
# Via1
vt = pya.ViaType("V1", "Via1 (0.2x0.2)")
vt.wbmin = 0.4
vt.hbmin = 0.4
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = pya.LayerInfo(1, 0)
vt.cut = pya.LayerInfo(2, 0)
vt.top = pya.LayerInfo(3, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by pya, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.15
vt.vwidth = 0.2
vt.vspace = 0.2
self.via_type_list.append(vt)
# Via2
vt = pya.ViaType("V2", "Via2 (0.3x0.3)")
vt.wbmin = 0.5
vt.hbmin = 0.5
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = pya.LayerInfo(3, 0)
vt.cut = pya.LayerInfo(4, 0)
vt.top = pya.LayerInfo(5, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by pya, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.1
vt.vwidth = 0.3
vt.vspace = 0.2
self.via_type_list.append(vt)
def via_types(self):
"""
Implements the "via_types" method from the PCellDeclaration
interface: delivers the vias supported by this PCell.
"""
return self.via_type_list
def via_type(self):
"""
Returns the via with the name given by the "via"
PCell argument
"""
for vt in self.via_type_list:
if vt.name == self.via:
return vt
return None
def display_text_impl(self):
"""
PCell interface implementation
"""
return "Via(" + self.via + ")"
def coerce_parameters_impl(self):
"""
PCell interface implementation
"""
pass
def can_create_from_shape_impl(self):
"""
PCell interface implementation
"""
return False
def parameters_from_shape_impl(self):
"""
PCell interface implementation
"""
pass
def transformation_from_shape_impl(self):
"""
PCell interface implementation
"""
return pya.Trans()
def min_dim(self, a, b, da, db):
"""
A helper function to compute the minimum
width or height less the enclosure
(a = w/h_bottom, b = w/h_top,
da/b = enclosure for a/b)
"""
if a &lt; 1e-10 and b &lt; 1e-10:
return 0.0
elif a &lt; 1e-10:
return b - 2.0 * max(da, db)
elif b &lt; 1e-10:
return a - 2.0 * max(da, db)
else:
return min(a - 2.0 * da, b - 2.0 * db)
def getnxy(self, vt):
"""
Computes the nx and ny as mandated by the widths and
heights
w/h_top/bottom can be zero, indicating no specific
dimension is requested. The implementation can choose
what to do in this case. It can also decide to provide
largher vias than given by w_ or h_ or favor square vias
over rectangular ones.
"""
mode = 2 # 1: maximum, 2: minimum
if mode == 1:
# This implementation uses the maximum size requested
# by either top or bottom:
w = max(max(self.w_bottom, vt.wbmin) - 2 * vt.enc_bottom,
max(self.w_top, vt.wtmin) - 2 * vt.enc_top)
h = max(max(self.h_bottom, vt.hbmin) - 2 * vt.enc_bottom,
max(self.h_top, vt.htmin) - 2 * vt.enc_top)
elif mode == 2:
# This implementation delivers the minimum via, ignoring
# zero dimensions:
w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top)
h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top)
# parameter nx or ny override computed value if &gt; 0
if self.nx &gt; 0:
nx = self.nx
else:
nx = max(1, int(math.floor((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10)))
if self.ny &gt; 0:
ny = self.ny
else:
ny = max(1, int(math.floor((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10)))
return (nx, ny)
def produce_impl(self):
"""
Implementation of the PCell interface: generates the layouts
"""
vt = self.via_type()
if vt is None:
return
(nx, ny) = self.getnxy(vt)
wcut = nx * vt.vwidth + (nx - 1) * vt.vspace
hcut = ny * vt.vwidth + (ny - 1) * vt.vspace
wbottom = max(max(self.w_bottom, vt.wbmin), vt.enc_bottom * 2.0 + wcut)
hbottom = max(max(self.h_bottom, vt.hbmin), vt.enc_bottom * 2.0 + hcut)
wtop = max(max(self.w_top, vt.wtmin), vt.enc_top * 2.0 + wcut)
htop = max(max(self.h_top, vt.htmin), vt.enc_top * 2.0 + hcut)
lbottom = self.layout.layer(vt.bottom)
ltop = self.layout.layer(vt.top)
lcut = self.layout.layer(vt.cut)
self.cell.shapes(lbottom).insert(pya.DBox(wbottom, hbottom))
self.cell.shapes(ltop).insert(pya.DBox(wtop, htop))
scut = self.cell.shapes(lcut)
for ix in range(0, nx):
x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace)
for iy in range(0, ny):
y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace)
scut.insert(pya.DBox(vt.vwidth, vt.vwidth).moved(x, y))
class MyViaLib(pya.Library):
"""
A declaration for a test library
"""
def __init__(self):
self.description = "Via Test Library"
self.layout().register_pcell("Via", ViaPCell())
self.register("MyViaLib")
# instantiates the test library
MyViaLib()
</text>
</klayout-macro>

View File

@ -31,6 +31,11 @@
#include <QWidget>
namespace db
{
struct LayerProperties;
}
namespace lay
{
@ -59,6 +64,7 @@ public:
virtual void apply (lay::Dispatcher * /*root*/) { }
virtual void setup (lay::Dispatcher * /*root*/) { }
virtual void commit_recent (lay::Dispatcher * /*root*/) { }
virtual void config_recent_for_layer (lay::Dispatcher * /*root*/, const db::LayerProperties & /*lp*/, int /*cv_index*/) { }
bool is_focus_page () const { return m_focus_page; }
void set_focus_page (bool f) { m_focus_page = f; }

View File

@ -1594,19 +1594,30 @@ LayoutViewBase::rename_properties (unsigned int index, const std::string &new_na
layer_list_changed_event (4);
}
bool
LayoutViewBase::set_current_layer (unsigned int cv_index, const db::LayerProperties &lp)
lay::LayerPropertiesConstIterator
LayoutViewBase::find_layer (unsigned int cv_index, const db::LayerProperties &lp) const
{
// rename the ones that got shifted.
lay::LayerPropertiesConstIterator l = begin_layers ();
while (! l.at_end ()) {
if (l->source (true).cv_index () == int (cv_index) && l->source (true).layer_props ().log_equal (lp)) {
set_current_layer (l);
return true;
return l;
}
++l;
}
return false;
return lay::LayerPropertiesConstIterator ();
}
bool
LayoutViewBase::set_current_layer (unsigned int cv_index, const db::LayerProperties &lp)
{
lay::LayerPropertiesConstIterator l = find_layer (cv_index, lp);
if (! l.is_null ()) {
set_current_layer (l);
return true;
} else {
return false;
}
}
void

View File

@ -602,6 +602,13 @@ public:
*/
virtual lay::LayerPropertiesConstIterator current_layer () const;
/**
* @brief Finds the first layer by layer properties and cell view index
*
* Returns a null iterator if the layer is not found in the list.
*/
virtual lay::LayerPropertiesConstIterator find_layer (unsigned int cv_index, const db::LayerProperties &properties) const;
/**
* @brief Return the layers that are selected in the layer browser
*

View File

@ -134,7 +134,7 @@ void render_cell_inst (const db::Layout &layout, const db::CellInstArray &inst,
db::HAlignCenter,
db::VAlignCenter,
// TODO: apply "real" transformation?
db::DFTrans (cell_name_text_transform ? tbox.fp_trans ().rot () : db::DFTrans::r0), 0, 0, 0, text);
db::DFTrans (cell_name_text_transform ? tbox.fp_trans ().rot () : db::DFTrans::r0), 0, 0, 0, contour);
}
@ -168,21 +168,6 @@ void render_cell_inst (const db::Layout &layout, const db::CellInstArray &inst,
}
{
// render error layer
db::RecursiveShapeIterator shapes (layout, cell, layout.error_layer ());
while (! shapes.at_end ()) {
for (db::CellInstArray::iterator arr = inst.begin (); ! arr.at_end (); ++arr) {
r.draw (*shapes, trans * inst.complex_trans (*arr) * shapes.trans (), fill, contour, 0 /*use vertex for origin*/, text);
}
++shapes;
}
}
// render the origins
if (render_origins && vertex) {

View File

@ -209,6 +209,7 @@ LayerControlPanel::LayerControlPanel (lay::LayoutViewBase *view, db::Manager *ma
m_tabs_need_update (true),
m_hidden_flags_need_update (true),
m_in_update (false),
m_current_layer (0),
m_phase (0),
m_do_update_content_dm (this, &LayerControlPanel::do_update_content),
m_do_update_visibility_dm (this, &LayerControlPanel::do_update_visibility),
@ -1747,6 +1748,7 @@ LayerControlPanel::begin_updates ()
// we force a clear_selection in this case, since we cannot make sure the
// selecting remains valid
clear_selection ();
m_current_layer = 0;
}
}
@ -1987,49 +1989,70 @@ LayerControlPanel::set_current_layer (const lay::LayerPropertiesConstIterator &l
end_updates ();
mp_layer_list->set_current (l);
if (m_in_update) {
// while in update, the layer list does not follow the selection, so keep a temporary one
m_current_layer = l.uint ();
} else {
mp_layer_list->set_current (l);
}
}
lay::LayerPropertiesConstIterator
LayerControlPanel::current_layer () const
{
return mp_model->iterator (mp_layer_list->currentIndex ());
if (m_in_update) {
return lay::LayerPropertiesConstIterator (mp_view->get_properties (), m_current_layer);
} else {
return mp_model->iterator (mp_layer_list->currentIndex ());
}
}
std::vector <lay::LayerPropertiesConstIterator>
LayerControlPanel::selected_layers () const
{
QModelIndexList selected = mp_layer_list->selectionModel ()->selectedIndexes ();
if (m_in_update) {
std::vector <lay::LayerPropertiesConstIterator> llist;
llist.reserve (selected.size ());
for (QModelIndexList::const_iterator i = selected.begin (); i != selected.end (); ++i) {
if (i->column () == 0) {
lay::LayerPropertiesConstIterator iter (mp_model->iterator (*i));
if (! iter.is_null () && ! iter.at_end ()) {
llist.push_back (iter);
std::vector <lay::LayerPropertiesConstIterator> new_sel;
for (std::vector<size_t>::const_iterator s = m_new_sel.begin (); s != m_new_sel.end (); ++s) {
new_sel.push_back (lay::LayerPropertiesConstIterator (mp_view->get_properties (), *s));
}
return new_sel;
} else {
QModelIndexList selected = mp_layer_list->selectionModel ()->selectedIndexes ();
std::vector <lay::LayerPropertiesConstIterator> llist;
llist.reserve (selected.size ());
for (QModelIndexList::const_iterator i = selected.begin (); i != selected.end (); ++i) {
if (i->column () == 0) {
lay::LayerPropertiesConstIterator iter (mp_model->iterator (*i));
if (! iter.is_null () && ! iter.at_end ()) {
llist.push_back (iter);
}
}
}
// filter out the children:
// we employ the fact, that the LayerPropertiesConstIterator's are ordered
// parents first and children before siblings.
std::sort (llist.begin (), llist.end ());
std::vector<lay::LayerPropertiesConstIterator>::iterator write = llist.begin ();
for (std::vector<lay::LayerPropertiesConstIterator>::iterator read = llist.begin (); read != llist.end (); ) {
lay::LayerPropertiesConstIterator n = *read;
*write++ = n;
n.next_sibling ();
read = std::lower_bound (read + 1, llist.end (), n);
}
llist.erase (write, llist.end ());
return llist;
}
// filter out the children:
// we employ the fact, that the LayerPropertiesConstIterator's are ordered
// parents first and children before siblings.
std::sort (llist.begin (), llist.end ());
std::vector<lay::LayerPropertiesConstIterator>::iterator write = llist.begin ();
for (std::vector<lay::LayerPropertiesConstIterator>::iterator read = llist.begin (); read != llist.end (); ) {
lay::LayerPropertiesConstIterator n = *read;
*write++ = n;
n.next_sibling ();
read = std::lower_bound (read + 1, llist.end (), n);
}
llist.erase (write, llist.end ());
return llist;
}
void

View File

@ -361,6 +361,7 @@ private:
bool m_tabs_need_update;
bool m_hidden_flags_need_update;
bool m_in_update;
size_t m_current_layer;
std::vector<size_t> m_new_sel;
int m_phase;
tl::DeferredMethod<LayerControlPanel> m_do_update_content_dm;

View File

@ -1088,7 +1088,7 @@ GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Sha
}
void
GDS2WriterBase::write_properties (const db::Layout &layout, db::properties_id_type prop_id)
GDS2WriterBase::write_properties (const db::Layout & /*layout*/, db::properties_id_type prop_id)
{
auto props = db::properties (prop_id).to_map ();
for (auto p = props.begin (); p != props.end (); ++p) {

View File

@ -139,6 +139,7 @@ RUBYTEST (dbTextTest, "dbTextTest.rb")
RUBYTEST (dbTextsTest, "dbTextsTest.rb")
RUBYTEST (dbTilingProcessorTest, "dbTilingProcessorTest.rb")
RUBYTEST (dbTransTest, "dbTransTest.rb")
RUBYTEST (dbViaTest, "dbViaTest.rb")
RUBYTEST (dbVectorTest, "dbVectorTest.rb")
RUBYTEST (dbUtilsTests, "dbUtilsTests.rb")
RUBYTEST (dbTechnologies, "dbTechnologies.rb")

65
testdata/ruby/dbViaTest.rb vendored Normal file
View File

@ -0,0 +1,65 @@
# encoding: UTF-8
# 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
if !$:.member?(File::dirname($0))
$:.push(File::dirname($0))
end
load("test_prologue.rb")
class DBVia_TestClass < TestBase
# Via basics
def test_1_Via
via = RBA::ViaType::new("name", "desc")
assert_equal(via.name, "name")
via.name = "none"
assert_equal(via.name, "none")
assert_equal(via.description, "desc")
via.description = "-"
assert_equal(via.description, "-")
via.wbmin = 0.5
assert_equal(via.wbmin.to_s, "0.5")
via.wtmin = 0.25
assert_equal(via.wtmin.to_s, "0.25")
via.hbmin = 1.5
assert_equal(via.hbmin.to_s, "1.5")
via.htmin = 1.25
assert_equal(via.htmin.to_s, "1.25")
via.bottom = RBA::LayerInfo::new(1, 0)
assert_equal(via.bottom.to_s, "1/0")
via.cut = RBA::LayerInfo::new(2, 0)
assert_equal(via.cut.to_s, "2/0")
via.top = RBA::LayerInfo::new(3, 0)
assert_equal(via.top.to_s, "3/0")
end
end
load("test_epilogue.rb")