diff --git a/src/lay/lay/layClipDialog.cc b/src/lay/lay/layClipDialog.cc
index a9dc2ae84..ca273cb56 100644
--- a/src/lay/lay/layClipDialog.cc
+++ b/src/lay/lay/layClipDialog.cc
@@ -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);
}
diff --git a/src/layui/layui/layWidgets.cc b/src/layui/layui/layWidgets.cc
index fc18882b1..93d61ec15 100644
--- a/src/layui/layui/layWidgets.cc
+++ b/src/layui/layui/layWidgets.cc
@@ -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
{
diff --git a/src/layui/layui/layWidgets.h b/src/layui/layui/layWidgets.h
index e812f2fbf..5a1eac17e 100644
--- a/src/layui/layui/layWidgets.h
+++ b/src/layui/layui/layWidgets.h
@@ -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)
*
diff --git a/src/plugins/tools/density_map/density_map.pro b/src/plugins/tools/density_map/density_map.pro
new file mode 100644
index 000000000..f1dd4434b
--- /dev/null
+++ b/src/plugins/tools/density_map/density_map.pro
@@ -0,0 +1,6 @@
+
+TEMPLATE = subdirs
+
+!equals(HAVE_QT, "0") {
+ SUBDIRS = lay_plugin
+}
diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui
new file mode 100644
index 000000000..27e8005e8
--- /dev/null
+++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui
@@ -0,0 +1,450 @@
+
+
+ DensityMapDialog
+
+
+
+ 0
+ 0
+ 641
+ 531
+
+
+
+ Density Map
+
+
+
+ 6
+
+
+ 9
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ 6
+
+
+ 0
+
+
+
+
+ -
+
+
+ Density map region
+
+
+
+ 9
+
+
+ 6
+
+
-
+
+
+ false
+
+
+ QComboBox::AdjustToContents
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ 6
+
+
+ 0
+
+
-
+
+
+ Single box with ...
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 231
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+ Box Boundaries
+
+
+
+ 9
+
+
+ 6
+
+
-
+
+
+ y =
+
+
+
+ -
+
+
+ 2nd corner
+
+
+
+ -
+
+
+ x =
+
+
+
+ -
+
+
+ 1st corner
+
+
+
+ -
+
+
+ x =
+
+
+
+ -
+
+
+ y =
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+ From rulers / box rulers
+
+
+ false
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ 6
+
+
+ 0
+
+
-
+
+
+ Visible region
+
+
+ false
+
+
+
+
+
+
+ -
+
+
+ Bounding box of layer
+
+
+ false
+
+
+
+ -
+
+
+ Bounding box of layouts
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ Density map parameters
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 100
+
+
+
+ -
+
+
+ Pixel size
+
+
+
+ -
+
+
+ µm
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 353
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 1
+
+
+
+ -
+
+
+ Threads
+
+
+
+
+
+
+ -
+
+
+ Density of
+
+
+
-
+
+
+ Layer
+
+
+
+ -
+
+
+ false
+
+
+ QComboBox::AdjustToContents
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 385
+ 20
+
+
+
+
+ -
+
+
+ Visible layers
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+ lay::LayerSelectionComboBox
+ QComboBox
+
+
+
+
+ rb_layer_bbox
+ rb_box1
+ rb_visible
+ rb_rulers
+ cb_box_layer
+ le_x1
+ le_y1
+ le_x2
+ le_y2
+ button_box
+
+
+
+
+ button_box
+ accepted()
+ DensityMapDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ button_box
+ rejected()
+ DensityMapDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc
new file mode 100644
index 000000000..909ecc788
--- /dev/null
+++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc
@@ -0,0 +1,368 @@
+
+/*
+
+ 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
+{
+
+// ------------------------------------------------------------
+// Declaration of the configuration options
+
+class DensityMapDialogPluginDeclaration
+ : public lay::PluginDeclaration
+{
+public:
+ virtual void get_options (std::vector < std::pair > & /*options*/) const
+ {
+ // .. no options yet ..
+ }
+
+ virtual lay::ConfigPage *config_page (QWidget * /*parent*/, std::string & /*title*/) const
+ {
+ return 0; // .. no config page yet ..
+ }
+
+ virtual void get_menu_entries (std::vector &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 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 (rb_box1, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ()));
+ connect (rb_rulers, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ()));
+ connect (rb_whole_layout, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ()));
+ connect (rb_layer_bbox, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ()));
+ connect (rb_visible, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ()));
+
+ connect (rb_visible_layers, SIGNAL (clicked ()), this, SLOT (source_selection_clicked ()));
+ connect (rb_of_layer, SIGNAL (clicked ()), this, SLOT (source_selection_clicked ()));
+}
+
+void
+DensityMapDialog::box_selection_clicked ()
+{
+ rb_box1->setChecked (sender () == rb_box1);
+ grp_box1->setEnabled (sender () == rb_box1);
+ rb_rulers->setChecked (sender () == rb_rulers);
+ rb_whole_layout->setChecked (sender () == rb_whole_layout);
+ rb_layer_bbox->setChecked (sender () == rb_layer_bbox);
+ cb_box_layer->setEnabled (sender () == rb_layer_bbox);
+ rb_visible->setChecked (sender () == rb_visible);
+}
+
+void
+DensityMapDialog::source_selection_clicked ()
+{
+ rb_visible_layers->setChecked (sender () == rb_visible_layers);
+ cb_source_layer->setEnabled (sender () == rb_of_layer);
+ rb_of_layer->setChecked (sender () == rb_of_layer);
+}
+
+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 ..
+}
+
+// @@@@
+class TileRec
+ : public db::TileOutputReceiver
+{
+public:
+ TileRec (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*/)
+ {
+ tl::info << "@@@ " << ix << "," << iy << " -> " << obj.to_string () << " -- " << tile.to_string ();
+ mp_img->set_pixel (ix, iy, obj.to_double ());
+ }
+
+private:
+ img::Object *mp_img;
+};
+// @@@
+
+void
+DensityMapDialog::accept ()
+{
+BEGIN_PROTECTED
+
+ // Collects all cv_index/layer index pairs used for input
+ std::vector > input_layers;
+
+ int threads = std::max (1, sb_threads->value ());
+
+ double pixel_size = 0.0;
+ tl::from_string_ext (tl::to_string (le_pixel_size->text ()), pixel_size);
+
+ if (pixel_size < 1e-6) {
+ throw tl::Exception (tl::to_string (QObject::tr ("Pixel size must be positive and not zero")));
+ }
+
+ db::DBox region;
+
+ if (rb_box1->isChecked ()) {
+
+ 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);
+
+ region = db::DBox (db::DPoint (x1, y1), db::DPoint (x2, y2));
+
+ } else if (rb_rulers->isChecked ()) {
+
+ ant::Service *ant_service = view ()->get_plugin ();
+ if (ant_service) {
+ ant::AnnotationIterator ant = ant_service->begin_annotations ();
+ while (! ant.at_end ()) {
+ region += db::DBox (ant->p1 (), ant->p2 ());
+ ++ant;
+ }
+ }
+
+ } else if (rb_layer_bbox->isChecked ()) {
+
+ lay::CellView ccv = view ()->cellview (cb_box_layer->cv_index ());
+ int sel_layer = cb_box_layer->current_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")));
+ }
+
+ region = db::CplxTrans (ccv->layout ().dbu ()) * ccv->layout ().cell (ccv.cell_index ()).bbox (sel_layer);
+
+ } else if (rb_visible->isChecked ()) {
+
+ region = view ()->box ();
+
+ } else {
+
+ lay::CellView cv = view ()->cellview (view ()->active_cellview_index ());
+ region = db::CplxTrans (cv->layout ().dbu ()) * cv->layout ().cell (cv.cell_index ()).bbox ();
+
+ }
+
+ if (rb_of_layer->isChecked ()) {
+
+ int cvi = cb_box_layer->cv_index ();
+ lay::CellView ccv = view ()->cellview (cvi);
+ int sel_layer = cb_box_layer->current_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")));
+ }
+
+ input_layers.push_back (std::make_pair (cvi, sel_layer));
+
+ } else {
+
+ 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)) {
+ input_layers.push_back (std::make_pair (cvi, li));
+ }
+
+ }
+
+ }
+
+ }
+
+ if (region.empty ()) {
+ throw tl::Exception (tl::to_string (QObject::tr ("Density map region is empty")));
+ }
+
+ if (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 (region.width () / pixel_size - db::epsilon));
+ double dny = std::max (1.0, ceil (region.height () / 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 = region.center ().x () - nx * 0.5 * pixel_size;
+ double y0 = region.center ().y () - ny * 0.5 * pixel_size;
+
+ double dbu = view ()->cellview (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 (pixel_size, pixel_size);
+
+ tp.set_threads (threads);
+ tp.set_dbu (dbu);
+
+ int ninput = 1;
+ std::string in_expr;
+
+ for (auto i = input_layers.begin (); i != 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 ();
+ if (img_service) {
+
+ // TODO: we could selectively clear all images that are used for density maps (-> needs a property that identifies them)
+ img_service->clear_images ();
+
+ 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)));
+
+ img::Object img (nx, ny, db::DCplxTrans (pixel_size, 0.0, false, region.center () - db::DPoint ()), false, false);
+ img.set_data_mapping (dm);
+
+ img_object = img_service->insert_image (img);
+
+ }
+
+ tl_assert (img_object);
+ tp.output ("dens", 0, new TileRec (img_object), db::ICplxTrans ());
+
+ // Execute the tiling processor
+
+ tp.execute (tl::to_string (tr ("Computing density map")));
+
+ // close this dialog
+ QDialog::accept ();
+
+END_PROTECTED
+}
+
+bool
+DensityMapDialog::configure (const std::string & /*name*/, const std::string & /*value*/)
+{
+ // .. nothing yet ..
+ return false;
+}
+
+}
+
diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h
new file mode 100644
index 000000000..360ba48f8
--- /dev/null
+++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h
@@ -0,0 +1,63 @@
+
+/*
+
+ 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 lay
+{
+
+class DensityMapDialog
+ : public lay::Browser,
+ private Ui::DensityMapDialog
+{
+Q_OBJECT
+
+public:
+ DensityMapDialog (lay::Dispatcher *root, lay::LayoutViewBase *view);
+ ~DensityMapDialog ();
+
+public slots:
+ void box_selection_clicked ();
+ void source_selection_clicked ();
+ void accept ();
+
+private:
+ // 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);
+
+};
+
+}
+
+#endif
+
diff --git a/src/plugins/tools/density_map/lay_plugin/lay_plugin.pro b/src/plugins/tools/density_map/lay_plugin/lay_plugin.pro
new file mode 100644
index 000000000..a448da1fe
--- /dev/null
+++ b/src/plugins/tools/density_map/lay_plugin/lay_plugin.pro
@@ -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 \