diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 74f9f4df6..fe45fbecb 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -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::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 (); } } diff --git a/src/img/img/imgObject.cc b/src/img/img/imgObject.cc index 6642ccdcb..4f6d97cf3 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -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 { diff --git a/src/img/img/imgObject.h b/src/img/img/imgObject.h index a48e72551..6658d2ae9 100644 --- a/src/img/img/imgObject.h +++ b/src/img/img/imgObject.h @@ -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; diff --git a/src/img/img/imgPropertiesPage.cc b/src/img/img/imgPropertiesPage.cc index cdb14e1d1..83938a4d8 100644 --- a/src/img/img/imgPropertiesPage.cc +++ b/src/img/img/imgPropertiesPage.cc @@ -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 (); } diff --git a/src/img/img/imgPropertiesPage.h b/src/img/img/imgPropertiesPage.h index c799775b0..06d73b173 100644 --- a/src/img/img/imgPropertiesPage.h +++ b/src/img/img/imgPropertiesPage.h @@ -101,6 +101,7 @@ private: void invalidate (); void init (); void get_xmin_xmax (double &xmin, double &xmax, bool &has_error_out); + bool update_controls (); }; } diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index 63b6450cf..259a272b0 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -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 (); } diff --git a/src/lay/lay/layApplication.h b/src/lay/lay/layApplication.h index e268df204..2a1322dfe 100644 --- a/src/lay/lay/layApplication.h +++ b/src/lay/lay/layApplication.h @@ -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 >f_record, bool gtf_record_incremental); virtual void start_recording (); virtual lay::Dispatcher *dispatcher () const = 0; 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/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 13c76abcf..f63026e95 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -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*/), diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index e1fc24972..a22ac7bc9 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -94,7 +94,6 @@ class ProgressWidget; */ class LAY_PUBLIC MainWindow : public QMainWindow, - public tl::Object, public gsi::ObjectBase, public lay::DispatcherDelegate { diff --git a/src/laybasic/laybasic/layDispatcher.h b/src/laybasic/laybasic/layDispatcher.h index f42b60585..df26686d8 100644 --- a/src/laybasic/laybasic/layDispatcher.h +++ b/src/laybasic/laybasic/layDispatcher.h @@ -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 mp_delegate; }; } diff --git a/src/laybasic/laybasic/layLayoutViewConfig.cc b/src/laybasic/laybasic/layLayoutViewConfig.cc index f16fec969..2f68789bc 100644 --- a/src/laybasic/laybasic/layLayoutViewConfig.cc +++ b/src/laybasic/laybasic/layLayoutViewConfig.cc @@ -48,6 +48,12 @@ public: options.push_back (std::pair (cfg_layers_always_show_ld, "true")); options.push_back (std::pair (cfg_layers_always_show_layout_index, "false")); options.push_back (std::pair (cfg_test_shapes_in_view, "false")); + options.push_back (std::pair (cfg_layer_search_as_expressions, "true")); + options.push_back (std::pair (cfg_layer_search_as_filter, "false")); + options.push_back (std::pair (cfg_layer_search_case_sensitive, "true")); + options.push_back (std::pair (cfg_cell_search_as_expressions, "true")); + options.push_back (std::pair (cfg_cell_search_as_filter, "false")); + options.push_back (std::pair (cfg_cell_search_case_sensitive, "true")); options.push_back (std::pair (cfg_flat_cell_list, "false")); options.push_back (std::pair (cfg_split_cell_list, "false")); options.push_back (std::pair (cfg_cell_list_sorting, "by-name")); diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index b23f7c3bc..b270ab585 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -437,9 +437,12 @@ MoveService::drag_cancel () { m_shift = db::DPoint (); if (m_dragging) { + show_toolbox (false); ui ()->ungrab_mouse (this); + m_dragging = false; + } } diff --git a/src/laybasic/laybasic/laybasicConfig.h b/src/laybasic/laybasic/laybasicConfig.h index 78dc40fbf..11194126b 100644 --- a/src/laybasic/laybasic/laybasicConfig.h +++ b/src/laybasic/laybasic/laybasicConfig.h @@ -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"); diff --git a/src/layui/layui/layHierarchyControlPanel.cc b/src/layui/layui/layHierarchyControlPanel.cc index 2b948058c..4a7d9a6ef 100644 --- a/src/layui/layui/layHierarchyControlPanel.cc +++ b/src/layui/layui/layHierarchyControlPanel.cc @@ -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; diff --git a/src/layui/layui/layHierarchyControlPanel.h b/src/layui/layui/layHierarchyControlPanel.h index f363d77a1..e7b6bf044 100644 --- a/src/layui/layui/layHierarchyControlPanel.h +++ b/src/layui/layui/layHierarchyControlPanel.h @@ -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 (); diff --git a/src/layui/layui/layLayerControlPanel.cc b/src/layui/layui/layLayerControlPanel.cc index b7a2ba70c..d6861df73 100644 --- a/src/layui/layui/layLayerControlPanel.cc +++ b/src/layui/layui/layLayerControlPanel.cc @@ -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; diff --git a/src/layui/layui/layLayerControlPanel.h b/src/layui/layui/layLayerControlPanel.h index fe2f6d920..32e6c0cd6 100644 --- a/src/layui/layui/layLayerControlPanel.h +++ b/src/layui/layui/layLayerControlPanel.h @@ -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 (); 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/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index 3045f4881..6e51d4b28 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -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 () { diff --git a/src/layview/layview/layLayoutView_qt.h b/src/layview/layview/layLayoutView_qt.h index 5b9df8062..b7387aec3 100644 --- a/src/layview/layview/layLayoutView_qt.h +++ b/src/layview/layview/layLayoutView_qt.h @@ -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; 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..ded9b81ce --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -0,0 +1,600 @@ + + + DensityMapDialog + + + + 0 + 0 + 657 + 480 + + + + Density Map + + + + 6 + + + 9 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + + + + Density map parameters + + + + + + + 0 + 0 + + + + 100 + + + + + + + Pixel size + + + + + + + + 0 + 0 + + + + + + + + µm + + + + + + + + 0 + 0 + + + + 1 + + + + + + + Averaging window size + + + + + + + Threads + + + + + + + Qt::Horizontal + + + + 353 + 20 + + + + + + + + µm (a multiple of the pixel size or empty) + + + + + + + Boundary mode + + + + + + + + 0 + 0 + + + + QComboBox::AdjustToContents + + + + Periodic (repeat region seamlessly) + + + + + Use zero density outside + + + + + Use 100% density outside + + + + + Use average density outside + + + + + + + + + + + Compute density of shapes on + + + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 385 + 20 + + + + + + + + + + + + + + + + Layer + + + + + Visible layers + + + + + Selected layers + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + Compute density map of region + + + + 6 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 351 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + Box Boundaries (all values in µm) + + + + 9 + + + 6 + + + + + y = + + + + + + + 2nd corner + + + + + + + x = + + + + + + + 1st corner + + + + + + + x = + + + + + + + y = + + + + + + + + + + + + + + + + + + + + + + + + + + + + Global bounding box + + + + + Bounding box of layer ... + + + + + Single box with .. + + + + + Visible region + + + + + Defined by rulers + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + lay::LayerSelectionComboBox + QComboBox +
layWidgets.h
+
+
+ + le_pixel_size + sb_threads + layer_cb + cb_source_layer + region_cb + cb_box_layer + le_x1 + le_y1 + le_x2 + le_y2 + + + + + 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..b6e280797 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -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 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 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 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 > &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 &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 (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 (); + 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 (); + 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 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::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 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::iterator d = havg_data.begin () + x; + + if (periodic || (x + dx >= 0 && x + dx < nx)) { + std::vector::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); +} + +} + 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..bd99bbbe8 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -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 > 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 + 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 \