Merge pull request #2354 from KLayout/wip

Wip
This commit is contained in:
Matthias Köfferlein 2026-05-20 19:06:25 +02:00 committed by GitHub
commit 0eed5d6ea5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1882 additions and 50 deletions

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

@ -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

@ -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

@ -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

@ -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

@ -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 \