From d3fa7ad8f072761908194362cd19e96fcf6853ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sat, 19 Apr 2025 11:23:04 +0200 Subject: [PATCH] FEM: Add index over frames visualizations --- src/Mod/Fem/Gui/CMakeLists.txt | 3 +- .../ui/PostHistogramFieldViewEdit.ui | 41 ++--- .../Resources/ui/PostHistogramIndexAppEdit.ui | 81 +++++++++ .../Resources/ui/PostLineplotFieldAppEdit.ui | 4 +- .../Resources/ui/PostLineplotFieldViewEdit.ui | 81 +++++---- .../Resources/ui/PostLineplotIndexAppEdit.ui | 85 ++++++++++ src/Mod/Fem/Gui/TaskPostBoxes.cpp | 19 +++ src/Mod/Fem/Gui/TaskPostBoxes.h | 10 ++ src/Mod/Fem/Gui/TaskPostExtraction.cpp | 18 ++ src/Mod/Fem/Gui/TaskPostExtraction.h | 1 + src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 1 + src/Mod/Fem/ObjectsFem.py | 33 +++- src/Mod/Fem/femcommands/commands.py | 2 +- src/Mod/Fem/femguiutils/data_extraction.py | 11 ++ src/Mod/Fem/femguiutils/extract_link_view.py | 72 ++++---- .../Fem/femobjects/base_fempostextractors.py | 48 ++++-- src/Mod/Fem/femobjects/post_extract1D.py | 71 ++++++-- src/Mod/Fem/femobjects/post_extract2D.py | 89 ++++++++-- src/Mod/Fem/femobjects/post_histogram.py | 14 +- src/Mod/Fem/femobjects/post_lineplot.py | 14 ++ .../Fem/femtaskpanels/task_post_histogram.py | 3 + .../Fem/femtaskpanels/task_post_lineplot.py | 3 + .../femviewprovider/view_post_histogram.py | 154 ++++++++++++++++-- .../Fem/femviewprovider/view_post_lineplot.py | 122 ++++++++++++-- src/Mod/Plot/Plot.py | 7 +- 25 files changed, 804 insertions(+), 183 deletions(-) create mode 100644 src/Mod/Fem/Gui/Resources/ui/PostHistogramIndexAppEdit.ui create mode 100644 src/Mod/Fem/Gui/Resources/ui/PostLineplotIndexAppEdit.ui diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt index e1523956ea..7337afd833 100755 --- a/src/Mod/Fem/Gui/CMakeLists.txt +++ b/src/Mod/Fem/Gui/CMakeLists.txt @@ -445,11 +445,12 @@ SET(FemGuiPythonUI_SRCS Resources/ui/TaskPostExtraction.ui Resources/ui/TaskPostHistogram.ui Resources/ui/TaskPostLineplot.ui - Resources/ui/PostExtractionSummaryWidget.ui Resources/ui/PostHistogramFieldViewEdit.ui Resources/ui/PostHistogramFieldAppEdit.ui + Resources/ui/PostHistogramIndexAppEdit.ui Resources/ui/PostLineplotFieldViewEdit.ui Resources/ui/PostLineplotFieldAppEdit.ui + Resources/ui/PostLineplotIndexAppEdit.ui ) ADD_CUSTOM_TARGET(FemPythonUi ALL diff --git a/src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldViewEdit.ui b/src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldViewEdit.ui index 744e5a6240..5fe4a7d3dc 100644 --- a/src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldViewEdit.ui +++ b/src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldViewEdit.ui @@ -6,8 +6,8 @@ 0 0 - 293 - 126 + 278 + 110 @@ -68,7 +68,7 @@ - + 0 0 @@ -93,7 +93,7 @@ - + 0 0 @@ -113,10 +113,20 @@ + + + + + + + Legend: + + + - + - + 0 0 @@ -127,7 +137,7 @@ - + 0 @@ -139,27 +149,10 @@ - - - - - - - Legend: - - - - - - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
-
Legend BarColor diff --git a/src/Mod/Fem/Gui/Resources/ui/PostHistogramIndexAppEdit.ui b/src/Mod/Fem/Gui/Resources/ui/PostHistogramIndexAppEdit.ui new file mode 100644 index 0000000000..e9dd2a2b3d --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/ui/PostHistogramIndexAppEdit.ui @@ -0,0 +1,81 @@ + + + Form + + + + 0 + 0 + 261 + 110 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Field: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Index: + + + + + + + + 0 + 0 + + + + + + + + + + + diff --git a/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui b/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui index 00b8ab4f55..b0d1830852 100644 --- a/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui +++ b/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui @@ -6,8 +6,8 @@ 0 0 - 296 - 186 + 271 + 174
diff --git a/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui b/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui index 720eb96c6e..f197016d12 100644 --- a/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui +++ b/src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui @@ -6,8 +6,8 @@ 0 0 - 335 - 124 + 274 + 114 @@ -28,25 +28,6 @@ - - - - - 0 - 0 - - - - Width of all lines (outline and hatch) - - - 99.000000000000000 - - - 0.100000000000000 - - - @@ -57,7 +38,7 @@ - + 0 0 @@ -107,18 +88,8 @@ - - - - 99.000000000000000 - - - 0.100000000000000 - - - - + 0 @@ -130,24 +101,50 @@ + + + + + 0 + 0 + + + + 99.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + Width of all lines (outline and hatch) + + + 99.000000000000000 + + + 0.100000000000000 + + + - - - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
-
Legend Color LineStyle - LineWidth MarkerStyle - MarkerSize diff --git a/src/Mod/Fem/Gui/Resources/ui/PostLineplotIndexAppEdit.ui b/src/Mod/Fem/Gui/Resources/ui/PostLineplotIndexAppEdit.ui new file mode 100644 index 0000000000..ba4ab0ead3 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/ui/PostLineplotIndexAppEdit.ui @@ -0,0 +1,85 @@ + + + Form + + + + 0 + 0 + 310 + 108 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Y Field: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Index: + + + + + + + + 0 + 0 + + + + 99999999 + + + + + + + Index + YField + YComponent + + + + diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index d880f73d33..1a2c244943 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -408,6 +408,21 @@ void TaskDlgPost::modifyStandardButtons(QDialogButtonBox* box) } } +void TaskDlgPost::processCollapsedWidgets() { + + for (auto& widget : Content) { + if(auto task_box = dynamic_cast(widget)) { + // get the task widget and check if it is a post widget + auto widget = task_box->groupLayout()->itemAt(0)->widget(); + if(auto post_widget = dynamic_cast(widget)) { + if(post_widget->initiallyCollapsed()) { + post_widget->setGeometry(QRect(QPoint(0,0), post_widget->sizeHint())); + task_box->hideGroupBox(); + } + } + } + } +} // *************************************************************************** // box to set the coloring @@ -571,6 +586,10 @@ void TaskPostFrames::applyPythonCode() // we apply the views widgets python code } +bool TaskPostFrames::initiallyCollapsed() { + + return (ui->FrameTable->rowCount() == 0); +} // *************************************************************************** // in the following, the different filters sorted alphabetically diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index 9b24eb314f..3c60fe8ddf 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -156,6 +156,11 @@ public: // executed when the apply button is pressed in the task dialog virtual void apply() {}; + // returns if the widget shall be collapsed when opening the task dialog + virtual bool initiallyCollapsed() { + return false; + }; + protected: App::DocumentObject* getObject() const { @@ -235,6 +240,9 @@ public: /// returns for Close and Help button QDialogButtonBox::StandardButtons getStandardButtons() const override; + /// makes sure all widgets are collapsed, if they want to be + void processCollapsedWidgets(); + protected: void recompute(); @@ -300,6 +308,8 @@ public: void applyPythonCode() override; + bool initiallyCollapsed() override; + private: void setupConnections(); void onSelectionChanged(); diff --git a/src/Mod/Fem/Gui/TaskPostExtraction.cpp b/src/Mod/Fem/Gui/TaskPostExtraction.cpp index ef70109462..57f39a70a2 100644 --- a/src/Mod/Fem/Gui/TaskPostExtraction.cpp +++ b/src/Mod/Fem/Gui/TaskPostExtraction.cpp @@ -166,4 +166,22 @@ void TaskPostExtraction::apply() } } +bool TaskPostExtraction::initiallyCollapsed() +{ + Base::PyGILStateLocker lock; + try { + if (m_panel.hasAttr(std::string("initiallyCollapsed"))) { + Py::Callable method(m_panel.getAttr(std::string("initiallyCollapsed"))); + auto result = Py::Boolean(method.apply()); + return result.as_bool(); + } + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + + return false; +} + #include "moc_TaskPostExtraction.cpp" diff --git a/src/Mod/Fem/Gui/TaskPostExtraction.h b/src/Mod/Fem/Gui/TaskPostExtraction.h index 5423a83d00..5fe2518760 100644 --- a/src/Mod/Fem/Gui/TaskPostExtraction.h +++ b/src/Mod/Fem/Gui/TaskPostExtraction.h @@ -56,6 +56,7 @@ protected: bool isGuiTaskOnly() override; void apply() override; void onPostDataChanged(Fem::FemPostObject* obj) override; + bool initiallyCollapsed() override; private: Py::Object m_panel; diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index bc4dd1d953..2a34070c72 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -1007,6 +1007,7 @@ bool ViewProviderFemPostObject::setEdit(int ModNum) postDlg = new TaskDlgPost(this); setupTaskDialog(postDlg); postDlg->connectSlots(); + postDlg->processCollapsedWidgets(); Gui::Control().showDialog(postDlg); } diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py index c01cc8f8e6..d40558f4bd 100644 --- a/src/Mod/Fem/ObjectsFem.py +++ b/src/Mod/Fem/ObjectsFem.py @@ -699,6 +699,7 @@ def makePostLineplot(doc, name="Lineplot"): view_post_lineplot.VPPostLineplot(obj.ViewObject) return obj + def makePostLineplotFieldData(doc, name="FieldData2D"): """makePostLineplotFieldData(document, [name]): creates a FEM post processing data extractor for 2D Field data @@ -712,6 +713,21 @@ def makePostLineplotFieldData(doc, name="FieldData2D"): view_post_lineplot.VPPostLineplotFieldData(obj.ViewObject) return obj + +def makePostLineplotIndexOverFrames(doc, name="IndexOverFrames2D"): + """makePostLineplotIndexOverFrames(document, [name]): + creates a FEM post processing data extractor for 2D index data + """ + obj = doc.addObject("App::FeaturePython", name) + from femobjects import post_lineplot + + post_lineplot.PostLineplotIndexOverFrames(obj) + if FreeCAD.GuiUp: + from femviewprovider import view_post_lineplot + view_post_lineplot.VPPostLineplotIndexOverFrames(obj.ViewObject) + return obj + + def makePostHistogram(doc, name="Histogram"): """makePostHistogram(document, [name]): creates a FEM post processing histogram plot @@ -725,8 +741,9 @@ def makePostHistogram(doc, name="Histogram"): view_post_histogram.VPPostHistogram(obj.ViewObject) return obj + def makePostHistogramFieldData(doc, name="FieldData1D"): - """makePostHistogramFieldData1D(document, [name]): + """makePostHistogramFieldData(document, [name]): creates a FEM post processing data extractor for 1D Field data """ obj = doc.addObject("App::FeaturePython", name) @@ -739,6 +756,20 @@ def makePostHistogramFieldData(doc, name="FieldData1D"): return obj +def makePostHistogramIndexOverFrames(doc, name="IndexOverFrames1D"): + """makePostHistogramIndexOverFrames(document, [name]): + creates a FEM post processing data extractor for 1D Field data + """ + obj = doc.addObject("App::FeaturePython", name) + from femobjects import post_histogram + + post_histogram.PostHistogramIndexOverFrames(obj) + if FreeCAD.GuiUp: + from femviewprovider import view_post_histogram + view_post_histogram.VPPostHistogramIndexOverFrames(obj.ViewObject) + return obj + + # ********* solver objects *********************************************************************** def makeEquationDeformation(doc, base_solver=None, name="Deformation"): """makeEquationDeformation(document, [base_solver], [name]): diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py index befcd48f30..f4edc1586e 100644 --- a/src/Mod/Fem/femcommands/commands.py +++ b/src/Mod/Fem/femcommands/commands.py @@ -1291,6 +1291,6 @@ if "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__: FreeCADGui.addCommand("FEM_PostFilterGlyph", _PostFilterGlyph()) # setup all visualization commands (register by importing) - import femobjects.post_histogram import femobjects.post_lineplot + import femobjects.post_histogram post_visualization.setup_commands("FEM_PostVisualization") diff --git a/src/Mod/Fem/femguiutils/data_extraction.py b/src/Mod/Fem/femguiutils/data_extraction.py index 4eeffbcef4..23a1bb784b 100644 --- a/src/Mod/Fem/femguiutils/data_extraction.py +++ b/src/Mod/Fem/femguiutils/data_extraction.py @@ -40,11 +40,13 @@ from vtkmodules.vtkFiltersGeneral import vtkSplitColumnComponents import FreeCAD import FreeCADGui +import femobjects.base_fempostextractors as extr from femtaskpanels.base_fempostpanel import _BasePostTaskPanel from . import extract_link_view ExtractLinkView = extract_link_view.ExtractLinkView + class DataExtraction(_BasePostTaskPanel): # The class is not a widget itself, but provides a widget. It implements # all required callbacks for the widget and the task dialog. @@ -137,3 +139,12 @@ class DataExtraction(_BasePostTaskPanel): def apply(self): pass + + def initiallyCollapsed(self): + # if we do not have any extractions to show we hide initially to remove clutter + + for obj in self.Object.InList: + if extr.is_extractor_object(obj): + return False + + return True diff --git a/src/Mod/Fem/femguiutils/extract_link_view.py b/src/Mod/Fem/femguiutils/extract_link_view.py index 9c984b537e..e1611f8609 100644 --- a/src/Mod/Fem/femguiutils/extract_link_view.py +++ b/src/Mod/Fem/femguiutils/extract_link_view.py @@ -198,16 +198,10 @@ class _SettingsPopup(QtGui.QGroupBox): close = QtCore.Signal() - def __init__(self, setting): - - toplevel = QtGui.QApplication.topLevelWidgets() - for i in toplevel: - if i.metaObject().className() == "Gui::MainWindow": - main = i - break - - super().__init__(main) + def __init__(self, setting, parent): + super().__init__(parent) + self.setWindowFlags(QtGui.Qt.Popup) self.setFocusPolicy(QtGui.Qt.ClickFocus) vbox = QtGui.QVBoxLayout() @@ -217,20 +211,22 @@ class _SettingsPopup(QtGui.QGroupBox): buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) vbox.addWidget(buttonBox) - buttonBox.accepted.connect(self.accept) + buttonBox.accepted.connect(self.hide) self.setLayout(vbox) - @QtCore.Slot() - def accept(self): - self.close.emit() - def showEvent(self, event): + # required to get keyboard events self.setFocus() - def keyPressEvent(self, event): - if event.key() == QtGui.Qt.Key_Enter or event.key() == QtGui.Qt.Key_Return: - self.accept() + def hideEvent(self, event): + # emit on hide: this happens for OK button as well as + # "click away" closing of the popup + self.close.emit() + def keyPressEvent(self, event): + # close on hitting enter + if event.key() == QtGui.Qt.Key_Enter or event.key() == QtGui.Qt.Key_Return: + self.hide() class _SummaryWidget(QtGui.QWidget): @@ -284,14 +280,12 @@ class _SummaryWidget(QtGui.QWidget): # make sure initial drawing happened self._redraw() + def _button(self, text): btn = QtGui.QPushButton(self) btn.full_text = text - #size = btn.sizeHint() - #size.setWidth(size.width()*2) btn.setMinimumSize(btn.sizeHint()) - btn.setFlat(True) btn.setText(text) btn.setStyleSheet("text-align:left;padding:6px"); @@ -313,7 +307,7 @@ class _SummaryWidget(QtGui.QWidget): for i, btn in enumerate(btns): btn_size = btn_total_size*btn_rel_size[i] - txt_size = btn_size - btn.iconSize().width() - btn_margin/2*3 + txt_size = btn_size - btn.iconSize().width() - btn_margin # we elide only if there is enough space for a meaningful text if txt_size >= min_text_width: @@ -355,24 +349,28 @@ class _SummaryWidget(QtGui.QWidget): def _position_dialog(self, dialog): - main = dialog.parent() - list_widget = self.parent().parent().parent() - widget_rect = list_widget.geometry() - diag_size = dialog.sizeHint() - # default is towards main window center - if main.geometry().center().x() >= list_widget.mapToGlobal(widget_rect.center()).x(): - rigth_point = list_widget.mapToGlobal(widget_rect.topRight()) - dialog.setGeometry(QtCore.QRect(rigth_point, diag_size)) - else: - left_point = list_widget.mapToGlobal(widget_rect.topLeft()) - left_point -= QtCore.QPoint(diag_size.width(), 0) - dialog.setGeometry(QtCore.QRect(left_point, diag_size)) + # the scroll area does mess the mapping to global up, somehow + # the transformation from the widget ot the scroll area gives + # very weird values. Hence we build the coords of the widget + # ourself + + summary = dialog.parent() # == self + base_widget = summary.parent() + viewport = summary.parent() + scroll = viewport.parent() + + top_left = summary.geometry().topLeft() + base_widget.geometry().topLeft() + viewport.geometry().topLeft() + delta = (summary.width() - dialog.sizeHint().width())/2 + local_point = QtCore.QPoint(top_left.x()+delta, top_left.y()+summary.height()) + global_point = scroll.mapToGlobal(local_point) + + dialog.setGeometry(QtCore.QRect(global_point, dialog.sizeHint())) @QtCore.Slot() def editApp(self): if not hasattr(self, "appDialog"): widget = self._extractor.ViewObject.Proxy.get_app_edit_widget(self._post_dialog) - self.appDialog = _SettingsPopup(widget) + self.appDialog = _SettingsPopup(widget, self) self.appDialog.close.connect(self.appAccept) if not self.appDialog.isVisible(): @@ -386,7 +384,7 @@ class _SummaryWidget(QtGui.QWidget): if not hasattr(self, "viewDialog"): widget = self._extractor.ViewObject.Proxy.get_view_edit_widget(self._post_dialog) - self.viewDialog = _SettingsPopup(widget) + self.viewDialog = _SettingsPopup(widget, self) self.viewDialog.close.connect(self.viewAccept) if not self.viewDialog.isVisible(): @@ -402,8 +400,6 @@ class _SummaryWidget(QtGui.QWidget): @QtCore.Slot() def viewAccept(self): - self.viewDialog.hide() - # update the preview extr_repr = self._extractor.ViewObject.Proxy.get_preview() self.viewButton.setIcon(extr_repr[0]) @@ -414,8 +410,6 @@ class _SummaryWidget(QtGui.QWidget): @QtCore.Slot() def appAccept(self): - self.appDialog.hide() - # update the preview extr_label = self._extractor.Proxy.get_representive_fieldname(self._extractor) self.extrButton.full_text = extr_label diff --git a/src/Mod/Fem/femobjects/base_fempostextractors.py b/src/Mod/Fem/femobjects/base_fempostextractors.py index 33ef4b4935..9e4ddac24f 100644 --- a/src/Mod/Fem/femobjects/base_fempostextractors.py +++ b/src/Mod/Fem/femobjects/base_fempostextractors.py @@ -116,6 +116,8 @@ class Extractor(base_fempythonobject.BaseFemPythonObject): return ["X", "Y"] case 3: return ["X", "Y", "Z"] + case 6: + return ["XX", "YY", "ZZ", "XY", "XZ", "YZ"] case _: return ["Not a vector"] @@ -217,11 +219,13 @@ class Extractor1D(Extractor): component_array.SetName(array.GetName()) table.AddColumn(component_array) - def _x_array_from_dataset(self, obj, dataset): + def _x_array_from_dataset(self, obj, dataset, copy=True): # extracts the relevant array from the dataset and returns a copy + # indices = None uses all indices, otherwise the values in this list match obj.XField: case "Index": + # index needs always to be build, ignore copy argument num = dataset.GetPoints().GetNumberOfPoints() array = vtkIntArray() array.SetNumberOfTuples(num) @@ -230,15 +234,22 @@ class Extractor1D(Extractor): array.SetValue(i,i) case "Position": + orig_array = dataset.GetPoints().GetData() - array = vtkDoubleArray() - array.DeepCopy(orig_array) + if copy: + array = vtkDoubleArray() + array.DeepCopy(orig_array) + else: + array = orig_array case _: point_data = dataset.GetPointData() orig_array = point_data.GetAbstractArray(obj.XField) - array = vtkDoubleArray() - array.DeepCopy(orig_array) + if copy: + array = vtkDoubleArray() + array.DeepCopy(orig_array) + else: + array = orig_array return array @@ -343,28 +354,29 @@ class Extractor2D(Extractor1D): component_array.SetName(array.GetName()) table.AddColumn(component_array) - def _y_array_from_dataset(self, obj, dataset): + def _y_array_from_dataset(self, obj, dataset, copy=True): # extracts the relevant array from the dataset and returns a copy + # indices = None uses all indices, otherwise the values in this list match obj.YField: - case "Index": - num = dataset.GetPoints().GetNumberOfPoints() - array = vtkIntArray() - array.SetNumberOfTuples(num) - array.SetNumberOfComponents(1) - for i in range(num): - array.SetValue(i,i) - case "Position": + orig_array = dataset.GetPoints().GetData() - array = vtkDoubleArray() - array.DeepCopy(orig_array) + if copy: + array = vtkDoubleArray() + array.DeepCopy(orig_array) + else: + array = orig_array case _: point_data = dataset.GetPointData() orig_array = point_data.GetAbstractArray(obj.YField) - array = vtkDoubleArray() - array.DeepCopy(orig_array) + + if copy: + array = vtkDoubleArray() + array.DeepCopy(orig_array) + else: + array = orig_array return array diff --git a/src/Mod/Fem/femobjects/post_extract1D.py b/src/Mod/Fem/femobjects/post_extract1D.py index f7a450c181..3540b6706a 100644 --- a/src/Mod/Fem/femobjects/post_extract1D.py +++ b/src/Mod/Fem/femobjects/post_extract1D.py @@ -33,6 +33,7 @@ from . import base_fempostextractors from . import base_fempythonobject _PropHelper = base_fempythonobject._PropHelper +from vtkmodules.vtkCommonCore import vtkDoubleArray from vtkmodules.vtkCommonDataModel import vtkTable from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline @@ -108,7 +109,7 @@ class PostFieldData1D(base_fempostextractors.Extractor1D): obj.Table = table -class PostIndexData1D(base_fempostextractors.Extractor1D): +class PostIndexOverFrames1D(base_fempostextractors.Extractor1D): """ A post processing extraction of one dimensional index data """ @@ -119,19 +120,67 @@ class PostIndexData1D(base_fempostextractors.Extractor1D): super().__init__(obj) def _get_properties(self): - prop =[ _PropHelper( - type="App::PropertyBool", - name="ExtractFrames", - group="Multiframe", - doc="Specify if the data at the index should be extracted for each frame", - value=False, - ), - _PropHelper( + prop =[_PropHelper( type="App::PropertyInteger", - name="XIndex", + name="Index", group="X Data", - doc="Specify for which point index the data should be extracted", + doc="Specify for which index the data should be extracted", value=0, ), ] return super()._get_properties() + prop + + def execute(self, obj): + + # on execution we populate the vtk table + table = vtkTable() + + if not obj.Source: + obj.Table = table + return + + dataset = obj.Source.getDataSet() + if not dataset: + obj.Table = table + return + + # check if we have timesteps (required!) + abort = True + info = obj.Source.getOutputAlgorithm().GetOutputInformation(0) + if info.Has(vtkStreamingDemandDrivenPipeline.TIME_STEPS()): + timesteps = info.Get(vtkStreamingDemandDrivenPipeline.TIME_STEPS()) + if len(timesteps) > 1: + abort = False + + if abort: + FreeCAD.Console.PrintWarning("Not sufficient frames available in data, cannot extract data") + obj.Table = table + return + + algo = obj.Source.getOutputAlgorithm() + setup = False + frame_array = vtkDoubleArray() + + idx = obj.Index + for i, timestep in enumerate(timesteps): + + algo.UpdateTimeStep(timestep) + dataset = algo.GetOutputDataObject(0) + array = self._x_array_from_dataset(obj, dataset, copy=False) + + if not setup: + frame_array.SetNumberOfComponents(array.GetNumberOfComponents()) + frame_array.SetNumberOfTuples(len(timesteps)) + setup = True + + frame_array.SetTuple(i, idx, array) + + if frame_array.GetNumberOfComponents() > 1: + frame_array.SetName(f"{obj.XField} ({obj.XComponent})") + else: + frame_array.SetName(f"{obj.XField}") + + self._x_array_component_to_table(obj, frame_array, table) + + # set the final table + obj.Table = table diff --git a/src/Mod/Fem/femobjects/post_extract2D.py b/src/Mod/Fem/femobjects/post_extract2D.py index b25b809655..60c9ac2df2 100644 --- a/src/Mod/Fem/femobjects/post_extract2D.py +++ b/src/Mod/Fem/femobjects/post_extract2D.py @@ -33,6 +33,7 @@ from . import base_fempostextractors from . import base_fempythonobject _PropHelper = base_fempythonobject._PropHelper +from vtkmodules.vtkCommonCore import vtkDoubleArray from vtkmodules.vtkCommonDataModel import vtkTable from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline @@ -124,9 +125,9 @@ class PostFieldData2D(base_fempostextractors.Extractor2D): obj.Table = table -class PostIndexData2D(base_fempostextractors.Extractor2D): +class PostIndexOverFrames2D(base_fempostextractors.Extractor2D): """ - A post processing extraction of one dimensional index data + A post processing extraction for two dimensional data with X always being the frames """ ExtractionType = "Index" @@ -135,19 +136,83 @@ class PostIndexData2D(base_fempostextractors.Extractor2D): super().__init__(obj) def _get_properties(self): - prop =[ _PropHelper( - type="App::PropertyBool", - name="ExtractFrames", - group="Multiframe", - doc="Specify if the data at the index should be extracted for each frame", - value=False, - ), - _PropHelper( + prop =[_PropHelper( type="App::PropertyInteger", - name="XIndex", - group="X Data", + name="Index", + group="Data", doc="Specify for which point index the data should be extracted", value=0, ), ] return super()._get_properties() + prop + + def _setup_x_component_property(self, obj, point_data): + # override to only allow "Frames" as X data + obj.XComponent = ["Not a vector"] + + def _setup_x_properties(self, obj, dataset): + # override to only allow "Frames" as X data + obj.XField = ["Frames"] + + def execute(self, obj): + + # on execution we populate the vtk table + table = vtkTable() + + if not obj.Source: + obj.Table = table + return + + dataset = obj.Source.getDataSet() + if not dataset: + obj.Table = table + return + + # check if we have timesteps (required!) + abort = True + info = obj.Source.getOutputAlgorithm().GetOutputInformation(0) + if info.Has(vtkStreamingDemandDrivenPipeline.TIME_STEPS()): + timesteps = info.Get(vtkStreamingDemandDrivenPipeline.TIME_STEPS()) + if len(timesteps) > 1: + abort = False + + if abort: + FreeCAD.Console.PrintWarning("Not sufficient frames available in data, cannot extract data") + obj.Table = table + return + + algo = obj.Source.getOutputAlgorithm() + + frame_x_array = vtkDoubleArray() + frame_x_array.SetNumberOfTuples(len(timesteps)) + frame_x_array.SetNumberOfComponents(1) + + + frame_y_array = vtkDoubleArray() + idx = obj.Index + setup = False + for i, timestep in enumerate(timesteps): + + frame_x_array.SetTuple1(i, timestep) + + algo.UpdateTimeStep(timestep) + dataset = algo.GetOutputDataObject(0) + array = self._y_array_from_dataset(obj, dataset, copy=False) + if not setup: + frame_y_array.SetNumberOfComponents(array.GetNumberOfComponents()) + frame_y_array.SetNumberOfTuples(len(timesteps)) + setup = True + + frame_y_array.SetTuple(i, idx, array) + + frame_x_array.SetName("Frames") + if frame_y_array.GetNumberOfComponents() > 1: + frame_y_array.SetName(f"{obj.YField} ({obj.YComponent})") + else: + frame_y_array.SetName(obj.YField) + + table.AddColumn(frame_x_array) + self._y_array_component_to_table(obj, frame_y_array, table) + + # set the final table + obj.Table = table diff --git a/src/Mod/Fem/femobjects/post_histogram.py b/src/Mod/Fem/femobjects/post_histogram.py index 8cbd72b41c..bdcb4ad553 100644 --- a/src/Mod/Fem/femobjects/post_histogram.py +++ b/src/Mod/Fem/femobjects/post_histogram.py @@ -57,6 +57,14 @@ post_visualization.register_extractor("Histogram", "makePostHistogramFieldData") +post_visualization.register_extractor("Histogram", + "HistogramIndexOverFrames", + ":/icons/FEM_PostIndex.svg", + "1D", + "Index", + "ObjectsFem", + "makePostHistogramIndexOverFrames") + # Implementation # ############## @@ -77,7 +85,11 @@ class PostHistogramFieldData(post_extract1D.PostFieldData1D): """ VisualizationType = "Histogram" - +class PostHistogramIndexOverFrames(post_extract1D.PostIndexOverFrames1D): + """ + A 1D index extraction for histogram. + """ + VisualizationType = "Histogram" class PostHistogram(base_fempostvisualizations.PostVisualization): diff --git a/src/Mod/Fem/femobjects/post_lineplot.py b/src/Mod/Fem/femobjects/post_lineplot.py index 272aaea74c..06f844ebac 100644 --- a/src/Mod/Fem/femobjects/post_lineplot.py +++ b/src/Mod/Fem/femobjects/post_lineplot.py @@ -56,6 +56,14 @@ post_visualization.register_extractor("Lineplot", "ObjectsFem", "makePostLineplotFieldData") +post_visualization.register_extractor("Lineplot", + "LineplotIndexOverFrames", + ":/icons/FEM_PostIndex.svg", + "2D", + "Index", + "ObjectsFem", + "makePostLineplotIndexOverFrames") + # Implementation # ############## @@ -77,6 +85,12 @@ class PostLineplotFieldData(post_extract2D.PostFieldData2D): """ VisualizationType = "Lineplot" +class PostLineplotIndexOverFrames(post_extract2D.PostIndexOverFrames2D): + """ + A 2D index extraction for lineplot. + """ + VisualizationType = "Lineplot" + class PostLineplot(base_fempostvisualizations.PostVisualization): diff --git a/src/Mod/Fem/femtaskpanels/task_post_histogram.py b/src/Mod/Fem/femtaskpanels/task_post_histogram.py index 593f177a94..79b4e4eeab 100644 --- a/src/Mod/Fem/femtaskpanels/task_post_histogram.py +++ b/src/Mod/Fem/femtaskpanels/task_post_histogram.py @@ -64,6 +64,7 @@ class _TaskPanel(base_fempostpanel._BasePostTaskPanel): self.data_widget.setLayout(vbox) self.data_widget.setWindowTitle("Histogram data") + self.data_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostHistogram.svg")) # histogram parameter widget @@ -71,6 +72,8 @@ class _TaskPanel(base_fempostpanel._BasePostTaskPanel): FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostHistogram.ui" ) self.view_widget.setWindowTitle("Histogram view settings") + self.view_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostHistogram.svg")) + self.__init_widgets() # form made from param and selection widget diff --git a/src/Mod/Fem/femtaskpanels/task_post_lineplot.py b/src/Mod/Fem/femtaskpanels/task_post_lineplot.py index 650cdd70a1..507e6b3cbf 100644 --- a/src/Mod/Fem/femtaskpanels/task_post_lineplot.py +++ b/src/Mod/Fem/femtaskpanels/task_post_lineplot.py @@ -64,6 +64,7 @@ class _TaskPanel(base_fempostpanel._BasePostTaskPanel): self.data_widget.setLayout(vbox) self.data_widget.setWindowTitle("Lineplot data") + self.data_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostLineplot.svg")) # lineplot parameter widget @@ -71,6 +72,8 @@ class _TaskPanel(base_fempostpanel._BasePostTaskPanel): FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostLineplot.ui" ) self.view_widget.setWindowTitle("Lineplot view settings") + self.view_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostLineplot.svg")) + self.__init_widgets() # form made from param and selection widget diff --git a/src/Mod/Fem/femviewprovider/view_post_histogram.py b/src/Mod/Fem/femviewprovider/view_post_histogram.py index 8fe2fa6b4c..777e3d15c0 100644 --- a/src/Mod/Fem/femviewprovider/view_post_histogram.py +++ b/src/Mod/Fem/femviewprovider/view_post_histogram.py @@ -36,6 +36,7 @@ import Plot import FemGui from PySide import QtGui, QtCore +import io import numpy as np import matplotlib as mpl @@ -73,25 +74,59 @@ class EditViewWidget(QtGui.QWidget): self._post_dialog._enumPropertyToCombobox(vobj, "LineStyle", self.widget.LineStyle) self.widget.LineWidth.setValue(vobj.LineWidth) self.widget.HatchDensity.setValue(vobj.HatchDensity) - self.widget.BarColor.setProperty("color", QtGui.QColor(*[v*255 for v in vobj.BarColor])) - self.widget.LineColor.setProperty("color", QtGui.QColor(*[v*255 for v in vobj.LineColor])) + + # setup the color buttons (don't use FreeCADs color button, as this does not work in popups!) + self._setup_color_button(self.widget.BarColor, vobj.BarColor, self.barColorChanged) + self._setup_color_button(self.widget.LineColor, vobj.LineColor, self.lineColorChanged) self.widget.Legend.editingFinished.connect(self.legendChanged) self.widget.Hatch.activated.connect(self.hatchPatternChanged) self.widget.LineStyle.activated.connect(self.lineStyleChanged) self.widget.HatchDensity.valueChanged.connect(self.hatchDensityChanged) self.widget.LineWidth.valueChanged.connect(self.lineWidthChanged) - self.widget.LineColor.changed.connect(self.lineColorChanged) - self.widget.BarColor.changed.connect(self.barColorChanged) - @QtCore.Slot() - def lineColorChanged(self): - color = self.widget.LineColor.property("color") + # sometimes wierd sizes occur with spinboxes + self.widget.HatchDensity.setMaximumHeight(self.widget.Hatch.sizeHint().height()) + self.widget.LineWidth.setMaximumHeight(self.widget.LineStyle.sizeHint().height()) + + + def _setup_color_button(self, button, fcColor, callback): + + barColor = QtGui.QColor(*[v*255 for v in fcColor]) + icon_size = button.iconSize() + icon_size.setWidth(icon_size.width()*2) + button.setIconSize(icon_size) + pixmap = QtGui.QPixmap(icon_size) + pixmap.fill(barColor) + button.setIcon(pixmap) + + action = QtGui.QWidgetAction(button) + diag = QtGui.QColorDialog(barColor, parent=button) + diag.accepted.connect(action.trigger) + diag.rejected.connect(action.trigger) + diag.colorSelected.connect(callback) + + action.setDefaultWidget(diag) + button.addAction(action) + button.setPopupMode(QtGui.QToolButton.InstantPopup) + + + @QtCore.Slot(QtGui.QColor) + def lineColorChanged(self, color): + + pixmap = QtGui.QPixmap(self.widget.LineColor.iconSize()) + pixmap.fill(color) + self.widget.LineColor.setIcon(pixmap) + self._object.ViewObject.LineColor = color.getRgb() - @QtCore.Slot() - def barColorChanged(self): - color = self.widget.BarColor.property("color") + @QtCore.Slot(QtGui.QColor) + def barColorChanged(self, color): + + pixmap = QtGui.QPixmap(self.widget.BarColor.iconSize()) + pixmap.fill(color) + self.widget.BarColor.setIcon(pixmap) + self._object.ViewObject.BarColor = color.getRgb() @QtCore.Slot(float) @@ -115,7 +150,7 @@ class EditViewWidget(QtGui.QWidget): self._object.ViewObject.Legend = self.widget.Legend.text() -class EditAppWidget(QtGui.QWidget): +class EditFieldAppWidget(QtGui.QWidget): def __init__(self, obj, post_dialog): super().__init__() @@ -160,6 +195,54 @@ class EditAppWidget(QtGui.QWidget): self._object.ExtractFrames = extract self._post_dialog._recompute() +class EditIndexAppWidget(QtGui.QWidget): + + def __init__(self, obj, post_dialog): + super().__init__() + + self._object = obj + self._post_dialog = post_dialog + + # load the ui and set it up + self.widget = FreeCADGui.PySideUic.loadUi( + FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostHistogramIndexAppEdit.ui" + ) + layout = QtGui.QVBoxLayout() + layout.addWidget(self.widget) + self.setLayout(layout) + + self.__init_widget() + + def __init_widget(self): + # set the other properties + + self.widget.Index.setValue(self._object.Index) + self._post_dialog._enumPropertyToCombobox(self._object, "XField", self.widget.Field) + self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.Component) + + self.widget.Index.valueChanged.connect(self.indexChanged) + self.widget.Field.activated.connect(self.fieldChanged) + self.widget.Component.activated.connect(self.componentChanged) + + # sometimes wierd sizes occur with spinboxes + self.widget.Index.setMaximumHeight(self.widget.Field.sizeHint().height()) + + @QtCore.Slot(int) + def fieldChanged(self, index): + self._object.XField = index + self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.Component) + self._post_dialog._recompute() + + @QtCore.Slot(int) + def componentChanged(self, index): + self._object.XComponent = index + self._post_dialog._recompute() + + @QtCore.Slot(int) + def indexChanged(self, value): + self._object.Index = value + self._post_dialog._recompute() + class VPPostHistogramFieldData(view_post_extract.VPPostExtractor): """ @@ -233,13 +316,30 @@ class VPPostHistogramFieldData(view_post_extract.VPPostExtractor): return ":/icons/FEM_PostField.svg" def get_app_edit_widget(self, post_dialog): - return EditAppWidget(self.Object, post_dialog) + return EditFieldAppWidget(self.Object, post_dialog) def get_view_edit_widget(self, post_dialog): return EditViewWidget(self.Object, post_dialog) def get_preview(self): - return (QtGui.QPixmap(), self.ViewObject.Legend) + + fig = mpl.pyplot.figure(figsize=(0.4,0.2), dpi=500) + ax = mpl.pyplot.Axes(fig, [0., 0., 2, 1]) + ax.set_axis_off() + fig.add_axes(ax) + + kwargs = self.get_kw_args() + patch = mpl.patches.Rectangle(xy=(0,0), width=2, height=1, **kwargs) + ax.add_patch(patch) + + data = io.BytesIO() + mpl.pyplot.savefig(data, bbox_inches=0, transparent=True) + mpl.pyplot.close() + + pixmap = QtGui.QPixmap() + pixmap.loadFromData(data.getvalue()) + + return (pixmap, self.ViewObject.Legend) def get_kw_args(self): # builds kw args from the properties @@ -256,6 +356,21 @@ class VPPostHistogramFieldData(view_post_extract.VPPostExtractor): return kwargs +class VPPostHistogramIndexOverFrames(VPPostHistogramFieldData): + """ + A View Provider for extraction of 1D index over frames data + """ + + def __init__(self, vobj): + super().__init__(vobj) + + def getIcon(self): + return ":/icons/FEM_PostIndex.svg" + + def get_app_edit_widget(self, post_dialog): + return EditIndexAppWidget(self.Object, post_dialog) + + class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization): """ A View Provider for Histogram plots @@ -365,15 +480,26 @@ class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization): def show_visualization(self): if not hasattr(self, "_plot") or not self._plot: + main = Plot.getMainWindow() self._plot = Plot.Plot() - self._dialog = QtGui.QDialog(Plot.getMainWindow()) + self._plot.destroyed.connect(self.destroyed) + self._dialog = QtGui.QDialog(main) box = QtGui.QVBoxLayout() box.addWidget(self._plot) + self._dialog.resize(main.size().height()/2, main.size().height()/3) # keep it square self._dialog.setLayout(box) self.drawPlot() self._dialog.show() + + def destroyed(self, obj): + print("*********************************************************") + print("**************** ******************") + print("**************** destroy ******************") + print("**************** ******************") + print("*********************************************************") + def get_kw_args(self, obj): view = obj.ViewObject if not view or not hasattr(view, "Proxy"): diff --git a/src/Mod/Fem/femviewprovider/view_post_lineplot.py b/src/Mod/Fem/femviewprovider/view_post_lineplot.py index 54fe5439f3..f98a5bf1e4 100644 --- a/src/Mod/Fem/femviewprovider/view_post_lineplot.py +++ b/src/Mod/Fem/femviewprovider/view_post_lineplot.py @@ -39,7 +39,6 @@ from PySide import QtGui, QtCore import io import numpy as np import matplotlib as mpl -import matplotlib.pyplot as plt from vtkmodules.numpy_interface.dataset_adapter import VTKArray @@ -75,18 +74,47 @@ class EditViewWidget(QtGui.QWidget): self._post_dialog._enumPropertyToCombobox(vobj, "MarkerStyle", self.widget.MarkerStyle) self.widget.LineWidth.setValue(vobj.LineWidth) self.widget.MarkerSize.setValue(vobj.MarkerSize) - self.widget.Color.setProperty("color", QtGui.QColor(*[v*255 for v in vobj.Color])) + + self._setup_color_button(self.widget.Color, vobj.Color, self.colorChanged) self.widget.Legend.editingFinished.connect(self.legendChanged) self.widget.MarkerStyle.activated.connect(self.markerStyleChanged) self.widget.LineStyle.activated.connect(self.lineStyleChanged) self.widget.MarkerSize.valueChanged.connect(self.markerSizeChanged) self.widget.LineWidth.valueChanged.connect(self.lineWidthChanged) - self.widget.Color.changed.connect(self.colorChanged) - @QtCore.Slot() - def colorChanged(self): - color = self.widget.Color.property("color") + # sometimes wierd sizes occur with spinboxes + self.widget.MarkerSize.setMaximumHeight(self.widget.MarkerStyle.sizeHint().height()) + self.widget.LineWidth.setMaximumHeight(self.widget.LineStyle.sizeHint().height()) + + def _setup_color_button(self, button, fcColor, callback): + + barColor = QtGui.QColor(*[v*255 for v in fcColor]) + icon_size = button.iconSize() + icon_size.setWidth(icon_size.width()*2) + button.setIconSize(icon_size) + pixmap = QtGui.QPixmap(icon_size) + pixmap.fill(barColor) + button.setIcon(pixmap) + + action = QtGui.QWidgetAction(button) + diag = QtGui.QColorDialog(barColor, parent=button) + diag.accepted.connect(action.trigger) + diag.rejected.connect(action.trigger) + diag.colorSelected.connect(callback) + + action.setDefaultWidget(diag) + button.addAction(action) + button.setPopupMode(QtGui.QToolButton.InstantPopup) + + + @QtCore.Slot(QtGui.QColor) + def colorChanged(self, color): + + pixmap = QtGui.QPixmap(self.widget.Color.iconSize()) + pixmap.fill(color) + self.widget.Color.setIcon(pixmap) + self._object.ViewObject.Color = color.getRgb() @QtCore.Slot(float) @@ -110,7 +138,7 @@ class EditViewWidget(QtGui.QWidget): self._object.ViewObject.Legend = self.widget.Legend.text() -class EditAppWidget(QtGui.QWidget): +class EditFieldAppWidget(QtGui.QWidget): def __init__(self, obj, post_dialog): super().__init__() @@ -171,9 +199,58 @@ class EditAppWidget(QtGui.QWidget): self._post_dialog._recompute() +class EditIndexAppWidget(QtGui.QWidget): + + def __init__(self, obj, post_dialog): + super().__init__() + + self._object = obj + self._post_dialog = post_dialog + + # load the ui and set it up + self.widget = FreeCADGui.PySideUic.loadUi( + FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostLineplotIndexAppEdit.ui" + ) + layout = QtGui.QVBoxLayout() + layout.addWidget(self.widget) + self.setLayout(layout) + + self.__init_widget() + + def __init_widget(self): + # set the other properties + + self.widget.Index.setValue(self._object.Index) + self._post_dialog._enumPropertyToCombobox(self._object, "YField", self.widget.YField) + self._post_dialog._enumPropertyToCombobox(self._object, "YComponent", self.widget.YComponent) + + self.widget.Index.valueChanged.connect(self.indexChanged) + self.widget.YField.activated.connect(self.yFieldChanged) + self.widget.YComponent.activated.connect(self.yComponentChanged) + + # sometimes wierd sizes occur with spinboxes + self.widget.Index.setMaximumHeight(self.widget.YField.sizeHint().height()) + + @QtCore.Slot(int) + def indexChanged(self, value): + self._object.Index = value + self._post_dialog._recompute() + + @QtCore.Slot(int) + def yFieldChanged(self, index): + self._object.YField = index + self._post_dialog._enumPropertyToCombobox(self._object, "YComponent", self.widget.YComponent) + self._post_dialog._recompute() + + @QtCore.Slot(int) + def yComponentChanged(self, index): + self._object.YComponent = index + self._post_dialog._recompute() + + class VPPostLineplotFieldData(view_post_extract.VPPostExtractor): """ - A View Provider for extraction of 1D field data specialy for histograms + A View Provider for extraction of 2D field data specialy for histograms """ def __init__(self, vobj): @@ -236,7 +313,7 @@ class VPPostLineplotFieldData(view_post_extract.VPPostExtractor): return ":/icons/FEM_PostField.svg" def get_app_edit_widget(self, post_dialog): - return EditAppWidget(self.Object, post_dialog) + return EditFieldAppWidget(self.Object, post_dialog) def get_view_edit_widget(self, post_dialog): return EditViewWidget(self.Object, post_dialog) @@ -245,16 +322,16 @@ class VPPostLineplotFieldData(view_post_extract.VPPostExtractor): # Returns the preview tuple of icon and label: (QPixmap, str) # Note: QPixmap in ratio 2:1 - fig = plt.figure(figsize=(0.2,0.1), dpi=1000) - ax = plt.Axes(fig, [0., 0., 1., 1.]) + fig = mpl.pyplot.figure(figsize=(0.2,0.1), dpi=1000) + ax = mpl.pyplot.Axes(fig, [0., 0., 1., 1.]) ax.set_axis_off() fig.add_axes(ax) kwargs = self.get_kw_args() kwargs["markevery"] = [1] ax.plot([0,0.5,1],[0.5,0.5,0.5], **kwargs) data = io.BytesIO() - plt.savefig(data, bbox_inches=0, transparent=True) - plt.close() + mpl.pyplot.savefig(data, bbox_inches=0, transparent=True) + mpl.pyplot.close() pixmap = QtGui.QPixmap() pixmap.loadFromData(data.getvalue()) @@ -277,6 +354,21 @@ class VPPostLineplotFieldData(view_post_extract.VPPostExtractor): return kwargs +class VPPostLineplotIndexOverFrames(VPPostLineplotFieldData): + """ + A View Provider for extraction of 2D index over frames data + """ + + def __init__(self, vobj): + super().__init__(vobj) + + def getIcon(self): + return ":/icons/FEM_PostIndex.svg" + + def get_app_edit_widget(self, post_dialog): + return EditIndexAppWidget(self.Object, post_dialog) + + class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization): """ A View Provider for Lineplot plots @@ -366,9 +458,11 @@ class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization): if not hasattr(self, "_plot") or not self._plot: self._plot = Plot.Plot() - self._dialog = QtGui.QDialog(Plot.getMainWindow()) + main = Plot.getMainWindow() + self._dialog = QtGui.QDialog(main) box = QtGui.QVBoxLayout() box.addWidget(self._plot) + self._dialog.resize(main.size().height()/2, main.size().height()/3) # keep aspect ratio constant self._dialog.setLayout(box) self.drawPlot() diff --git a/src/Mod/Plot/Plot.py b/src/Mod/Plot/Plot.py index 4f5a360745..a3423ae93d 100644 --- a/src/Mod/Plot/Plot.py +++ b/src/Mod/Plot/Plot.py @@ -28,7 +28,7 @@ import sys try: import matplotlib - matplotlib.use("Qt5Agg") + matplotlib.use("QtAgg") # Force matplotlib to use PySide backend by temporarily unloading PyQt if "PyQt5.QtCore" in sys.modules: @@ -36,10 +36,11 @@ try: import matplotlib.pyplot as plt import PyQt5.QtCore else: + print("default matplotlib import") import matplotlib.pyplot as plt - from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas - from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar + from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas + from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure except ImportError: