mirror of https://github.com/KLayout/klayout.git
Merge branch 'master' into devel
This commit is contained in:
commit
53a7414757
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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 ());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 ¶meters) 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"
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
);
|
||||
|
||||
}
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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 ());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ¶meters_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::
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1557,6 +1557,9 @@ Object::from_string (const char *str, const char *base_dir)
|
|||
|
||||
ex.test ("]");
|
||||
|
||||
} else {
|
||||
// otherwise stop
|
||||
break;
|
||||
}
|
||||
|
||||
ex.test (";");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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 < 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 && p.num_points < 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>
|
||||
|
|
@ -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() < 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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 < 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 < 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 => false)
|
||||
param(:w_bottom, TypeDouble, "Bottom width", :hidden => false, :default => 0.0)
|
||||
param(:h_bottom, TypeDouble, "Bottom height", :hidden => false, :default => 0.0)
|
||||
param(:w_top, TypeDouble, "Top width", :hidden => false, :default => 0.0)
|
||||
param(:h_top, 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.
|
||||
|
||||
param(:nx, TypeInt, "nx", :default => 0)
|
||||
param(:ny, TypeInt, "ny", :default => 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 < 1e-10 && b < 1e-10
|
||||
return 0.0
|
||||
elsif a < 1e-10
|
||||
return b - 2.0 * [da, db].max
|
||||
elsif b < 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 > 0
|
||||
|
||||
if self.nx > 0
|
||||
nx = self.nx
|
||||
else
|
||||
nx = [1, ((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
|
||||
end
|
||||
|
||||
if self.ny > 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 < 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>
|
||||
|
|
@ -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 < 1e-10 and b < 1e-10:
|
||||
return 0.0
|
||||
elif a < 1e-10:
|
||||
return b - 2.0 * max(da, db)
|
||||
elif b < 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 > 0
|
||||
|
||||
if self.nx > 0:
|
||||
nx = self.nx
|
||||
else:
|
||||
nx = max(1, int(math.floor((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10)))
|
||||
|
||||
if self.ny > 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>
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
Loading…
Reference in New Issue