| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | __title__ = "FreeCAD FEM postprocessing line plot ViewProvider for the document object" |
| | __author__ = "Stefan Tröger" |
| | __url__ = "https://www.freecad.org" |
| |
|
| | |
| | |
| | |
| |
|
| | import FreeCAD |
| | import FreeCADGui |
| |
|
| | import Plot |
| | from PySide import QtGui, QtCore |
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| |
|
| | import io |
| | import numpy as np |
| | import matplotlib as mpl |
| | from packaging.version import Version |
| |
|
| | from vtkmodules.numpy_interface.dataset_adapter import VTKArray |
| |
|
| | from . import view_base_fempostextractors |
| | from . import view_base_fempostvisualization |
| | from femtaskpanels import task_post_histogram |
| |
|
| | from . import view_base_femobject |
| |
|
| | _GuiPropHelper = view_base_femobject._GuiPropHelper |
| |
|
| |
|
| | class EditViewWidget(QtGui.QWidget): |
| |
|
| | def __init__(self, obj, post_dialog): |
| | super().__init__() |
| |
|
| | self._object = obj |
| | self._post_dialog = post_dialog |
| |
|
| | |
| | self.widget = FreeCADGui.PySideUic.loadUi( |
| | FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostHistogramFieldViewEdit.ui" |
| | ) |
| | layout = QtGui.QVBoxLayout() |
| | layout.addWidget(self.widget) |
| | self.setLayout(layout) |
| |
|
| | self.__init_widget() |
| |
|
| | def __init_widget(self): |
| | vobj = self._object.ViewObject |
| |
|
| | self.widget.Legend.setText(vobj.Legend) |
| | self._post_dialog._enumPropertyToCombobox(vobj, "Hatch", self.widget.Hatch) |
| | self._post_dialog._enumPropertyToCombobox(vobj, "LineStyle", self.widget.LineStyle) |
| | self.widget.LineWidth.setValue(vobj.LineWidth) |
| | self.widget.HatchDensity.setValue(vobj.HatchDensity) |
| |
|
| | |
| | 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.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.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) |
| | 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(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) |
| | def lineWidthChanged(self, value): |
| | self._object.ViewObject.LineWidth = value |
| |
|
| | @QtCore.Slot(float) |
| | def hatchDensityChanged(self, value): |
| | self._object.ViewObject.HatchDensity = value |
| |
|
| | @QtCore.Slot(int) |
| | def hatchPatternChanged(self, index): |
| | self._object.ViewObject.Hatch = index |
| |
|
| | @QtCore.Slot(int) |
| | def lineStyleChanged(self, index): |
| | self._object.ViewObject.LineStyle = index |
| |
|
| | @QtCore.Slot() |
| | def legendChanged(self): |
| | self._object.ViewObject.Legend = self.widget.Legend.text() |
| |
|
| |
|
| | class EditFieldAppWidget(QtGui.QWidget): |
| |
|
| | def __init__(self, obj, post_dialog): |
| | super().__init__() |
| |
|
| | self._object = obj |
| | self._post_dialog = post_dialog |
| |
|
| | |
| | self.widget = FreeCADGui.PySideUic.loadUi( |
| | FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostHistogramFieldAppEdit.ui" |
| | ) |
| | layout = QtGui.QVBoxLayout() |
| | layout.addWidget(self.widget) |
| | self.setLayout(layout) |
| |
|
| | self.__init_widget() |
| |
|
| | def __init_widget(self): |
| | |
| |
|
| | self._post_dialog._enumPropertyToCombobox(self._object, "XField", self.widget.Field) |
| | self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.Component) |
| | self.widget.Extract.setChecked(self._object.ExtractFrames) |
| |
|
| | self.widget.Field.activated.connect(self.fieldChanged) |
| | self.widget.Component.activated.connect(self.componentChanged) |
| | self.widget.Extract.toggled.connect(self.extractionChanged) |
| |
|
| | @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(bool) |
| | def extractionChanged(self, extract): |
| | 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 |
| |
|
| | |
| | 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): |
| | |
| |
|
| | 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) |
| |
|
| | |
| | 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_base_fempostextractors.VPPostExtractor): |
| | """ |
| | A View Provider for extraction of 1D field data specially for histograms |
| | """ |
| |
|
| | def __init__(self, vobj): |
| | super().__init__(vobj) |
| | vobj.Proxy = self |
| |
|
| | def _get_properties(self): |
| |
|
| | prop = [ |
| | _GuiPropHelper( |
| | type="App::PropertyString", |
| | name="Legend", |
| | group="HistogramPlot", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The name used in the plots legend"), |
| | value="", |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyColor", |
| | name="BarColor", |
| | group="HistogramBar", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The color the data bin area is drawn with"), |
| | value=(0, 85, 255, 255), |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyEnumeration", |
| | name="Hatch", |
| | group="HistogramBar", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The hatch pattern drawn in the bar"), |
| | value=["None", "/", "\\", "|", "-", "+", "x", "o", "O", ".", "*"], |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyIntegerConstraint", |
| | name="HatchDensity", |
| | group="HistogramBar", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The line width of the hatch)"), |
| | value=(1, 1, 99, 1), |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyColor", |
| | name="LineColor", |
| | group="HistogramLine", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The color the data bin area is drawn with"), |
| | value=(0, 0, 0, 1), |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyFloatConstraint", |
| | name="LineWidth", |
| | group="HistogramLine", |
| | doc=QT_TRANSLATE_NOOP( |
| | "FEM", "The width of the bar, between 0 and 1 (1 being without gaps)" |
| | ), |
| | value=(1, 0, 99, 0.1), |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyEnumeration", |
| | name="LineStyle", |
| | group="HistogramLine", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The style the line is drawn in"), |
| | value=["None", "-", "--", "-.", ":"], |
| | ), |
| | ] |
| | return super()._get_properties() + prop |
| |
|
| | def attach(self, vobj): |
| | self.Object = vobj.Object |
| | self.ViewObject = vobj |
| |
|
| | def getIcon(self): |
| | return ":/icons/FEM_PostField.svg" |
| |
|
| | def get_app_edit_widget(self, 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): |
| |
|
| | fig = mpl.pyplot.figure(figsize=(0.4, 0.2), dpi=500) |
| | ax = mpl.pyplot.Axes(fig, [0.0, 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): |
| | |
| | kwargs = {} |
| |
|
| | |
| | kwargs["edgecolor"] = self.ViewObject.LineColor |
| | kwargs["facecolor"] = self.ViewObject.BarColor |
| | kwargs["linestyle"] = self.ViewObject.LineStyle |
| | kwargs["linewidth"] = self.ViewObject.LineWidth |
| | if self.ViewObject.Hatch != "None": |
| | kwargs["hatch"] = self.ViewObject.Hatch * self.ViewObject.HatchDensity |
| |
|
| | return kwargs |
| |
|
| | def get_default_color_property(self): |
| | return "BarColor" |
| |
|
| |
|
| | 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 |
| | """ |
| |
|
| | def __init__(self, vobj): |
| | super().__init__(vobj) |
| |
|
| | def _get_properties(self): |
| |
|
| | prop = [ |
| | _GuiPropHelper( |
| | type="App::PropertyBool", |
| | name="Cumulative", |
| | group="Histogram", |
| | doc=QT_TRANSLATE_NOOP( |
| | "FEM", "If the bars should show the cumulative sum left to right" |
| | ), |
| | value=False, |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyEnumeration", |
| | name="Type", |
| | group="Histogram", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The type of histogram plotted"), |
| | value=["bar", "barstacked", "step", "stepfilled"], |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyFloatConstraint", |
| | name="BarWidth", |
| | group="Histogram", |
| | doc=QT_TRANSLATE_NOOP( |
| | "FEM", "The width of the bar, between 0 and 1 (1 being without gaps)" |
| | ), |
| | value=(0.9, 0, 1, 0.05), |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyFloatConstraint", |
| | name="HatchLineWidth", |
| | group="Histogram", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The line width of all drawn hatch patterns"), |
| | value=(1, 0, 99, 0.1), |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyInteger", |
| | name="Bins", |
| | group="Histogram", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The number of bins the data is split into"), |
| | value=10, |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyString", |
| | name="Title", |
| | group="Plot", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The histogram plot title"), |
| | value="", |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyString", |
| | name="XLabel", |
| | group="Plot", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The label shown for the histogram X axis"), |
| | value="", |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyString", |
| | name="YLabel", |
| | group="Plot", |
| | doc=QT_TRANSLATE_NOOP("FEM", "The label shown for the histogram Y axis"), |
| | value="", |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyBool", |
| | name="Legend", |
| | group="Plot", |
| | doc=QT_TRANSLATE_NOOP("FEM", "Determines if the legend is plotted"), |
| | value=True, |
| | ), |
| | _GuiPropHelper( |
| | type="App::PropertyEnumeration", |
| | name="LegendLocation", |
| | group="Plot", |
| | doc=QT_TRANSLATE_NOOP("FEM", "Determines if the legend is plotted"), |
| | value=[ |
| | "best", |
| | "upper right", |
| | "upper left", |
| | "lower left", |
| | "lower right", |
| | "right", |
| | "center left", |
| | "center right", |
| | "lower center", |
| | "upper center", |
| | "center", |
| | ], |
| | ), |
| | ] |
| | return prop |
| |
|
| | def getIcon(self): |
| | return ":/icons/FEM_PostHistogram.svg" |
| |
|
| | def setEdit(self, vobj, mode): |
| |
|
| | |
| | taskd = task_post_histogram._TaskPanel(vobj) |
| |
|
| | |
| | FreeCADGui.Control.showDialog(taskd) |
| |
|
| | return True |
| |
|
| | def show_visualization(self): |
| |
|
| | if not hasattr(self, "_plot") or not self._plot: |
| | main = FreeCADGui.getMainWindow() |
| | self._plot = Plot.Plot() |
| | self._plot.setWindowTitle(self.Object.Label) |
| | self._plot.setParent(main) |
| | self._plot.setWindowFlags(QtGui.Qt.Dialog) |
| | self._plot.resize(main.size().height() / 2, main.size().height() / 3) |
| | self.update_visualization() |
| |
|
| | self._plot.show() |
| |
|
| | def get_kw_args(self, obj): |
| | view = obj.ViewObject |
| | if not view or not hasattr(view, "Proxy"): |
| | return {} |
| | if not hasattr(view.Proxy, "get_kw_args"): |
| | return {} |
| | return view.Proxy.get_kw_args() |
| |
|
| | def update_visualization(self): |
| |
|
| | if not hasattr(self, "_plot") or not self._plot: |
| | return |
| |
|
| | self._plot.axes.clear() |
| | bins = self.ViewObject.Bins |
| |
|
| | |
| | |
| | full_args = [] |
| | full_data = [] |
| | labels = [] |
| | for child in self.Object.Group: |
| |
|
| | table = child.Table |
| | kwargs = self.get_kw_args(child) |
| |
|
| | |
| | color_factor = np.linspace(1, 0.5, table.GetNumberOfColumns()) |
| | legend_multiframe = table.GetNumberOfColumns() > 1 |
| | for i in range(table.GetNumberOfColumns()): |
| |
|
| | |
| | args = kwargs.copy() |
| | for key in kwargs: |
| |
|
| | if "color" in key: |
| | value = np.array(kwargs[key]) * color_factor[i] |
| | args[key] = mpl.colors.to_hex(value) |
| |
|
| | full_args.append(args) |
| |
|
| | data = VTKArray(table.GetColumn(i)) |
| | full_data.append(data) |
| |
|
| | |
| | if child.ViewObject.Legend: |
| | if not legend_multiframe: |
| | labels.append(child.ViewObject.Legend) |
| | else: |
| | postfix = table.GetColumnName(i).split("-")[-1] |
| | labels.append(child.ViewObject.Legend + " - " + postfix) |
| | else: |
| | legend_prefix = "" |
| | if len(self.Object.Group) > 1: |
| | legend_prefix = child.Source.Label + ": " |
| | labels.append(legend_prefix + table.GetColumnName(i)) |
| |
|
| | args = {} |
| | args["rwidth"] = self.ViewObject.BarWidth |
| | args["cumulative"] = self.ViewObject.Cumulative |
| | args["histtype"] = self.ViewObject.Type |
| | args["label"] = labels |
| | if Version(mpl.__version__) >= Version("3.10.0"): |
| | args["hatch_linewidth"] = self.ViewObject.HatchLineWidth |
| |
|
| | n, b, patches = self._plot.axes.hist(full_data, bins, **args) |
| |
|
| | |
| | if len(full_args) == 1: |
| | for patch in patches: |
| | patch.set(**full_args[0]) |
| | elif len(full_args) > 1: |
| | for i, args in enumerate(full_args): |
| | for patch in patches[i]: |
| | patch.set(**full_args[i]) |
| |
|
| | |
| | if self.ViewObject.Title: |
| | self._plot.axes.set_title(self.ViewObject.Title) |
| | if self.ViewObject.XLabel: |
| | self._plot.axes.set_xlabel(self.ViewObject.XLabel) |
| | if self.ViewObject.YLabel: |
| | self._plot.axes.set_ylabel(self.ViewObject.YLabel) |
| |
|
| | if self.ViewObject.Legend and labels: |
| | self._plot.axes.legend(loc=self.ViewObject.LegendLocation) |
| |
|
| | self._plot.update() |
| |
|
| | def get_next_default_color(self): |
| | |
| | |
| | i = len(self.Object.Group) |
| | cmap = mpl.pyplot.get_cmap("tab10") |
| | return cmap(i) |
| |
|