Merge remote-tracking branch 'origin/master' into feature/issue-2337

This commit is contained in:
Matthias Koefferlein 2026-05-20 19:07:39 +02:00
commit 4afe2e5e8a
62 changed files with 2495 additions and 98 deletions

View File

@ -66,7 +66,7 @@ jobs:
mkdir -p $HOST_CCACHE_DIR
- name: Build wheels (ARM)
if: matrix.os == 'ubuntu-24.04-arm'
uses: pypa/cibuildwheel@v3.4.0
uses: pypa/cibuildwheel@v3.4.1
env:
# override the default CentOS “yum install … ccache” and drop ccache
CIBW_BEFORE_ALL_LINUX: |
@ -81,7 +81,7 @@ jobs:
- name: Build wheels (all other platforms)
if: matrix.os != 'ubuntu-24.04-arm'
uses: pypa/cibuildwheel@v3.4.0
uses: pypa/cibuildwheel@v3.4.1
env:
CIBW_BUILD: ${{ matrix.cibuild }}
CIBW_ARCHS_MACOS: ${{ matrix.macos-arch }}
@ -127,7 +127,7 @@ jobs:
merge-multiple: true
path: dist
- uses: pypa/gh-action-pypi-publish@v1.13.0
- uses: pypa/gh-action-pypi-publish@v1.14.0
continue-on-error: true # might fail if we don't bump the version
with:
user: __token__
@ -144,7 +144,7 @@ jobs:
merge-multiple: true
path: dist
- uses: pypa/gh-action-pypi-publish@v1.13.0
- uses: pypa/gh-action-pypi-publish@v1.14.0
with:
user: __token__
password: ${{ secrets.pypi_password }}

View File

@ -328,7 +328,7 @@ RubyTahoe = { 'exe': '/System/Library/Frameworks/Ruby.framework/Versions
# install with 'sudo port install ruby34'
# [Key Type Name] = 'MP34'
Ruby34MacPorts = { 'exe': '/opt/local/bin/ruby3.4',
'inc': '/opt/local/include/ruby-3.4.8',
'inc': '/opt/local/include/ruby-3.4.9',
'lib': '/opt/local/lib/libruby.3.4.dylib'
}
@ -567,8 +567,8 @@ if _have_Homebrew_Python:
# [4] KLayout executables including buddy tools
#-----------------------------------------------------
KLayoutExecs = [ 'klayout' ]
KLayoutExecs += [ 'strm2cif', 'strm2dxf', 'strm2gds', 'strm2gdstxt', 'strm2mag', 'strm2oas' ]
KLayoutExecs += [ 'strm2txt', 'strmclip', 'strmcmp', 'strmrun', 'strmxor' ]
KLayoutExecs += [ 'strm2cif', 'strm2dxf', 'strm2gds', 'strm2gdstxt', 'strm2lstr', 'strm2mag' ]
KLayoutExecs += [ 'strm2oas', 'strm2txt', 'strmclip', 'strmcmp', 'strmrun', 'strmxor' ]
#----------------
# End of File

View File

@ -1541,6 +1541,19 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang
}
}
static int snap_prio (lay::PointSnapToObjectResult::ObjectSnap os)
{
if (os == lay::PointSnapToObjectResult::ObjectVertex) {
return 3;
} else if (os == lay::PointSnapToObjectResult::ObjectEdge) {
return 2;
} else if (os == lay::PointSnapToObjectResult::ObjectUnspecific) {
return 1;
} else {
return 0;
}
}
void
Service::snap_rulers (lay::angle_constraint_type ac)
{
@ -1566,7 +1579,7 @@ Service::snap_rulers (lay::angle_constraint_type ac)
auto snp = snap2_details (org1, p1, ruler, ac);
double dist = p1.distance (snp.snapped_point);
if (min_dist < 0 || dist < min_dist) {
if (min_dist < 0 || snap_prio (snp.object_snap) > snap_prio (min_snp.object_snap) || (snap_prio (snp.object_snap) == snap_prio (min_snp.object_snap) && dist < min_dist)) {
min_snp = snp;
min_dist = dist;
min_delta = snp.snapped_point - p1;
@ -1575,7 +1588,7 @@ Service::snap_rulers (lay::angle_constraint_type ac)
snp = snap2_details (org2, p2, ruler, ac);
dist = p2.distance (snp.snapped_point);
if (min_dist < 0 || dist < min_dist) {
if (min_dist < 0 || snap_prio (snp.object_snap) > snap_prio (min_snp.object_snap) || (snap_prio (snp.object_snap) == snap_prio (min_snp.object_snap) && dist < min_dist)) {
min_snp = snp;
min_dist = dist;
min_delta = snp.snapped_point - p2;
@ -1631,10 +1644,10 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac)
m_trans = db::DTrans (dp + (m_p1 - db::DPoint ()) - m_trans.disp ()) * m_trans * db::DTrans (db::DPoint () - m_p1);
propose_move_transformation (m_trans, 1);
snap_rulers (ac_eff);
propose_move_transformation (m_trans, 1);
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
(*r)->transform_by (db::DCplxTrans (m_trans));
}
@ -1834,6 +1847,7 @@ Service::edit_cancel ()
m_move_mode = MoveNone;
m_selected.clear ();
selection_to_view ();
clear_mouse_cursors ();
}
}

View File

@ -103,6 +103,13 @@ EdgePairs::EdgePairs (DeepShapeStore &dss)
mp_delegate = new DeepEdgePairs (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
}
void
EdgePairs::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepEdgePairs (layer)));
}
void
EdgePairs::write (const std::string &fn) const
{

View File

@ -204,6 +204,11 @@ public:
*/
explicit EdgePairs (DeepShapeStore &dss);
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/**
* @brief Writes the edge pair collection to a file
*

View File

@ -114,6 +114,13 @@ Edges::Edges (DeepShapeStore &dss)
mp_delegate = new DeepEdges (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
}
void
Edges::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepEdges (layer)));
}
const db::RecursiveShapeIterator &
Edges::iter () const
{

View File

@ -264,6 +264,11 @@ public:
*/
explicit Edges (DeepShapeStore &dss);
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/**
* @brief Implementation of the ShapeCollection interface
*/

View File

@ -218,6 +218,7 @@ copy_or_propagate_shapes (db::Layout &target,
db::Cell &target_cell = target.cell (cm->second);
transformer->insert_transformed (target_cell.shapes (target_layer), source_cell.shapes (source_layer), trans * propagate_trans);
}
}

View File

@ -244,7 +244,11 @@ LibraryProxy::update (db::ImportLayerMapping *layer_mapping)
inst.transform_into (db::ICplxTrans (lib->layout ().dbu () / layout ()->dbu ()));
insert (inst);
if (i->has_prop_id ()) {
insert (db::CellInstArrayWithProperties (inst, i->prop_id ()));
} else {
insert (inst);
}
}
}

View File

@ -130,8 +130,10 @@ void NetlistDeviceExtractor::extract (db::DeepShapeStore &dss, unsigned int layo
std::pair<bool, db::DeepLayer> alias = dss.layer_for_flat (tl::id_of (l->second->get_delegate ()));
if (alias.first) {
// use deep layer alias for a given flat one (if found)
layers.push_back (alias.second.layer ());
// use deep layer alias for a given flat one (if found) and convert layer to a deep one
db::DeepLayer dl = alias.second;
l->second->convert_to_deep (dl);
layers.push_back (dl.layer ());
} else {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid region passed to input layer '%s' for device extraction (device %s): must be of deep region kind")), ld->name, name ()));
}

View File

@ -1581,7 +1581,7 @@ public:
void translate (const polygon<C> &d, const T &t, db::generic_repository<C> &, db::ArrayRepository &)
{
*this = d;
transform (t);
transform (t, false);
}
/**

View File

@ -132,6 +132,13 @@ Region::Region (DeepShapeStore &dss)
mp_delegate = new db::DeepRegion (db::DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
}
void
Region::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepRegion (layer)));
}
void
Region::write (const std::string &fn) const
{

View File

@ -256,6 +256,11 @@ public:
*/
void write (const std::string &fn) const;
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/**
* @brief Implementation of the ShapeCollection interface
*/

View File

@ -75,7 +75,12 @@ class DB_PUBLIC ShapeCollectionDelegateBase
: public tl::UniqueId
{
public:
ShapeCollectionDelegateBase () { }
ShapeCollectionDelegateBase ()
: m_data_id (tl::id_of (this))
{
// .. nothing yet ..
}
virtual ~ShapeCollectionDelegateBase () { }
virtual DeepShapeCollectionDelegateBase *deep () { return 0; }
@ -88,6 +93,22 @@ public:
apply_property_translator (db::PropertiesTranslator::make_remove_all ());
}
}
tl::id_type data_id () const
{
return m_data_id;
}
private:
friend class ShapeCollection;
tl::id_type m_data_id;
// used for conversion to deep
void set_data_id (tl::id_type data_id)
{
m_data_id = data_id;
}
};
/**
@ -102,6 +123,11 @@ public:
virtual ShapeCollectionDelegateBase *get_delegate () const = 0;
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer) = 0;
/**
* @brief Applies a PropertyTranslator
*
@ -111,6 +137,14 @@ public:
* delivered by "properties_repository".
*/
void apply_property_translator (const db::PropertiesTranslator &pt);
protected:
template <class Delegate>
Delegate *copy_data_id (Delegate *dlg)
{
dlg->set_data_id (get_delegate ()->data_id ());
return dlg;
}
};
}

View File

@ -99,6 +99,13 @@ Texts::Texts (DeepShapeStore &dss)
mp_delegate = new DeepTexts (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ()));
}
void
Texts::convert_to_deep (const db::DeepLayer &layer)
{
tl_assert (mp_delegate->deep () == 0);
set_delegate (copy_data_id (new db::DeepTexts (layer)));
}
void
Texts::write (const std::string &fn) const
{

View File

@ -208,6 +208,11 @@ public:
*/
void write (const std::string &fn) const;
/**
* @brief Converts the shape collection to a deep one using the specified layer
*/
virtual void convert_to_deep (const db::DeepLayer &layer);
/**
* @brief Implementation of the ShapeCollection interface
*/

View File

@ -630,9 +630,9 @@ static bool is_deep (const db::EdgePairs *ep)
return dynamic_cast<const db::DeepEdgePairs *> (ep->delegate ()) != 0;
}
static size_t id (const db::EdgePairs *ep)
static size_t data_id (const db::EdgePairs *ep)
{
return tl::id_of (ep->delegate ());
return ep->delegate ()->data_id ();
}
static db::EdgePairs filtered (const db::EdgePairs *r, const gsi::EdgePairFilterBase *f)
@ -1114,7 +1114,7 @@ Class<db::EdgePairs> decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs",
"\n"
"This method has been added in version 0.26."
) +
method_ext ("data_id", &id,
method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
"\n"
"This method has been added in version 0.26."

View File

@ -892,9 +892,9 @@ static db::Edges *new_texts_as_dots2 (const db::RecursiveShapeIterator &si, db::
return new db::Edges (db::Region (si).texts_as_dots (pat, pattern, dss));
}
static size_t id (const db::Edges *e)
static size_t data_id (const db::Edges *e)
{
return tl::id_of (e->delegate ());
return e->delegate ()->data_id ();
}
static std::vector<db::Edges> andnot_with_edges (const db::Edges *r, const db::Edges &other)
@ -2572,7 +2572,7 @@ Class<db::Edges> decl_Edges (decl_dbShapeCollection, "db", "Edges",
"\n"
"This method has been added in version 0.26."
) +
method_ext ("data_id", &id,
method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
"\n"
"This method has been added in version 0.26."

View File

@ -1288,9 +1288,9 @@ static bool is_deep (const db::Region *region)
return dynamic_cast<const db::DeepRegion *> (region->delegate ()) != 0;
}
static size_t id (const db::Region *r)
static size_t data_id (const db::Region *r)
{
return tl::id_of (r->delegate ());
return r->delegate ()->data_id ();
}
@ -4214,7 +4214,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method has been added in version 0.26."
) +
method_ext ("data_id", &id,
method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
"\n"
"This method has been added in version 0.26."

View File

@ -477,9 +477,9 @@ static bool is_deep (const db::Texts *t)
return dynamic_cast<const db::DeepTexts *> (t->delegate ()) != 0;
}
static size_t id (const db::Texts *t)
static size_t data_id (const db::Texts *t)
{
return tl::id_of (t->delegate ());
return t->delegate ()->data_id ();
}
static db::Texts filtered (const db::Texts *r, const gsi::TextFilterBase *f)
@ -686,7 +686,7 @@ Class<db::Texts> decl_Texts (decl_dbShapeCollection, "db", "Texts",
method_ext ("is_deep?", &is_deep,
"@brief Returns true if the edge pair collection is a deep (hierarchical) one\n"
) +
method_ext ("data_id", &id,
method_ext ("data_id", &data_id,
"@brief Returns the data ID (a unique identifier for the underlying data storage)\n"
) +
method ("+|join", &db::Texts::operator+, gsi::arg ("other"),

View File

@ -1009,3 +1009,34 @@ TEST(100_UndoOfDeleteLayer)
EXPECT_EQ (l.get_properties (li).to_string (), "1/0");
EXPECT_EQ (l.get_properties (li2).to_string (), "2/0");
}
// issue #2343
TEST(101_CopyTreeDoesNotModifyPolygons)
{
db::Manager m;
db::Layout l (&m);
db::Cell &top = l.cell (l.add_cell ("TOP"));
l.insert_layer (db::LayerProperties (1, 0));
std::unique_ptr<db::Layout> t (new db::Layout ());
db::Cell &ttop = t->cell (t->add_cell ("TOP"));
unsigned int tl1 = t->insert_layer (db::LayerProperties (1, 0));
std::vector<db::Point> pts = {
{ 0, 0 }, { 0, 1000 }, { 500, 1000 }, { 1500, 1000 }, { 1000, 1000 }, { 1000, 0 }
};
db::Polygon poly;
poly.assign_hull (pts.begin (), pts.end (), false /*don't compress*/, false /*don't remove reflected*/);
ttop.shapes (tl1).insert (poly);
EXPECT_EQ (l2s (*t), "begin_lib 0.001\nbegin_cell {TOP}\nboundary 1 0 {0 0} {0 1000} {500 1000} {1500 1000} {1000 1000} {1000 0} {0 0}\nend_cell\nend_lib\n");
db::CellMapping cm;
cm.create_single_mapping (l, top.cell_index (), *t, ttop.cell_index ());
l.copy_tree_shapes (*t, cm);
// polygon is still not normalized
EXPECT_EQ (l2s (l), "begin_lib 0.001\nbegin_cell {TOP}\nboundary 1 0 {0 0} {0 1000} {500 1000} {1500 1000} {1000 1000} {1000 0} {0 0}\nend_cell\nend_lib\n");
}

View File

@ -34,6 +34,7 @@
#include "dbTestSupport.h"
#include "dbFileBasedLibrary.h"
#include "dbColdProxy.h"
#include "dbTextWriter.h"
#include "tlStream.h"
#include "tlStaticObjects.h"
#include "tlUnitTest.h"
@ -861,3 +862,64 @@ TEST(7_monsterlib)
// but the layout did not change
db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au5.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons));
}
namespace {
class PCellWithChildDeclaration :
public db::PCellDeclaration
{
void produce (const db::Layout &layout, const std::vector<unsigned int> & /*layer_ids*/, const db::pcell_parameters_type & /*parameters*/, db::Cell &cell) const
{
auto cid = const_cast<db::Layout &> (layout).add_cell ("CHILD");
db::PropertiesSet ps;
ps.insert ("id", tl::Variant ("my_id"));
auto ps_id = db::properties_id (ps);
cell.insert (db::CellInstArrayWithProperties (db::CellInstArray (cid, db::Trans ()), ps_id));
}
};
}
static std::string l2s (const db::Layout &layout)
{
tl::OutputStringStream os;
tl::OutputStream ostream (os);
db::TextWriter writer (ostream);
writer.write (layout);
return os.string ();
}
// PCells with subcells with properties
TEST(8_issue2344)
{
std::unique_ptr<db::Library> lib (new db::Library ());
lib->set_name ("__PCellLibrary");
lib->layout ().register_pcell ("PCell1", new PCellWithChildDeclaration ());
db::LibraryManager::instance ().register_lib (lib.get ());
db::Layout ly;
std::pair<bool, db::pcell_id_type> pc = lib->layout ().pcell_by_name ("PCell1");
tl_assert (pc.first);
db::cell_index_type lib_cell = lib->layout ().get_pcell_variant_dict (pc.second, std::map<std::string, tl::Variant> ());
ly.get_lib_proxy (lib.get (), lib_cell);
EXPECT_EQ (l2s (ly),
"begin_lib 0.001\n"
"begin_cell {CHILD}\n"
"end_cell\n"
"begin_cell {PCell1}\n"
"set props {\n"
" {{id} {my_id}}\n"
"}\n"
"srefp $props {CHILD} 0 0 1 {0 0}\n"
"end_cell\n"
"end_lib\n"
);
db::LibraryManager::instance ().delete_lib (lib.release ());
EXPECT (true);
}

View File

@ -323,7 +323,9 @@ module DRC
#
# If layers are not named, they will be given a name made from the
# \name_prefix and an incremental number when the layer is used for the
# first time.
# first time. As the default name prefix is "l", you may can name conflicts
# with auto-named layers, if you register explicit layer names like "l5".
# Consider changing the name prefix in that case to some prefix you are not using.
#
# \name can only be used once on a layer and the layer names must be
# unique (not taken by another layer).

View File

@ -1009,6 +1009,7 @@ Object::operator= (const img::Object &d)
m_trans = d.m_trans;
m_filename = d.m_filename;
m_tag = d.m_tag;
mp_data = d.mp_data;
if (mp_data) {
@ -1963,6 +1964,7 @@ void
Object::swap (Object &other)
{
m_filename.swap (other.m_filename);
m_tag.swap (other.m_tag);
std::swap (m_trans, other.m_trans);
std::swap (mp_data, other.mp_data);
std::swap (m_id, other.m_id);
@ -1979,6 +1981,12 @@ Object::swap (Object &other)
std::swap (m_updates_enabled, other.m_updates_enabled);
}
void
Object::set_tag (const std::string &tag)
{
m_tag = tag;
}
size_t
Object::width () const
{

View File

@ -581,6 +581,22 @@ public:
return d;
}
/**
* @brief Sets the tag string
*
* The tag string is an arbitrary string that can be used to identify
* the image. It is not persisted and it not considered for equality or sorting.
*/
void set_tag (const std::string &tag);
/**
* @brief Gets the tag string
*/
const std::string tag () const
{
return m_tag;
}
/**
* @brief Accessor to the width property
*/
@ -1036,6 +1052,7 @@ protected:
private:
std::string m_filename;
std::string m_tag;
db::Matrix3d m_trans;
DataHeader *mp_data;
size_t m_id;

View File

@ -314,46 +314,48 @@ PropertiesPage::min_max_value_changed ()
emit edited ();
}
bool
PropertiesPage::update_controls ()
{
bool has_error = false;
value_le->setText (QString ());
value_le->setEnabled (false);
colors->setEnabled (false_color_control->has_selection ());
colors->set_single_mode (false);
if (false_color_control->has_selection () && false_color_control->selected_node () > 0 && false_color_control->selected_node () < int (false_color_control->nodes ().size ()) - 1) {
double xmin, xmax;
get_xmin_xmax (xmin, xmax, has_error);
if (! has_error) {
double x = false_color_control->nodes () [false_color_control->selected_node ()].first;
double xx = x * (xmax - xmin) + xmin;
value_le->setText (tl::to_qstring (tl::sprintf ("%.4g", xx)));
value_le->setEnabled (true);
}
} else if (false_color_control->has_selection ()) {
colors->set_single_mode (true);
}
return has_error;
}
void
PropertiesPage::color_mapping_changed ()
{
if (! m_no_signals) {
bool has_error = false;
value_le->setText (QString ());
value_le->setEnabled (false);
colors->setEnabled (false_color_control->has_selection ());
colors->set_single_mode (false);
if (false_color_control->has_selection () && false_color_control->selected_node () > 0 && false_color_control->selected_node () < int (false_color_control->nodes ().size ()) - 1) {
double xmin, xmax;
get_xmin_xmax (xmin, xmax, has_error);
if (! has_error) {
double x = false_color_control->nodes () [false_color_control->selected_node ()].first;
double xx = x * (xmax - xmin) + xmin;
value_le->setText (tl::to_qstring (tl::sprintf ("%.4g", xx)));
value_le->setEnabled (true);
}
} else if (false_color_control->has_selection ()) {
colors->set_single_mode (true);
}
if (! has_error) {
m_in_color_mapping_signal = true;
emit edited ();
m_in_color_mapping_signal = false;
}
if (! m_no_signals && ! update_controls ()) {
m_in_color_mapping_signal = true;
emit edited ();
m_in_color_mapping_signal = false;
}
}
@ -509,6 +511,7 @@ PropertiesPage::update ()
m_no_signals = false;
update_controls ();
recompute_histogram ();
}

View File

@ -101,6 +101,7 @@ private:
void invalidate ();
void init ();
void get_xmin_xmax (double &xmin, double &xmax, bool &has_error_out);
bool update_controls ();
};
}

View File

@ -1658,6 +1658,12 @@ GuiApplication::event (QEvent *event)
return QApplication::event(event);
}
static void atexit_handler ()
{
if (lay::ApplicationBase::instance ()) {
lay::ApplicationBase::instance ()->shutdown ();
}
}
int
GuiApplication::exec ()
@ -1693,6 +1699,10 @@ GuiApplication::exec ()
}
// register an exit handler to shutdown cleanly in case of an explicit exit
// inside the code
::atexit (&atexit_handler);
return QApplication::exec ();
}

View File

@ -105,6 +105,15 @@ public:
*/
void exit (int result);
/**
* @brief Shut down the application
*
* Calling this function will close the main window and
* prepare for exit. Unlike "exit", it dows not actually exit
* the process.
*/
virtual void shutdown ();
/**
* @brief Return the program's version
*/
@ -330,7 +339,6 @@ public:
protected:
virtual void setup () = 0;
virtual void shutdown ();
virtual void prepare_recording (const std::string &gtf_record, bool gtf_record_incremental);
virtual void start_recording ();
virtual lay::Dispatcher *dispatcher () const = 0;

View File

@ -168,12 +168,14 @@ BEGIN_PROTECTED
} else if (rb_shapes->isChecked ()) {
lay::CellView ccv = view ()->cellview (cb_layer->cv_index ());
int sel_layer = cb_layer->current_layer ();
if (sel_layer < 0 || ! cv->layout ().is_valid_layer (sel_layer)) {
if (! ccv.is_valid () || sel_layer < 0 || ! ccv->layout ().is_valid_layer (sel_layer)) {
throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected to get clip boxes from")));
}
db::collect_clip_boxes (cv->layout (), cv.cell_index (), (unsigned int) sel_layer, clip_boxes);
db::collect_clip_boxes (ccv->layout (), ccv.cell_index (), (unsigned int) sel_layer, clip_boxes);
}

View File

@ -165,7 +165,6 @@ show_dock_widget (QDockWidget *dock_widget, bool visible)
MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled)
: QMainWindow (0),
tl::Object (),
lay::DispatcherDelegate (),
m_dispatcher (this),
m_text_progress (this, 10 /*verbosity threshold*/),

View File

@ -94,7 +94,6 @@ class ProgressWidget;
*/
class LAY_PUBLIC MainWindow
: public QMainWindow,
public tl::Object,
public gsi::ObjectBase,
public lay::DispatcherDelegate
{

View File

@ -49,6 +49,7 @@ class ConfigureAction;
* @brief A delegate by which the dispatcher can submit notification events
*/
class LAYBASIC_PUBLIC DispatcherDelegate
: public tl::Object
{
public:
/**
@ -271,7 +272,7 @@ private:
#if defined(HAVE_QT)
QWidget *mp_menu_parent_widget;
#endif
DispatcherDelegate *mp_delegate;
tl::weak_ptr<DispatcherDelegate> mp_delegate;
};
}

View File

@ -252,7 +252,7 @@ public:
/**
* @brief Gets a value indicating whether the page is for that specific plugin (given by a declaration object)
*/
bool for_plugin_declaration (const lay::PluginDeclaration *pd)
bool for_plugin_declaration (const lay::PluginDeclaration *pd) const
{
return m_plugin_declarations.find (pd) != m_plugin_declarations.end ();
}

View File

@ -947,8 +947,10 @@ LayoutCanvas::screenshot ()
void
LayoutCanvas::resize_event (unsigned int width, unsigned int height)
{
unsigned int w = width * dpr () + 0.5, h = height * dpr () + 0.5;
unsigned int wl = width * m_oversampling * dpr () + 0.5, hl = height * m_oversampling * dpr () + 0.5;
unsigned int w = (unsigned int) ceil (width * dpr () - db::epsilon);
unsigned int h = (unsigned int) ceil (height * dpr () - db::epsilon);
unsigned int wl = w * m_oversampling;
unsigned int hl = h * m_oversampling;
if (m_viewport.width () != w || m_viewport.height () != h ||
m_viewport_l.width () != wl || m_viewport_l.height () != hl) {
@ -957,8 +959,8 @@ LayoutCanvas::resize_event (unsigned int width, unsigned int height)
m_image_cache.clear ();
// set the viewport to the new size
m_viewport.set_size (width * dpr () + 0.5, height * dpr () + 0.5);
m_viewport_l.set_size (width * m_oversampling * dpr () + 0.5, height * m_oversampling * dpr () + 0.5);
m_viewport.set_size (w, h);
m_viewport_l.set_size (wl, hl);
mouse_event_trans (db::DCplxTrans (1.0 / dpr ()) * m_viewport.trans ());
do_redraw_all (true);

View File

@ -2225,6 +2225,14 @@ public:
return pi;
}
/**
* @brief Gets the active plugin
*/
lay::Plugin *active_plugin () const
{
return mp_active_plugin;
}
/**
* @brief Create a plugin of the given type
*
@ -3214,11 +3222,6 @@ protected:
void init (db::Manager *mgr);
lay::Plugin *active_plugin () const
{
return mp_active_plugin;
}
virtual LayoutView *get_ui ();
bool configure (const std::string &name, const std::string &value);

View File

@ -48,6 +48,12 @@ public:
options.push_back (std::pair<std::string, std::string> (cfg_layers_always_show_ld, "true"));
options.push_back (std::pair<std::string, std::string> (cfg_layers_always_show_layout_index, "false"));
options.push_back (std::pair<std::string, std::string> (cfg_test_shapes_in_view, "false"));
options.push_back (std::pair<std::string, std::string> (cfg_layer_search_as_expressions, "true"));
options.push_back (std::pair<std::string, std::string> (cfg_layer_search_as_filter, "false"));
options.push_back (std::pair<std::string, std::string> (cfg_layer_search_case_sensitive, "true"));
options.push_back (std::pair<std::string, std::string> (cfg_cell_search_as_expressions, "true"));
options.push_back (std::pair<std::string, std::string> (cfg_cell_search_as_filter, "false"));
options.push_back (std::pair<std::string, std::string> (cfg_cell_search_case_sensitive, "true"));
options.push_back (std::pair<std::string, std::string> (cfg_flat_cell_list, "false"));
options.push_back (std::pair<std::string, std::string> (cfg_split_cell_list, "false"));
options.push_back (std::pair<std::string, std::string> (cfg_cell_list_sorting, "by-name"));

View File

@ -437,9 +437,12 @@ MoveService::drag_cancel ()
{
m_shift = db::DPoint ();
if (m_dragging) {
show_toolbox (false);
ui ()->ungrab_mouse (this);
m_dragging = false;
}
}

View File

@ -332,6 +332,14 @@ public:
{
return new SelectionService (view);
}
virtual std::vector<std::string> additional_editor_options_pages () const
{
std::vector<std::string> names;
// TODO: provide in a central place instead of borrowing from the edt module
names.push_back ("GenericEditorOptions");
return names;
}
};
static tl::RegisteredClass<lay::PluginDeclaration> selection_service_decl (new SelectionServiceDeclaration (), -980, "laybasic::SelectionServicePlugin");

View File

@ -135,6 +135,13 @@ static const std::string cfg_layers_always_show_layout_index ("layers-always-sho
static const std::string cfg_reader_options_show_always ("reader-options-show-always");
static const std::string cfg_tip_window_hidden ("tip-window-hidden");
static const std::string cfg_layer_search_as_expressions ("layer-search-as-expressions");
static const std::string cfg_layer_search_as_filter ("layer-search-as-filter");
static const std::string cfg_layer_search_case_sensitive ("layer-search-case-sensitive");
static const std::string cfg_cell_search_as_expressions ("cell-search-as-expressions");
static const std::string cfg_cell_search_as_filter ("cell-search-as-filter");
static const std::string cfg_cell_search_case_sensitive ("cell-search-case-sensitive");
static const std::string cfg_bitmap_oversampling ("bitmap-oversampling");
static const std::string cfg_highres_mode ("highres-mode");
static const std::string cfg_subres_mode ("subres-mode");

View File

@ -264,7 +264,7 @@ HierarchyControlPanel::HierarchyControlPanel (lay::LayoutViewBase *view, QWidget
mp_search_edit_box->set_escape_signal_enabled (true);
mp_search_edit_box->set_tab_signal_enabled (true);
connect (mp_search_edit_box, SIGNAL (returnPressed ()), this, SLOT (search_editing_finished ()));
connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited ()));
connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited_no_signal ()));
connect (mp_search_edit_box, SIGNAL (esc_pressed ()), this, SLOT (search_editing_finished ()));
connect (mp_search_edit_box, SIGNAL (tab_pressed ()), this, SLOT (search_next ()));
connect (mp_search_edit_box, SIGNAL (backtab_pressed ()), this, SLOT (search_prev ()));
@ -452,8 +452,42 @@ HierarchyControlPanel::search_triggered (const QString &t)
}
}
void
HierarchyControlPanel::set_search_as_filter (bool f)
{
if (f != search_as_filter ()) {
mp_filter->setChecked (f);
search_edited_no_signal ();
}
}
void
HierarchyControlPanel::set_search_case_sensitive (bool f)
{
if (f != search_case_sensitive ()) {
mp_case_sensitive->setChecked (f);
search_edited_no_signal ();
}
}
void
HierarchyControlPanel::set_search_as_expression (bool f)
{
if (f != search_as_expression ()) {
mp_use_regular_expressions->setChecked (f);
search_edited_no_signal ();
}
}
void
HierarchyControlPanel::search_edited ()
{
search_edited_no_signal ();
emit search_options_changed ();
}
void
HierarchyControlPanel::search_edited_no_signal ()
{
bool filter_invalid = false;

View File

@ -152,6 +152,52 @@ public:
return m_active_index;
}
/**
* @brief Sets the "as_filter" flag for the search feature
*
* If this flag is set, search expressions are applied as filter
*/
void set_search_as_filter (bool f);
/**
* @brief Gets the "search_as_filter" flag
*/
bool search_as_filter ()
{
return mp_filter->isChecked ();
}
/**
* @brief Sets the "case_sensitive" flag for the search feature
*
* If this flag is set, search expressions are case sensitive
*/
void set_search_case_sensitive (bool f);
/**
* @brief Gets the "case_sensitive" flag for the search feature
*/
bool search_case_sensitive ()
{
return mp_case_sensitive->isChecked ();
}
/**
* @brief Sets the "as_expression" flag for the search feature
*
* If this flag is set, search expressions are handled as glob expressions
*/
void set_search_as_expression (bool f);
/**
* @brief Gets the "as_expression" flag for the search feature
*/
bool search_as_expression ()
{
return mp_use_regular_expressions->isChecked ();
}
/**
* @brief Returns the paths of the selected cells
*/
@ -273,6 +319,7 @@ public:
signals:
void cell_selected (cell_path_type path, int cellview_index);
void active_cellview_changed (int cellview_index);
void search_options_changed ();
public slots:
void clicked (const QModelIndex &index);
@ -283,6 +330,7 @@ public slots:
void context_menu (const QPoint &pt);
void search_triggered (const QString &t);
void search_edited ();
void search_edited_no_signal ();
void search_editing_finished ();
void search_next ();
void search_prev ();

View File

@ -257,7 +257,7 @@ LayerControlPanel::LayerControlPanel (lay::LayoutViewBase *view, db::Manager *ma
mp_search_edit_box->set_escape_signal_enabled (true);
mp_search_edit_box->set_tab_signal_enabled (true);
connect (mp_search_edit_box, SIGNAL (returnPressed ()), this, SLOT (search_editing_finished ()));
connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited ()));
connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited_no_signal ()));
connect (mp_search_edit_box, SIGNAL (esc_pressed ()), this, SLOT (search_editing_finished ()));
connect (mp_search_edit_box, SIGNAL (tab_pressed ()), this, SLOT (search_next ()));
connect (mp_search_edit_box, SIGNAL (backtab_pressed ()), this, SLOT (search_prev ()));
@ -1160,12 +1160,46 @@ LayerControlPanel::search_triggered (const QString &t)
mp_search_frame->show ();
mp_search_edit_box->setText (t);
mp_search_edit_box->setFocus ();
search_edited ();
search_edited_no_signal ();
}
}
void
LayerControlPanel::set_search_as_filter (bool f)
{
if (f != search_as_filter ()) {
mp_filter->setChecked (f);
search_edited_no_signal ();
}
}
void
LayerControlPanel::set_search_case_sensitive (bool f)
{
if (f != search_case_sensitive ()) {
mp_case_sensitive->setChecked (f);
search_edited_no_signal ();
}
}
void
LayerControlPanel::set_search_as_expression (bool f)
{
if (f != search_as_expression ()) {
mp_use_regular_expressions->setChecked (f);
search_edited_no_signal ();
}
}
void
LayerControlPanel::search_edited ()
{
search_edited_no_signal ();
emit search_options_changed ();
}
void
LayerControlPanel::search_edited_no_signal ()
{
if (! mp_model) {
return;

View File

@ -215,6 +215,51 @@ public:
return mp_model->get_test_shapes_in_view ();
}
/**
* @brief Sets the "as_filter" flag for the search feature
*
* If this flag is set, search expressions are applied as filter
*/
void set_search_as_filter (bool f);
/**
* @brief Gets the "search_as_filter" flag
*/
bool search_as_filter ()
{
return mp_filter->isChecked ();
}
/**
* @brief Sets the "case_sensitive" flag for the search feature
*
* If this flag is set, search expressions are case sensitive
*/
void set_search_case_sensitive (bool f);
/**
* @brief Gets the "case_sensitive" flag for the search feature
*/
bool search_case_sensitive ()
{
return mp_case_sensitive->isChecked ();
}
/**
* @brief Sets the "as_expression" flag for the search feature
*
* If this flag is set, search expressions are handled as glob expressions
*/
void set_search_as_expression (bool f);
/**
* @brief Gets the "as_expression" flag for the search feature
*/
bool search_as_expression ()
{
return mp_use_regular_expressions->isChecked ();
}
/**
* @brief Set the animation phase
*/
@ -297,6 +342,7 @@ signals:
void tab_changed ();
void current_layer_changed (const lay::LayerPropertiesConstIterator &iter);
void selected_layers_changed ();
void search_options_changed ();
public slots:
void cm_new_tab ();
@ -344,6 +390,7 @@ public slots:
void downdown_clicked ();
void search_triggered (const QString &t);
void search_edited ();
void search_edited_no_signal ();
void search_editing_finished ();
void search_next ();
void search_prev ();

View File

@ -848,6 +848,12 @@ LayerSelectionComboBox::is_no_layer_selected () const
return currentIndex () < 0;
}
int
LayerSelectionComboBox::cv_index () const
{
return mp_private->cv_index;
}
int
LayerSelectionComboBox::current_layer () const
{

View File

@ -307,6 +307,14 @@ public:
*/
bool is_no_layer_selected () const;
/**
* @brief Gets the cellview index
*
* NOTE: this methods returns -1 if the widget is not
* associated with a cellview index.
*/
int cv_index () const;
/**
* @brief Get the current layer (index)
*

View File

@ -120,8 +120,13 @@ EditorOptionsPages::editor_options_pages ()
bool
EditorOptionsPages::has_content () const
{
lay::Plugin *plugin = mp_view->active_plugin ();
if (! plugin) {
return false;
}
for (auto p = m_pages.begin (); p != m_pages.end (); ++p) {
if (p->active () && ! p->is_modal_page () && ! p->is_toolbox_widget ()) {
if (! p->is_modal_page () && ! p->is_toolbox_widget () && p->for_plugin_declaration (plugin->plugin_declaration ())) {
return true;
}
}
@ -131,8 +136,13 @@ EditorOptionsPages::has_content () const
bool
EditorOptionsPages::has_modal_content () const
{
lay::Plugin *plugin = mp_view->active_plugin ();
if (! plugin) {
return false;
}
for (auto p = m_pages.begin (); p != m_pages.end (); ++p) {
if (p->active () && p->is_modal_page () && ! p->is_toolbox_widget ()) {
if (p->is_modal_page () && ! p->is_toolbox_widget () && p->for_plugin_declaration (plugin->plugin_declaration ())) {
return true;
}
}
@ -173,7 +183,7 @@ EditorOptionsPages::activate (const lay::Plugin *plugin)
bool is_active = plugin && op->for_plugin_declaration (plugin->plugin_declaration ());
// The zero order page is picked as the initial one
if (is_active && ! op->active () && op->order () == 0 && page == 0) {
if (is_active && ! op->active () && (op->order () == 0 || page == 0)) {
page = op.operator-> ();
}

View File

@ -404,6 +404,16 @@ void LayoutViewSignalConnector::layer_order_changed ()
mp_view->layer_order_changed ();
}
void LayoutViewSignalConnector::layer_search_options_edited ()
{
mp_view->layer_search_options_edited ();
}
void LayoutViewSignalConnector::cell_search_options_edited ()
{
mp_view->cell_search_options_edited ();
}
void LayoutViewSignalConnector::min_hier_changed (int i)
{
mp_view->min_hier_changed (i);
@ -573,6 +583,7 @@ LayoutView::init_ui (db::Manager *mgr)
mp_hierarchy_panel = new lay::HierarchyControlPanel (this, hierarchy_frame, "hcp");
left_frame_ly->addWidget (mp_hierarchy_panel, 1 /*stretch*/);
QObject::connect (mp_hierarchy_panel, SIGNAL (search_options_changed ()), mp_connector, SLOT (cell_search_options_edited ()));
QObject::connect (mp_hierarchy_panel, SIGNAL (cell_selected (cell_path_type, int)), mp_connector, SLOT (select_cell_dispatch (cell_path_type, int)));
QObject::connect (mp_hierarchy_panel, SIGNAL (active_cellview_changed (int)), mp_connector, SLOT (active_cellview_changed (int)));
QObject::connect (mp_hierarchy_frame, SIGNAL (destroyed ()), mp_connector, SLOT (side_panel_destroyed ()));
@ -655,6 +666,7 @@ LayoutView::init_ui (db::Manager *mgr)
mp_control_frame = mp_control_panel;
QObject::connect (mp_control_frame, SIGNAL (destroyed ()), mp_connector, SLOT (side_panel_destroyed ()));
QObject::connect (mp_control_frame, SIGNAL (search_options_changed ()), mp_connector, SLOT (layer_search_options_edited ()));
QObject::connect (mp_control_panel, SIGNAL (tab_changed ()), mp_connector, SLOT (layer_tab_changed ()));
QObject::connect (mp_control_panel, SIGNAL (order_changed ()), mp_connector, SLOT (layer_order_changed ()));
QObject::connect (mp_control_panel, SIGNAL (current_layer_changed (const lay::LayerPropertiesConstIterator &)), mp_connector, SLOT (current_layer_changed_slot (const lay::LayerPropertiesConstIterator &)));
@ -1041,6 +1053,60 @@ LayoutView::configure (const std::string &name, const std::string &value)
}
return true;
} else if (name == cfg_layer_search_as_expressions) {
bool f;
tl::from_string (value, f);
if (mp_control_panel) {
mp_control_panel->set_search_as_expression (f);
}
return true;
} else if (name == cfg_layer_search_as_filter) {
bool f;
tl::from_string (value, f);
if (mp_control_panel) {
mp_control_panel->set_search_as_filter (f);
}
return true;
} else if (name == cfg_layer_search_case_sensitive) {
bool f;
tl::from_string (value, f);
if (mp_control_panel) {
mp_control_panel->set_search_case_sensitive (f);
}
return true;
} else if (name == cfg_cell_search_as_expressions) {
bool f;
tl::from_string (value, f);
if (mp_hierarchy_panel) {
mp_hierarchy_panel->set_search_as_expression (f);
}
return true;
} else if (name == cfg_cell_search_as_filter) {
bool f;
tl::from_string (value, f);
if (mp_hierarchy_panel) {
mp_hierarchy_panel->set_search_as_filter (f);
}
return true;
} else if (name == cfg_cell_search_case_sensitive) {
bool f;
tl::from_string (value, f);
if (mp_hierarchy_panel) {
mp_hierarchy_panel->set_search_case_sensitive (f);
}
return true;
} else if (name == cfg_layers_always_show_source) {
bool a = false;
@ -1299,6 +1365,26 @@ LayoutView::max_hier_changed (int i)
set_hier_levels (std::make_pair (get_hier_levels ().first, i));
}
void
LayoutView::layer_search_options_edited ()
{
if (mp_control_panel) {
dispatcher ()->config_set (cfg_layer_search_as_expressions, mp_control_panel->search_as_expression ());
dispatcher ()->config_set (cfg_layer_search_case_sensitive, mp_control_panel->search_case_sensitive ());
dispatcher ()->config_set (cfg_layer_search_as_filter, mp_control_panel->search_as_filter ());
}
}
void
LayoutView::cell_search_options_edited ()
{
if (mp_hierarchy_panel) {
dispatcher ()->config_set (cfg_cell_search_as_expressions, mp_hierarchy_panel->search_as_expression ());
dispatcher ()->config_set (cfg_cell_search_case_sensitive, mp_hierarchy_panel->search_case_sensitive ());
dispatcher ()->config_set (cfg_cell_search_as_filter, mp_hierarchy_panel->search_as_filter ());
}
}
tl::Color
LayoutView::default_background_color ()
{

View File

@ -114,6 +114,8 @@ public slots:
void layer_tab_changed ();
void layer_order_changed ();
void select_cell_dispatch (const cell_path_type &path, int cellview_index);
void layer_search_options_edited ();
void cell_search_options_edited ();
void min_hier_changed (int i);
void max_hier_changed (int i);
void app_terminated ();
@ -654,6 +656,8 @@ private:
void layer_order_changed ();
void min_hier_changed (int i);
void max_hier_changed (int i);
void layer_search_options_edited ();
void cell_search_options_edited ();
bool event_filter (QObject *obj, QEvent *ev, bool &taken);
QSize size_hint () const;

View File

@ -362,3 +362,9 @@ TEST(63_FlagMissingPorts)
run_test (_this, "flag_missing_ports", "flag_missing_ports.gds", false, true, "TOP");
}
// Split substrate - marker and global connection (issue #2345)
TEST(64_SplitSubstrate)
{
run_test (_this, "split_substrate", "split_substrate.gds", true, false /*no LVS*/);
}

View File

@ -0,0 +1,6 @@
TEMPLATE = subdirs
!equals(HAVE_QT, "0") {
SUBDIRS = lay_plugin
}

View File

@ -0,0 +1,600 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DensityMapDialog</class>
<widget class="QDialog" name="DensityMapDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>657</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>Density Map</string>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>9</number>
</property>
<item>
<widget class="QFrame" name="frame_3">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Density map parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QLineEdit" name="le_pixel_size">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>100</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Pixel size</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="le_window_size">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>µm</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="sb_threads">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Averaging window size</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Threads</string>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>353</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2" colspan="2">
<widget class="QLabel" name="label_10">
<property name="text">
<string>µm (a multiple of the pixel size or empty)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Boundary mode</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="3">
<widget class="QComboBox" name="cb_boundary_mode">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>Periodic (repeat region seamlessly)</string>
</property>
</item>
<item>
<property name="text">
<string>Use zero density outside</string>
</property>
</item>
<item>
<property name="text">
<string>Use 100% density outside</string>
</property>
</item>
<item>
<property name="text">
<string>Use average density outside</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Compute density of shapes on</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2" colspan="2">
<widget class="QStackedWidget" name="layer_stack">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="page_3">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="lay::LayerSelectionComboBox" name="cb_source_layer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>385</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
<layout class="QHBoxLayout" name="horizontalLayout_3"/>
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="layer_cb">
<item>
<property name="text">
<string>Layer</string>
</property>
</item>
<item>
<property name="text">
<string>Visible layers</string>
</property>
</item>
<item>
<property name="text">
<string>Selected layers</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Compute density map of region</string>
</property>
<layout class="QGridLayout">
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0">
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>0</number>
</property>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item row="0" column="2" rowspan="2" colspan="2">
<widget class="QStackedWidget" name="region_stack">
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="lay::LayerSelectionComboBox" name="cb_box_layer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>351</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="region_box_group">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Box Boundaries (all values in µm)</string>
</property>
<layout class="QGridLayout" name="_2">
<property name="margin" stdset="0">
<number>9</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="3">
<widget class="QLabel" name="label_6">
<property name="text">
<string>y =</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>2nd corner</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>x =</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>1st corner</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>x =</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_2">
<property name="text">
<string>y =</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLineEdit" name="le_y2"/>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="le_x2"/>
</item>
<item row="0" column="4">
<widget class="QLineEdit" name="le_y1"/>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="le_x1"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_5"/>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="region_cb">
<item>
<property name="text">
<string>Global bounding box</string>
</property>
</item>
<item>
<property name="text">
<string>Bounding box of layer ...</string>
</property>
</item>
<item>
<property name="text">
<string>Single box with ..</string>
</property>
</item>
<item>
<property name="text">
<string>Visible region</string>
</property>
</item>
<item>
<property name="text">
<string>Defined by rulers</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>lay::LayerSelectionComboBox</class>
<extends>QComboBox</extends>
<header>layWidgets.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>le_pixel_size</tabstop>
<tabstop>sb_threads</tabstop>
<tabstop>layer_cb</tabstop>
<tabstop>cb_source_layer</tabstop>
<tabstop>region_cb</tabstop>
<tabstop>cb_box_layer</tabstop>
<tabstop>le_x1</tabstop>
<tabstop>le_y1</tabstop>
<tabstop>le_x2</tabstop>
<tabstop>le_y2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>DensityMapDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>DensityMapDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,765 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2026 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 "layDensityMapDialog.h"
#include "dbClip.h"
#include "dbTilingProcessor.h"
#include "antService.h"
#include "tlException.h"
#include "tlString.h"
#include "tlExceptions.h"
#include "layUtils.h"
#include "imgObject.h"
#include "imgService.h"
namespace lay
{
static const std::string layer_mode_specific ("layer-mode-specific");
static const std::string layer_mode_visible ("layer-mode-visible");
static const std::string layer_mode_selected ("layer-mode-selected");
// items in layer_cb
static const std::vector<std::string> layer_modes = { layer_mode_specific, layer_mode_visible, layer_mode_selected };
static const std::string region_mode_global_bbox ("region-mode-global-bbox");
static const std::string region_mode_layer_bbox ("region-mode-layer-bbox");
static const std::string region_mode_single_box ("region-mode-single-box");
static const std::string region_mode_visible_region ("region-mode-visible-region");
static const std::string region_mode_by_rulers ("region-mode-by-rulers");
// items in region_cb
static const std::vector<std::string> region_modes = {
region_mode_global_bbox,
region_mode_layer_bbox,
region_mode_single_box,
region_mode_visible_region,
region_mode_by_rulers
};
static const std::string boundary_mode_periodic ("boundary-mode-periodic");
static const std::string boundary_mode_zero ("boundary-mode-zero");
static const std::string boundary_mode_one ("boundary-mode-one");
static const std::string boundary_mode_average ("boundary-mode-average");
// items in boundary_mode_cb
static const std::vector<std::string> boundary_modes = { boundary_mode_periodic, boundary_mode_zero, boundary_mode_one, boundary_mode_average };
static const std::string cfg_density_map_region_mode ("density-map-region-mode");
static const std::string cfg_density_map_layer_mode ("density-map-layer-mode");
static const std::string cfg_density_map_pixel_size ("density-map-pixel-size");
static const std::string cfg_density_map_window_size ("density-map-window-size");
static const std::string cfg_density_map_boundary_mode ("density-map-boundary-mode");
static const std::string cfg_density_map_threads ("density-map-threads");
static const std::string cfg_density_map_source_layer ("density-map-source-layer");
static const std::string cfg_density_map_box_layer ("density-map-box-layer");
static const std::string cfg_density_map_single_box ("density-map-single-box");
// ------------------------------------------------------------
// Declaration of the configuration options
class DensityMapDialogPluginDeclaration
: public lay::PluginDeclaration
{
public:
virtual void get_options (std::vector < std::pair<std::string, std::string> > &options) const
{
options.push_back (std::make_pair (cfg_density_map_layer_mode, layer_mode_specific));
options.push_back (std::make_pair (cfg_density_map_region_mode, region_mode_global_bbox));
options.push_back (std::make_pair (cfg_density_map_pixel_size, "100"));
options.push_back (std::make_pair (cfg_density_map_window_size, "0"));
options.push_back (std::make_pair (cfg_density_map_boundary_mode, boundary_mode_periodic));
options.push_back (std::make_pair (cfg_density_map_threads, "1"));
}
virtual lay::ConfigPage *config_page (QWidget * /*parent*/, std::string & /*title*/) const
{
return 0; // .. no config page yet ..
}
virtual void get_menu_entries (std::vector<lay::MenuEntry> &menu_entries) const
{
lay::PluginDeclaration::get_menu_entries (menu_entries);
menu_entries.push_back (lay::menu_item ("density_map::show", "density_map_tool:edit", "tools_menu.post_verification_group", tl::to_string (QObject::tr ("Density Map"))));
}
virtual lay::Plugin *create_plugin (db::Manager *, lay::Dispatcher *root, lay::LayoutViewBase *view) const
{
if (lay::has_gui ()) {
return new DensityMapDialog (root, view);
} else {
return 0;
}
}
};
static tl::RegisteredClass<lay::PluginDeclaration> config_decl (new DensityMapDialogPluginDeclaration (), 3002, "lay::DensityMapPlugin");
// ------------------------------------------------------------
DensityMapDialog::DensityMapDialog (lay::Dispatcher *root, LayoutViewBase *vw)
: lay::Browser (root, vw),
Ui::DensityMapDialog ()
{
Ui::DensityMapDialog::setupUi (this);
connect (layer_cb, SIGNAL (activated (int)), SLOT (layer_mode_changed (int)));
connect (region_cb, SIGNAL (activated (int)), SLOT (region_mode_changed (int)));
connect (button_box->button (QDialogButtonBox::Apply), SIGNAL (clicked ()), this, SLOT (apply ()));
region_mode_changed (region_cb->currentIndex ());
layer_mode_changed (layer_cb->currentIndex ());
}
void
DensityMapDialog::region_mode_changed (int mode)
{
if (mode < 0 || mode >= int (region_modes.size ())) {
return;
}
const auto &rm = region_modes [mode];
if (rm == region_mode_layer_bbox) {
region_stack->setCurrentIndex (0);
} else if (rm == region_mode_single_box) {
region_stack->setCurrentIndex (1);
} else {
region_stack->setCurrentIndex (2);
}
}
void
DensityMapDialog::layer_mode_changed (int mode)
{
if (mode < 0 || mode >= int (layer_modes.size ())) {
return;
}
const auto &lm = layer_modes [mode];
if (lm == layer_mode_specific) {
layer_stack->setCurrentIndex (0);
} else {
layer_stack->setCurrentIndex (1);
}
}
void
DensityMapDialog::menu_activated (const std::string &symbol)
{
if (symbol == "density_map::show") {
int cv_index = view ()->active_cellview_index ();
lay::CellView cv = view ()->cellview (cv_index);
if (cv.is_valid ()) {
cb_box_layer->set_view (view (), cv_index);
cb_source_layer->set_view (view (), cv_index);
show ();
activate ();
}
} else {
lay::Browser::menu_activated (symbol);
}
}
DensityMapDialog::~DensityMapDialog ()
{
// .. nothing yet ..
}
namespace
{
class DensityMapTileReceiver
: public db::TileOutputReceiver
{
public:
DensityMapTileReceiver (img::Object *img)
: mp_img (img)
{
// .. nothing yet ..
}
virtual void put (size_t ix, size_t iy, const db::Box &tile, size_t /*id*/, const tl::Variant &obj, double /*dbu*/, const db::ICplxTrans & /*trans*/, bool /*clip*/)
{
if (tl::verbosity () >= 30) {
tl::info << "Density map value: " << ix << "," << iy << " " << tile.to_string () << " -> " << obj.to_string ();
}
mp_img->set_pixel (ix, iy, obj.to_double ());
}
private:
img::Object *mp_img;
};
}
void
DensityMapDialog::apply ()
{
BEGIN_PROTECTED
make_density_map ();
END_PROTECTED
}
void
DensityMapDialog::accept ()
{
BEGIN_PROTECTED
make_density_map ();
// close this dialog
QDialog::accept ();
END_PROTECTED
}
bool
DensityMapDialog::configure (const std::string &name, const std::string &value)
{
if (name == cfg_density_map_layer_mode) {
int mode = 0;
for (size_t i = 0; i < layer_modes.size (); ++i) {
if (layer_modes[i] == value) {
mode = int (i);
}
}
layer_cb->setCurrentIndex (mode);
layer_mode_changed (mode);
return true;
} else if (name == cfg_density_map_region_mode) {
int mode = 0;
for (size_t i = 0; i < region_modes.size (); ++i) {
if (region_modes[i] == value) {
mode = int (i);
}
}
region_cb->setCurrentIndex (mode);
region_mode_changed (mode);
return true;
} else if (name == cfg_density_map_pixel_size) {
double px = 100.0;
try {
tl::from_string (value, px);
le_pixel_size->setText (tl::to_qstring (tl::to_string (px)));
} catch (...) {
}
return true;
} else if (name == cfg_density_map_window_size) {
double ws = 0.0;
try {
tl::from_string (value, ws);
if (ws > 0) {
le_window_size->setText (tl::to_qstring (tl::to_string (ws)));
} else {
le_window_size->setText (QString ());
}
} catch (...) {
}
return true;
} else if (name == cfg_density_map_boundary_mode) {
int mode = 0;
for (size_t i = 0; i < boundary_modes.size (); ++i) {
if (boundary_modes[i] == value) {
mode = int (i);
}
}
cb_boundary_mode->setCurrentIndex (mode);
return true;
} else if (name == cfg_density_map_threads) {
int thr = 1;
try {
tl::from_string (value, thr);
sb_threads->setValue (thr);
} catch (...) {
}
return true;
} else if (name == cfg_density_map_source_layer) {
db::LayerProperties lp;
try {
tl::from_string (value, lp);
cb_source_layer->set_current_layer (lp);
} catch (...) {
}
return true;
} else if (name == cfg_density_map_box_layer) {
db::LayerProperties lp;
try {
tl::from_string (value, lp);
cb_box_layer->set_current_layer (lp);
} catch (...) {
}
return true;
} else if (name == cfg_density_map_single_box) {
db::DBox bx;
try {
tl::from_string (value, bx);
if (bx.empty ()) {
le_x1->setText (QString ());
le_y1->setText (QString ());
le_x2->setText (QString ());
le_y2->setText (QString ());
} else {
le_x1->setText (tl::to_qstring (tl::to_string (bx.left ())));
le_y1->setText (tl::to_qstring (tl::to_string (bx.bottom ())));
le_x2->setText (tl::to_qstring (tl::to_string (bx.right ())));
le_y2->setText (tl::to_qstring (tl::to_string (bx.top ())));
}
} catch (...) {
}
return true;
} else {
return false;
}
}
void
DensityMapDialog::make_density_map ()
{
DensityMapParameters par;
par.threads = std::max (1, sb_threads->value ());
par.pixel_size = 0.0;
tl::from_string_ext (tl::to_string (le_pixel_size->text ()), par.pixel_size);
if (par.pixel_size < 1e-6) {
throw tl::Exception (tl::to_string (QObject::tr ("The pixel size must be positive and not zero")));
}
par.window_size = 0.0;
if (! le_window_size->text ().simplified ().isEmpty ()) {
tl::from_string_ext (tl::to_string (le_window_size->text ()), par.window_size);
if (par.window_size < 1e-6) {
throw tl::Exception (tl::to_string (QObject::tr ("The window size must be positive and not zero or empty")));
}
}
int boundary_mode = cb_boundary_mode->currentIndex ();
if (boundary_mode < 0 || boundary_mode >= int (boundary_modes.size ())) {
return;
}
par.boundary_mode = boundary_modes [boundary_mode];
int region_mode = region_cb->currentIndex ();
if (region_mode < 0 || region_mode >= int (region_modes.size ())) {
return;
}
const auto &rm = region_modes [region_mode];
int layer_mode = layer_cb->currentIndex ();
if (layer_mode < 0 || layer_mode >= int (layer_modes.size ())) {
return;
}
const auto &lm = layer_modes [layer_mode];
db::LayerProperties region_layer;
db::LayerProperties source_layer;
if (rm == region_mode_single_box) {
if (le_x1->text ().isEmpty () || le_x2->text ().isEmpty () ||
le_y1->text ().isEmpty () || le_y2->text ().isEmpty ()) {
throw tl::Exception (tl::to_string (QObject::tr ("All four coordinates of the clip box must be given")));
}
double x1 = 0.0, y1 = 0.0;
double x2 = 0.0, y2 = 0.0;
tl::from_string_ext (tl::to_string (le_x1->text ()), x1);
tl::from_string_ext (tl::to_string (le_x2->text ()), x2);
tl::from_string_ext (tl::to_string (le_y1->text ()), y1);
tl::from_string_ext (tl::to_string (le_y2->text ()), y2);
par.region = db::DBox (db::DPoint (x1, y1), db::DPoint (x2, y2));
} else if (rm == region_mode_by_rulers) {
ant::Service *ant_service = view ()->get_plugin <ant::Service> ();
if (ant_service) {
ant::AnnotationIterator ant = ant_service->begin_annotations ();
while (! ant.at_end ()) {
par.region += db::DBox (ant->p1 (), ant->p2 ());
++ant;
}
}
} else if (rm == region_mode_layer_bbox) {
lay::CellView ccv = view ()->cellview (cb_box_layer->cv_index ());
int sel_layer = cb_box_layer->current_layer ();
region_layer = cb_box_layer->current_layer_props ();
if (! ccv.is_valid () || sel_layer < 0 || ! ccv->layout ().is_valid_layer (sel_layer)) {
throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected to get clip boxes from")));
}
par.region = db::CplxTrans (ccv->layout ().dbu ()) * ccv->layout ().cell (ccv.cell_index ()).bbox (sel_layer);
} else if (rm == region_mode_visible_region) {
par.region = view ()->box ();
} else {
lay::CellView cv = view ()->cellview (view ()->active_cellview_index ());
par.region = db::CplxTrans (cv->layout ().dbu ()) * cv->layout ().cell (cv.cell_index ()).bbox ();
}
if (lm == layer_mode_specific) {
int cvi = cb_source_layer->cv_index ();
lay::CellView ccv = view ()->cellview (cvi);
int sel_layer = cb_source_layer->current_layer ();
source_layer = cb_source_layer->current_layer_props ();
if (! ccv.is_valid () || sel_layer < 0 || ! ccv->layout ().is_valid_layer (sel_layer)) {
throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected to get clip boxes from")));
}
par.input_layers.push_back (std::make_pair (cvi, sel_layer));
} else if (lm == layer_mode_visible) {
for (auto l = view ()->begin_layers (); !l.at_end (); ++l) {
if (! l->has_children () && l->visible (true)) {
int cvi = (l->cellview_index () >= 0) ? l->cellview_index () : 0;
lay::CellView ccv = view ()->cellview (cvi);
int li = l->layer_index ();
if (ccv.is_valid () || li >= 0 || ! ccv->layout ().is_valid_layer (li)) {
par.input_layers.push_back (std::make_pair (cvi, li));
}
}
}
} else if (lm == layer_mode_selected) {
auto sel = view ()->selected_layers ();
for (auto s = sel.begin (); s != sel.end (); ++s) {
auto l = *s;
if (! l->has_children ()) {
int cvi = (l->cellview_index () >= 0) ? l->cellview_index () : 0;
lay::CellView ccv = view ()->cellview (cvi);
int li = l->layer_index ();
if (ccv.is_valid () || li >= 0 || ! ccv->layout ().is_valid_layer (li)) {
par.input_layers.push_back (std::make_pair (cvi, li));
}
}
}
}
// Commit the parameters
dispatcher ()->config_set (cfg_density_map_layer_mode, lm);
dispatcher ()->config_set (cfg_density_map_region_mode, rm);
dispatcher ()->config_set (cfg_density_map_boundary_mode, par.boundary_mode);
dispatcher ()->config_set (cfg_density_map_threads, tl::to_string (par.threads));
dispatcher ()->config_set (cfg_density_map_pixel_size, tl::to_string (par.pixel_size));
dispatcher ()->config_set (cfg_density_map_window_size, tl::to_string (par.window_size));
if (lm == layer_mode_specific) {
dispatcher ()->config_set (cfg_density_map_source_layer, source_layer.to_string ());
}
if (rm == region_mode_layer_bbox) {
dispatcher ()->config_set (cfg_density_map_box_layer, region_layer.to_string ());
} else if (rm == region_mode_single_box) {
dispatcher ()->config_set (cfg_density_map_single_box, par.region.to_string ());
}
dispatcher ()->config_setup ();
compute_density_map (par);
}
void
DensityMapDialog::compute_density_map (const DensityMapParameters &par)
{
if (par.region.empty ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Density map region is empty")));
}
if (par.input_layers.empty ()) {
throw tl::Exception (tl::to_string (QObject::tr ("No input layers given")));
}
// Compute the tile origins
const double max_wh = 100000.0;
const int max_pixels = 100000000;
double dnx = std::max (1.0, ceil (par.region.width () / par.pixel_size - db::epsilon));
double dny = std::max (1.0, ceil (par.region.height () / par.pixel_size - db::epsilon));
if (dnx > max_wh || dny > max_wh) {
throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Density map dimensions exceed maximum limit of %g pixels in one direction"))), max_wh);
}
int nx = int (dnx);
int ny = int (dny);
if (dnx * dny > max_pixels) {
throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Density map array exceed maximum limit of %d pixels in total"))), max_pixels);
}
double x0 = par.region.center ().x () - nx * 0.5 * par.pixel_size;
double y0 = par.region.center ().y () - ny * 0.5 * par.pixel_size;
double dbu = view ()->cellview (par.input_layers.front ().first)->layout ().dbu ();
// Set up the tiling processor
db::TilingProcessor tp;
tp.tiles (nx, ny);
tp.tile_origin (x0, y0);
tp.tile_size (par.pixel_size, par.pixel_size);
tp.set_threads (par.threads);
tp.set_dbu (dbu);
int ninput = 1;
std::string in_expr;
for (auto i = par.input_layers.begin (); i != par.input_layers.end (); ++i, ++ninput) {
const lay::CellView &cv = view ()->cellview (i->first);
const db::Layout &ly = cv->layout ();
const db::Cell &cell = ly.cell (cv.cell_index ());
std::string in_name = "in" + tl::to_string (ninput);
tp.input (in_name, db::RecursiveShapeIterator (ly, cell, i->second, true), db::ICplxTrans (ly.dbu () / dbu), db::TilingProcessor::TypeRegion, true);
if (! in_expr.empty ()) {
in_expr += "+";
}
in_expr += in_name;
}
tp.queue (std::string ("var inp = ") + in_expr + "; _tile && _output(dens, to_f(inp.area(_tile.bbox)) / to_f(_tile.bbox.area))");
// Prepare the Image for receiving
img::Object *img_object = 0;
img::Service *img_service = view ()->get_plugin <img::Service> ();
if (img_service) {
const std::string img_tag = "density-map-image";
img::DataMapping dm;
dm.false_color_nodes.clear ();
// default mapping blue -> red
// TODO: we could use that from a previous image ...
dm.false_color_nodes.push_back (std::make_pair (0.0, std::make_pair (0x0000ff, 0x0000ff)));
dm.false_color_nodes.push_back (std::make_pair (1.0, std::make_pair (0xff0000, 0xff0000)));
for (auto i = img_service->begin_images (); ! i.at_end (); ++i) {
if (i->tag () == img_tag) {
// inherit data mapping from previous image
dm = i->data_mapping ();
img_service->erase_image_by_id (i->id ());
break;
}
}
img::Object img (nx, ny, db::DCplxTrans (par.pixel_size, 0.0, false, par.region.center () - db::DPoint ()), false, false);
img.set_data_mapping (dm);
img.set_tag (img_tag);
img_object = img_service->insert_image (img);
}
tl_assert (img_object);
tp.output ("dens", 0, new DensityMapTileReceiver (img_object), db::ICplxTrans ());
// Execute the tiling processor
tp.execute (tl::to_string (tr ("Computing density map")));
// Do the averaging if requested
unsigned int nw = (unsigned int) (std::max (0.0, std::min (1000.0, floor (par.window_size / par.pixel_size + 0.5))));
if (nw > 1) {
average_window (*img_object, par.boundary_mode, nw);
}
}
inline int safe_mod (int a, int b)
{
if (a < 0) {
return b - (-a % b);
} else {
return a % b;
}
}
void
DensityMapDialog::average_window (img::Object &img_object, const std::string boundary_mode, int nw)
{
bool periodic = (boundary_mode == boundary_mode_periodic);
int nx = img_object.width ();
int ny = img_object.height ();
// compute the outside value if not periodic
double outside = 0.0;
if (boundary_mode == boundary_mode_one) {
outside = 1.0;
} else if (boundary_mode == boundary_mode_average) {
double outside = 0.0;
const float *d = img_object.float_data ();
for (size_t i = size_t (nx) * size_t (ny); i > 0; --i) {
outside += *d++;
}
outside /= double (nx) * double (ny);
}
std::vector<double> vavg_data;
vavg_data.resize (size_t (nx) * size_t (ny), 0.0);
// vertical sum
for (int y = 0; y < ny; ++y) {
int wh = nw / 2;
double fb = (nw % 2 == 0) ? 0.5 : 1.0;
for (int dy = -wh; dy <= wh; ++dy) {
// top and bottom row count half in case of even nw
double f = (dy == -wh || dy == wh) ? fb : 1.0;
std::vector<double>::iterator d = vavg_data.begin () + y * nx;
if (periodic || (y + dy >= 0 && y + dy < ny)) {
const float *s = img_object.float_data () + safe_mod (y + dy, ny) * nx;
for (int ix = 0; ix < nx; ++ix) {
*d++ += f * *s++;
}
} else {
for (int ix = 0; ix < nx; ++ix) {
*d++ += f * outside;
}
}
}
}
// horizontal sum
outside *= nw; // because we do normalization later
// TODO: transposing the image would make things more efficient
std::vector<double> havg_data;
havg_data.resize (size_t (nx) * size_t (ny), 0.0);
for (int x = 0; x < nx; ++x) {
int wh = nw / 2;
double fb = (nw % 2 == 0) ? 0.5 : 1.0;
for (int dx = -wh; dx <= wh; ++dx) {
// top and bottom row count half in case of even nw
double f = (dx == -wh || dx == wh) ? fb : 1.0;
std::vector<double>::iterator d = havg_data.begin () + x;
if (periodic || (x + dx >= 0 && x + dx < nx)) {
std::vector<double>::const_iterator s = vavg_data.begin () + safe_mod (x + dx, nx);
for (int iy = 0; iy < ny; ++iy) {
*d += f * *s;
d += nx;
s += nx;
}
} else {
for (int iy = 0; iy < ny; ++iy) {
*d += f * outside;
d += nx;
}
}
}
}
// take the average
double s = 1.0 / (double (nw) * double (nw));
for (auto i = havg_data.begin (); i != havg_data.end (); ++i) {
*i *= s;
}
img_object.set_data (nx, ny, havg_data);
}
}

View File

@ -0,0 +1,98 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2026 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_layDensityMapDialog
#define HDR_layDensityMapDialog
#include "ui_DensityMapDialog.h"
#include "layLayoutView.h"
#include "layBrowser.h"
#include "layMarker.h"
namespace img
{
class Object;
}
namespace lay
{
class DensityMapDialog
: public lay::Browser,
private Ui::DensityMapDialog
{
Q_OBJECT
public:
DensityMapDialog (lay::Dispatcher *root, lay::LayoutViewBase *view);
~DensityMapDialog ();
public slots:
void layer_mode_changed (int);
void region_mode_changed (int);
void accept ();
void apply ();
private:
struct DensityMapParameters
{
DensityMapParameters ()
: pixel_size (100.0), threads (1)
{ }
// Collects all cv_index/layer index pairs used for input
std::vector<std::pair<unsigned int, unsigned int> > input_layers;
// The region to compute the density map from
db::DBox region;
// The pixel size
double pixel_size;
// The window size or zero for "no window"
double window_size;
// The boundary mode
std::string boundary_mode;
// The number of threads to use
int threads;
};
// implementation of the lay::Plugin interface
virtual bool configure (const std::string &name, const std::string &value);
// implementation of the lay::Plugin interface
void menu_activated (const std::string &symbol);
void make_density_map ();
void compute_density_map (const DensityMapParameters &par);
void average_window (img::Object &img_object, const std::string boundary_mode, int nw);
};
}
#endif

View File

@ -0,0 +1,18 @@
TARGET = density_map_ui
DESTDIR = $$OUT_PWD/../../../../lay_plugins
include($$PWD/../../../lay_plugin.pri)
INCLUDEPATH += $$IMG_INC $$ANT_INC
DEPENDPATH += $$IMG_INC $$ANT_INC
LIBS += -L$$DESTDIR/.. -lklayout_img -lklayout_ant
HEADERS = \
layDensityMapDialog.h \
SOURCES = \
layDensityMapDialog.cc \
FORMS = \
DensityMapDialog.ui \

View File

@ -1920,13 +1920,15 @@ static void map_databases (rdb::Database &self, const rdb::Database &other,
std::map<id_type, id_type> &rev_tag2tag,
bool create_missing)
{
std::list<std::pair<rdb::Cell *, const rdb::Cell *> > new_cells;
for (auto c = other.cells ().begin (); c != other.cells ().end (); ++c) {
// TODO: do we have a consistent scheme of naming variants? What requirements
// exist towards detecting variant specific waivers
rdb::Cell *this_cell = self.cell_by_qname_non_const (c->qname ());
if (! this_cell && create_missing) {
this_cell = self.create_cell (c->name (), c->variant (), c->layout_name ());
this_cell->import_references (c->references ());
new_cells.push_back (std::make_pair (this_cell, c.operator-> ()));
}
if (this_cell) {
cell2cell.insert (std::make_pair (this_cell->id (), c->id ()));
@ -1934,6 +1936,19 @@ static void map_databases (rdb::Database &self, const rdb::Database &other,
}
}
// import and map references for new cells
for (auto cp = new_cells.begin (); cp != new_cells.end (); ++cp) {
auto &new_refs = cp->first->references ();
for (auto r = cp->second->references ().begin (); r != cp->second->references ().end (); ++r) {
rdb::Reference rnew = *r;
auto cid = rev_cell2cell.find (rnew.parent_cell_id ());
if (cid != rev_cell2cell.end ()) {
rnew.set_parent_cell_id (cid->second);
new_refs.insert (rnew);
}
}
}
for (auto c = other.categories ().begin (); c != other.categories ().end (); ++c) {
map_category (*c, self, cat2cat, rev_cat2cat, create_missing, 0);
}

View File

@ -979,16 +979,18 @@ TEST(22_MergeCells)
}
{
rdb::Cell *cell1, *cell2, *cell3;
cell1 = db2.create_cell ("B");
cell2 = db2.create_cell ("A");
cell3 = db2.create_cell ("A", "VAR2", "ALAY");
// NOTE: db2 parent is at a different position (issue #2339)
rdb::Cell *parent;
parent = db2.create_cell ("TOP");
rdb::Cell *cell;
cell = db2.create_cell ("B");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 0.0)), parent->id ()));
cell = db2.create_cell ("A");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 2.5)), parent->id ())); // reference not taken!
cell = db2.create_cell ("A", "VAR2", "ALAY");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, -1.0)), parent->id ()));
cell1->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 0.0)), parent->id ()));
cell2->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 2.5)), parent->id ())); // reference not taken as cell will be taken from db1
cell3->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, -1.0)), parent->id ()));
}
db1.merge (db2);
@ -996,13 +998,13 @@ TEST(22_MergeCells)
std::set<std::string> cells;
for (auto c = db1.cells ().begin (); c != db1.cells ().end (); ++c) {
if (c->references ().begin () != c->references ().end ()) {
cells.insert (c->qname () + "[" + c->references ().begin ()->trans_str () + "]");
cells.insert (c->qname () + "[" + c->references ().begin ()->trans_str () + ":" + db1.cell_by_id (c->references ().begin ()->parent_cell_id ())->qname () + "]");
} else {
cells.insert (c->qname ());
}
}
EXPECT_EQ (tl::join (cells.begin (), cells.end (), ";"), "A:1[r0 *1 1,2];A:VAR1[r0 *1 1,-2];A:VAR2[r0 *1 1,-1];B[r0 *1 1,0];TOP");
EXPECT_EQ (tl::join (cells.begin (), cells.end (), ";"), "A:1[r0 *1 1,2:TOP];A:VAR1[r0 *1 1,-2:TOP];A:VAR2[r0 *1 1,-1:TOP];B[r0 *1 1,0:TOP];TOP");
}
TEST(23_MergeTags)

Binary file not shown.

13
testdata/lvs/split_substrate.cir vendored Normal file
View File

@ -0,0 +1,13 @@
* Extracted by KLayout
.SUBCKT TOP A Q IOSUB SUBSTRATE
X$1 \$7 \$1 Q IOSUB IOSUB INV
X$2 \$7 A \$1 SUBSTRATE SUBSTRATE INV
.ENDS TOP
.SUBCKT INV \$2 \$4 \$5 \$1 \$I11
M$1 \$2 \$4 \$5 \$2 PMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U
+ PD=3.45U
M$2 \$1 \$4 \$5 \$I11 NMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U
+ PD=3.45U
.ENDS INV

BIN
testdata/lvs/split_substrate.gds vendored Normal file

Binary file not shown.

215
testdata/lvs/split_substrate.l2n vendored Normal file
View File

@ -0,0 +1,215 @@
#%l2n-klayout
W(TOP)
U(0.001)
L(l3 '1/0')
L(l4 '3/0')
L(l15 '3/1')
L(l8 '4/0')
L(l11 '5/0')
L(l12 '6/0')
L(l16 '6/1')
L(l13 '7/0')
L(l14 '8/0')
L(l17)
L(l22 '10/0')
L(l7)
L(l10)
L(l2)
L(l9)
L(l6)
L(l21)
L(l18)
L(l20)
L(l19)
C(l3 l3 l10)
C(l4 l4 l15 l11)
C(l15 l4 l15)
C(l8 l8 l12 l10 l2 l9 l6)
CS(l8 l10 l2 l9 l6)
C(l11 l4 l11 l12)
CS(l11 l4)
C(l12 l8 l11 l12 l16 l13)
C(l16 l12 l16)
C(l13 l12 l13 l14)
C(l14 l13 l14 l17)
C(l17 l14 l17)
C(l22 l22 l21 l20)
C(l7 l7 l18 l20)
C(l10 l3 l8 l10)
CS(l10 l3)
C(l2 l8 l2)
C(l9 l8 l9 l21 l19)
C(l6 l8 l6)
C(l21 l22 l9 l21)
CS(l21 l22)
C(l18 l7 l18)
C(l20 l22 l7 l20)
C(l19 l9 l19)
G(l22 IOSUB)
G(l18 SUBSTRATE)
G(l19 SUBSTRATE)
GS(l19 SUBSTRATE)
H(W B('Net with incomplete wiring (soft-connected partial nets)') C(TOP) X('soft-connection-check'))
H(B('\tPartial net #1: TOP/INV[r0 3,1.6]:$1 - $2') C(TOP) Q('(1.5,3.95;1.5,4.85;5.3,4.85;5.3,3.95)'))
H(B('\tPartial net #2: TOP/INV[r0 7.7,1.6]:$2 - $2') C(TOP) Q('(6.2,3.95;6.2,4.85;10,4.85;10,3.95)'))
K(PMOS MOS4)
K(NMOS MOS4)
D(D$PMOS PMOS
T(S
R(l2 (-900 -475) (775 950))
)
T(G
R(l4 (-125 -475) (250 950))
)
T(D
R(l2 (125 -475) (775 950))
)
T(B
R(l3 (-125 -475) (250 950))
)
)
D(D$NMOS NMOS
T(S
R(l6 (-900 -475) (775 950))
)
T(G
R(l4 (-125 -475) (250 950))
)
T(D
R(l6 (125 -475) (775 950))
)
T(B
R(l7 (-125 -475) (250 950))
)
)
X(INV
R((-1500 -800) (3800 4600))
N(1 I($1)
R(l8 (1700 100) (200 200))
R(l8 (-200 -600) (200 200))
R(l8 (-1610 -210) (220 220))
R(l8 (-220 180) (220 220))
R(l12 (890 -760) (800 900))
R(l12 (-1980 -830) (360 760))
R(l13 (-305 -705) (250 250))
R(l13 (-250 150) (250 250))
R(l13 (1175 -225) (200 200))
R(l13 (-200 -600) (200 200))
R(l14 (-3400 -350) (3000 900))
R(l14 (-200 -900) (1000 900))
R(l9 (-700 -950) (400 1000))
R(l6 (-1875 -975) (775 950))
)
N(2 I($2)
R(l3 (-1500 1800) (3000 2000))
R(l3 (-200 -2000) (1000 2000))
R(l8 (-2010 -1310) (220 220))
R(l8 (-220 180) (220 220))
R(l8 (1190 -210) (200 200))
R(l8 (-200 -600) (200 200))
R(l12 (-1680 -280) (360 760))
R(l12 (820 -830) (800 900))
R(l13 (-1925 -775) (250 250))
R(l13 (-250 150) (250 250))
R(l13 (1175 -225) (200 200))
R(l13 (-200 -600) (200 200))
R(l14 (-3400 -350) (3000 900))
R(l14 (-200 -900) (1000 900))
R(l10 (-700 -950) (400 1000))
R(l2 (-1875 -975) (775 950))
)
N(3 I($4)
R(l4 (-125 -250) (250 2500))
R(l4 (-250 -3050) (250 1600))
R(l4 (-250 1200) (250 1600))
)
N(4 I($5)
R(l8 (-510 -310) (220 220))
R(l8 (-220 180) (220 220))
R(l8 (-220 2180) (220 220))
R(l8 (-220 180) (220 220))
R(l12 (-290 -3530) (360 2840))
R(l12 (-360 -2800) (360 760))
R(l12 (-360 2040) (360 760))
R(l2 (-680 -855) (775 950))
R(l6 (-775 -3750) (775 950))
)
N(5 I($I11))
P(2)
P(3)
P(4)
P(1)
P(5)
D(1 D$PMOS
Y(0 2800)
E(L 0.25)
E(W 0.95)
E(AS 0.73625)
E(AD 0.73625)
E(PS 3.45)
E(PD 3.45)
T(S 4)
T(G 3)
T(D 2)
T(B 2)
)
D(2 D$NMOS
Y(0 0)
E(L 0.25)
E(W 0.95)
E(AS 0.73625)
E(AD 0.73625)
E(PS 3.45)
E(PD 3.45)
T(S 4)
T(G 3)
T(D 1)
T(B 5)
)
)
X(TOP
R((1334 621) (9246 5073))
N(1 I($1)
R(l4 (2920 2600) (3980 400))
R(l11 (-300 -300) (200 200))
R(l12 (-300 -300) (690 400))
)
N(2 I(A)
R(l4 (7700 2600) (2880 400))
R(l15 (-2380 -200) (0 0))
)
N(3 I(Q)
R(l12 (1810 2600) (690 400))
R(l16 (-400 -200) (0 0))
)
N(4 I(IOSUB)
R(l22 (1334 621) (4230 5073))
R(l21 (-964 -4594) (400 1000))
R(l20 (-2125 -975) (250 950))
)
N(5 I($7)
R(l3 (4000 3400) (2700 2000))
)
N(6 I(SUBSTRATE)
R(l18 (7575 1125) (250 950))
R(l19 (1475 -975) (400 1000))
)
P(2 I(A))
P(3 I(Q))
P(4 I(IOSUB))
P(6 I(SUBSTRATE))
X(1 INV Y(3000 1600)
P(0 5)
P(1 1)
P(2 3)
P(3 4)
P(4 4)
)
X(2 INV Y(7700 1600)
P(0 5)
P(1 2)
P(2 1)
P(3 6)
P(4 6)
)
)

107
testdata/lvs/split_substrate.lvs vendored Normal file
View File

@ -0,0 +1,107 @@
$lvs_test_source && source($lvs_test_source)
if $lvs_test_target_l2n
report_netlist($lvs_test_target_l2n)
else
report_netlist
end
writer = write_spice(true, false)
$lvs_test_target_cir && target_netlist($lvs_test_target_cir, writer, "Extracted by KLayout")
deep
# Drawing layers
nwell = input(1, 0)
active = input(2, 0)
nplus = input(2, 1)
pplus = input(2, 2)
poly = input(3, 0)
poly_lbl = input(3, 1)
diff_cont = input(4, 0)
poly_cont = input(5, 0)
metal1 = input(6, 0)
metal1_lbl = input(6, 1)
via1 = input(7, 0)
metal2 = input(8, 0)
metal2_lbl = input(8, 1)
iosub = input(10, 0)
# Bulk layer for terminal provisioning
bulk = polygon_layer
psd = nil
nsd = nil
# Computed layers
active_in_nwell = active & nwell
pactive = active_in_nwell & pplus
ntie = active_in_nwell & nplus
pgate = pactive & poly
psd = pactive - pgate
active_outside_nwell = active - nwell
nactive = active_outside_nwell & nplus
ptie = active_outside_nwell & pplus
ngate = nactive & poly
nsd = nactive - ngate
# Device extraction
# PMOS transistor device extraction
extract_devices(mos4("PMOS"), { "SD" => psd, "G" => pgate, "W" => nwell,
"tS" => psd, "tD" => psd, "tG" => poly })
# NMOS transistor device extraction
extract_devices(mos4("NMOS"), { "SD" => nsd, "G" => ngate, "W" => bulk,
"tS" => nsd, "tD" => nsd, "tG" => poly })
# Define connectivity for netlist extraction
# Inter-layer
soft_connect(diff_cont, psd)
soft_connect(diff_cont, nsd)
soft_connect(diff_cont, ptie)
soft_connect(diff_cont, ntie)
soft_connect(ntie, nwell)
soft_connect(poly_cont, poly)
connect(diff_cont, metal1)
connect(poly_cont, metal1)
connect(metal1, via1)
connect(via1, metal2)
# attach labels
connect(poly, poly_lbl)
connect(metal1, metal1_lbl)
connect(metal2, metal2_lbl)
# Split bulk and ptie into inside and outside iosub
(bulk_io, bulk_reg) = bulk.andnot(iosub)
(ptie_io, ptie_reg) = ptie.andnot(iosub)
# connect outside to "SUBSTRATE" globally
connect(bulk, bulk_reg)
connect(ptie, ptie_reg)
connect_global(bulk_reg, "SUBSTRATE")
soft_connect_global(ptie_reg, "SUBSTRATE")
# connect inside to "IOSUB" via polygons
connect(bulk, bulk_io)
connect(ptie, ptie_io)
connect(bulk_io, iosub)
soft_connect(ptie_io, iosub)
connect_global(iosub, "IOSUB")
# Netlist section (NOTE: we only check log here)
# for debugging: _make_soft_connection_diodes(true)
netlist
netlist.simplify