Fem: Implement lineplot visualization
This commit is contained in:
@@ -36,7 +36,7 @@ SET(FemBaseModules_SRCS
|
||||
coding_conventions.md
|
||||
Init.py
|
||||
InitGui.py
|
||||
# ObjectsFem.py
|
||||
ObjectsFem.py
|
||||
TestFemApp.py
|
||||
CreateLabels.py
|
||||
)
|
||||
@@ -220,7 +220,9 @@ if(BUILD_FEM_VTK_PYTHON)
|
||||
${FemObjects_SRCS}
|
||||
femobjects/post_glyphfilter.py
|
||||
femobjects/post_extract1D.py
|
||||
femobjects/post_extract2D.py
|
||||
femobjects/post_histogram.py
|
||||
femobjects/post_lineplot.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
@@ -634,6 +636,7 @@ if(BUILD_FEM_VTK_PYTHON)
|
||||
${FemGuiTaskPanels_SRCS}
|
||||
femtaskpanels/task_post_glyphfilter.py
|
||||
femtaskpanels/task_post_histogram.py
|
||||
femtaskpanels/task_post_lineplot.py
|
||||
femtaskpanels/task_post_extractor.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
@@ -700,6 +703,7 @@ if(BUILD_FEM_VTK_PYTHON)
|
||||
femviewprovider/view_post_glyphfilter.py
|
||||
femviewprovider/view_post_extract.py
|
||||
femviewprovider/view_post_histogram.py
|
||||
femviewprovider/view_post_lineplot.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
|
||||
@@ -444,8 +444,12 @@ SET(FemGuiPythonUI_SRCS
|
||||
Resources/ui/TaskPostGlyph.ui
|
||||
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/PostLineplotFieldViewEdit.ui
|
||||
Resources/ui/PostLineplotFieldAppEdit.ui
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET(FemPythonUi ALL
|
||||
|
||||
@@ -14,6 +14,18 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
|
||||
@@ -14,6 +14,18 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="2">
|
||||
@@ -76,9 +88,6 @@
|
||||
<property name="text">
|
||||
<string>Lines:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
@@ -102,9 +111,6 @@
|
||||
<property name="text">
|
||||
<string>Bars:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
@@ -141,9 +147,6 @@
|
||||
<property name="text">
|
||||
<string>Legend:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -157,6 +160,15 @@
|
||||
<header>Gui/Widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>Legend</tabstop>
|
||||
<tabstop>BarColor</tabstop>
|
||||
<tabstop>Hatch</tabstop>
|
||||
<tabstop>HatchDensity</tabstop>
|
||||
<tabstop>LineColor</tabstop>
|
||||
<tabstop>LineStyle</tabstop>
|
||||
<tabstop>LineWidth</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
101
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui
Normal file
101
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>296</width>
|
||||
<height>186</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>X Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="XField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="XComponent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Y Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="YField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="YComponent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Frames:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="Extract">
|
||||
<property name="text">
|
||||
<string>One Y field for each frames</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
154
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui
Normal file
154
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui
Normal file
@@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PostHistogramEdit</class>
|
||||
<widget class="QWidget" name="PostHistogramEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>335</width>
|
||||
<height>124</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="4">
|
||||
<widget class="QDoubleSpinBox" name="MarkerSize">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Width of all lines (outline and hatch)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>99.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Marker:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="LineStyle">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Hatch pattern</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="4">
|
||||
<widget class="QLineEdit" name="Legend"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Legend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QComboBox" name="MarkerStyle">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Outline draw style (None does not draw outlines)</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Line:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QDoubleSpinBox" name="LineWidth">
|
||||
<property name="maximum">
|
||||
<double>99.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::ColorButton" name="Color">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Color of the bars in histogram</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::ColorButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>Gui/Widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>Legend</tabstop>
|
||||
<tabstop>Color</tabstop>
|
||||
<tabstop>LineStyle</tabstop>
|
||||
<tabstop>LineWidth</tabstop>
|
||||
<tabstop>MarkerStyle</tabstop>
|
||||
<tabstop>MarkerSize</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -14,9 +14,21 @@
|
||||
<string notr="true">Glyph settings</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LayoutDirection::LeftToRight</enum>
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
@@ -38,7 +50,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LayoutDirection::LeftToRight</enum>
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
@@ -94,7 +106,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="LegendShow">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LayoutDirection::LeftToRight</enum>
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show</string>
|
||||
@@ -195,9 +207,6 @@
|
||||
<property name="text">
|
||||
<string>Hatch Line Width</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
@@ -205,9 +214,6 @@
|
||||
<property name="text">
|
||||
<string>Bar width</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
@@ -218,6 +224,18 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Bins</tabstop>
|
||||
<tabstop>Type</tabstop>
|
||||
<tabstop>Cumulative</tabstop>
|
||||
<tabstop>LegendShow</tabstop>
|
||||
<tabstop>LegendPos</tabstop>
|
||||
<tabstop>Title</tabstop>
|
||||
<tabstop>XLabel</tabstop>
|
||||
<tabstop>YLabel</tabstop>
|
||||
<tabstop>BarWidth</tabstop>
|
||||
<tabstop>HatchWidth</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
181
src/Mod/Fem/Gui/Resources/ui/TaskPostLineplot.ui
Normal file
181
src/Mod/Fem/Gui/Resources/ui/TaskPostLineplot.ui
Normal file
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPostGlyph</class>
|
||||
<widget class="QWidget" name="TaskPostGlyph">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>302</width>
|
||||
<height>302</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Glyph settings</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="toolTip">
|
||||
<string>The form of the glyph</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Grid</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="Grid">
|
||||
<property name="text">
|
||||
<string>Show</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="LegendShow">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Legend</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="LegendPos">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="toolTip">
|
||||
<string>Which vector field is used to orient the glyphs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scale</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="Scale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Which vector field is used to orient the glyphs</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="Form">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Labels</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="Title"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Y Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="YLabel"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="toolTip">
|
||||
<string>If the scale data is a vector this property decides if the glyph is scaled by vector magnitude or by the individual components</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>X Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="toolTip">
|
||||
<string>A constant multiplier the glyphs are scaled with</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="XLabel"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Grid</tabstop>
|
||||
<tabstop>LegendShow</tabstop>
|
||||
<tabstop>LegendPos</tabstop>
|
||||
<tabstop>Scale</tabstop>
|
||||
<tabstop>Title</tabstop>
|
||||
<tabstop>XLabel</tabstop>
|
||||
<tabstop>YLabel</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -686,36 +686,34 @@ def makePostVtkResult(doc, result_data, name="VtkResult"):
|
||||
return obj
|
||||
|
||||
|
||||
def makePostVtkLinePlot(doc, name="Lineplot"):
|
||||
"""makePostVtkLineplot(document, [name]):
|
||||
def makePostLineplot(doc, name="Lineplot"):
|
||||
"""makePostLineplot(document, [name]):
|
||||
creates a FEM post processing line plot
|
||||
"""
|
||||
obj = doc.addObject("App::FeaturePython", name)
|
||||
from femobjects import post_lineplot
|
||||
|
||||
post_lineplot.PostLinePlot(obj)
|
||||
post_lineplot.PostLineplot(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_lineplot
|
||||
view_post_lineplot.VPPostLinePlot(obj.ViewObject)
|
||||
return
|
||||
|
||||
|
||||
def makePostVtkHistogramFieldData(doc, name="FieldData1D"):
|
||||
"""makePostVtkFieldData1D(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.PostHistogramFieldData(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_histogram
|
||||
view_post_histogram.VPPostHistogramFieldData(obj.ViewObject)
|
||||
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
|
||||
"""
|
||||
obj = doc.addObject("App::FeaturePython", name)
|
||||
from femobjects import post_lineplot
|
||||
|
||||
def makePostVtkHistogram(doc, name="Histogram"):
|
||||
"""makePostVtkHistogram(document, [name]):
|
||||
post_lineplot.PostLineplotFieldData(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_lineplot
|
||||
view_post_lineplot.VPPostLineplotFieldData(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
def makePostHistogram(doc, name="Histogram"):
|
||||
"""makePostHistogram(document, [name]):
|
||||
creates a FEM post processing histogram plot
|
||||
"""
|
||||
obj = doc.addObject("App::FeaturePython", name)
|
||||
@@ -727,6 +725,19 @@ def makePostVtkHistogram(doc, name="Histogram"):
|
||||
view_post_histogram.VPPostHistogram(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
def makePostHistogramFieldData(doc, name="FieldData1D"):
|
||||
"""makePostHistogramFieldData1D(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.PostHistogramFieldData(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_histogram
|
||||
view_post_histogram.VPPostHistogramFieldData(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
# ********* solver objects ***********************************************************************
|
||||
def makeEquationDeformation(doc, base_solver=None, name="Deformation"):
|
||||
|
||||
@@ -1292,5 +1292,5 @@ if "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__:
|
||||
|
||||
# setup all visualization commands (register by importing)
|
||||
import femobjects.post_histogram
|
||||
import femobjects.post_lineplot
|
||||
post_visualization.setup_commands("FEM_PostVisualization")
|
||||
|
||||
|
||||
@@ -145,7 +145,10 @@ def build_add_from_data_tree_model(vis_type):
|
||||
|
||||
return model
|
||||
|
||||
class TreeChoiceButton(QtGui.QToolButton):
|
||||
# implementation of GUI and its functionality
|
||||
# ###########################################
|
||||
|
||||
class _TreeChoiceButton(QtGui.QToolButton):
|
||||
|
||||
selection = QtCore.Signal(object,object)
|
||||
|
||||
@@ -191,15 +194,157 @@ class TreeChoiceButton(QtGui.QToolButton):
|
||||
# check if we should be disabled
|
||||
self.setEnabled(bool(model.rowCount()))
|
||||
|
||||
class _SettingsPopup(QtGui.QGroupBox):
|
||||
|
||||
# implementationof GUI and its functionality
|
||||
# ##########################################
|
||||
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)
|
||||
|
||||
self.setFocusPolicy(QtGui.Qt.ClickFocus)
|
||||
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.addWidget(setting)
|
||||
|
||||
buttonBox = QtGui.QDialogButtonBox()
|
||||
buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
|
||||
vbox.addWidget(buttonBox)
|
||||
|
||||
buttonBox.accepted.connect(self.accept)
|
||||
self.setLayout(vbox)
|
||||
|
||||
@QtCore.Slot()
|
||||
def accept(self):
|
||||
self.close.emit()
|
||||
|
||||
def showEvent(self, event):
|
||||
self.setFocus()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtGui.Qt.Key_Enter or event.key() == QtGui.Qt.Key_Return:
|
||||
self.accept()
|
||||
|
||||
|
||||
|
||||
class _SummaryWidget(QtGui.QWidget):
|
||||
|
||||
delete = QtCore.Signal(object, object) # to delete: document object, summary widget
|
||||
|
||||
def __init__(self, st_object, extractor, post_dialog):
|
||||
super().__init__()
|
||||
|
||||
class _ShowVisualization:
|
||||
def __init__(self, st_object):
|
||||
self._st_object = st_object
|
||||
self._extractor = extractor
|
||||
self._post_dialog = post_dialog
|
||||
|
||||
def __call__(self):
|
||||
extr_label = extractor.Proxy.get_representive_fieldname(extractor)
|
||||
extr_repr = extractor.ViewObject.Proxy.get_preview()
|
||||
|
||||
# build the UI
|
||||
|
||||
self.stButton = self._button(st_object.Label)
|
||||
self.stButton.setIcon(st_object.ViewObject.Icon)
|
||||
|
||||
self.extrButton = self._button(extr_label)
|
||||
self.extrButton.setIcon(extractor.ViewObject.Icon)
|
||||
|
||||
self.viewButton = self._button(extr_repr[1])
|
||||
size = self.viewButton.iconSize()
|
||||
size.setWidth(size.width()*2)
|
||||
self.viewButton.setIconSize(size)
|
||||
self.viewButton.setIcon(extr_repr[0])
|
||||
|
||||
|
||||
self.rmButton = QtGui.QToolButton(self)
|
||||
self.rmButton.setIcon(QtGui.QIcon.fromTheme("delete"))
|
||||
self.rmButton.setAutoRaise(True)
|
||||
|
||||
# add the separation line
|
||||
self.frame = QtGui.QFrame(self)
|
||||
self.frame.setFrameShape(QtGui.QFrame.HLine);
|
||||
|
||||
policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||
self.setSizePolicy(policy)
|
||||
self.setMinimumSize(self.stButton.sizeHint()+self.frame.sizeHint()*3)
|
||||
|
||||
# connect actions. We add functions to widget, as well as the data we need,
|
||||
# and use those as callback. This way every widget knows which objects to use
|
||||
self.stButton.clicked.connect(self.showVisualization)
|
||||
self.extrButton.clicked.connect(self.editApp)
|
||||
self.viewButton.clicked.connect(self.editView)
|
||||
self.rmButton.clicked.connect(self.deleteTriggered)
|
||||
|
||||
# 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");
|
||||
btn.setToolTip(text)
|
||||
|
||||
return btn
|
||||
|
||||
def _redraw(self):
|
||||
|
||||
btn_total_size = ((self.size() - self.rmButton.size()).width() - 20) #20 is space to rmButton
|
||||
btn_margin = (self.rmButton.size() - self.rmButton.iconSize()).width()
|
||||
fm = self.fontMetrics()
|
||||
min_text_width = fm.size(QtGui.Qt.TextSingleLine, "...").width()*2
|
||||
|
||||
pos = 0
|
||||
btns = [self.stButton, self.extrButton, self.viewButton]
|
||||
btn_rel_size = [0.4, 0.4, 0.2]
|
||||
btn_elide_mode = [QtGui.Qt.ElideMiddle, QtGui.Qt.ElideMiddle, QtGui.Qt.ElideRight]
|
||||
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
|
||||
|
||||
# we elide only if there is enough space for a meaningful text
|
||||
if txt_size >= min_text_width:
|
||||
|
||||
text = fm.elidedText(btn.full_text, btn_elide_mode[i], txt_size)
|
||||
btn.setText(text)
|
||||
btn.setStyleSheet("text-align:left;padding:6px");
|
||||
else:
|
||||
btn.setText("")
|
||||
btn.setStyleSheet("text-align:center;");
|
||||
|
||||
rect = QtCore.QRect(pos,0, btn_size, btn.sizeHint().height())
|
||||
btn.setGeometry(rect)
|
||||
pos+=btn_size
|
||||
|
||||
rmsize = self.stButton.height()
|
||||
pos = self.size().width() - rmsize
|
||||
self.rmButton.setGeometry(pos, 0, rmsize, rmsize)
|
||||
|
||||
frame_hint = self.frame.sizeHint()
|
||||
rect = QtCore.QRect(0, self.stButton.height()+frame_hint.height(), self.size().width(), frame_hint.height())
|
||||
self.frame.setGeometry(rect)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
|
||||
# calculate the allowed text length
|
||||
self._redraw()
|
||||
super().resizeEvent(event)
|
||||
|
||||
@QtCore.Slot()
|
||||
def showVisualization(self):
|
||||
if vis.is_visualization_object(self._st_object):
|
||||
# show the visualization
|
||||
self._st_object.ViewObject.Proxy.show_visualization()
|
||||
@@ -208,67 +353,76 @@ class _ShowVisualization:
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCADGui.Selection.addSelection(self._st_object)
|
||||
|
||||
class _ShowEditDialog:
|
||||
def __init__(self, extractor, post_dialog, widget):
|
||||
self._extractor = extractor
|
||||
self._post_dialog = post_dialog
|
||||
self._widget = widget
|
||||
def _position_dialog(self, dialog):
|
||||
|
||||
widgets = self._extractor.ViewObject.Proxy.get_edit_widgets(self._post_dialog)
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
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))
|
||||
|
||||
buttonBox = QtGui.QDialogButtonBox()
|
||||
buttonBox.setCenterButtons(True)
|
||||
buttonBox.setStandardButtons(self._post_dialog.getStandardButtons())
|
||||
vbox.addWidget(buttonBox)
|
||||
@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.close.connect(self.appAccept)
|
||||
|
||||
started = False
|
||||
for widget in widgets:
|
||||
if not self.appDialog.isVisible():
|
||||
# position correctly and show
|
||||
self._position_dialog(self.appDialog)
|
||||
self.appDialog.show()
|
||||
#self.appDialog.raise_()
|
||||
|
||||
if started:
|
||||
# add a seperator line
|
||||
frame = QtGui.QFrame()
|
||||
frame.setFrameShape(QtGui.QFrame.HLine);
|
||||
vbox.addWidget(frame);
|
||||
else:
|
||||
started = True
|
||||
@QtCore.Slot()
|
||||
def editView(self):
|
||||
|
||||
vbox.addWidget(widget)
|
||||
if not hasattr(self, "viewDialog"):
|
||||
widget = self._extractor.ViewObject.Proxy.get_view_edit_widget(self._post_dialog)
|
||||
self.viewDialog = _SettingsPopup(widget)
|
||||
self.viewDialog.close.connect(self.viewAccept)
|
||||
|
||||
vbox.addStretch()
|
||||
if not self.viewDialog.isVisible():
|
||||
# position correctly and show
|
||||
self._position_dialog(self.viewDialog)
|
||||
self.viewDialog.show()
|
||||
#self.viewDialog.raise_()
|
||||
|
||||
self.dialog = QtGui.QDialog(self._widget)
|
||||
buttonBox.accepted.connect(self.accept)
|
||||
buttonBox.rejected.connect(self.dialog.close)
|
||||
buttonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self.apply)
|
||||
self.dialog.setLayout(vbox)
|
||||
@QtCore.Slot()
|
||||
def deleteTriggered(self):
|
||||
self.delete.emit(self._extractor, self)
|
||||
|
||||
@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])
|
||||
self.viewButton.full_text = extr_repr[1]
|
||||
self.viewButton.setToolTip(extr_repr[1])
|
||||
self._redraw()
|
||||
|
||||
@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
|
||||
self.extrButton.setToolTip(extr_label)
|
||||
self._redraw()
|
||||
|
||||
|
||||
def accept(self):
|
||||
# recompute and close
|
||||
self._extractor.Document.recompute()
|
||||
self.dialog.close()
|
||||
|
||||
def apply(self):
|
||||
self._extractor.Document.recompute()
|
||||
|
||||
def __call__(self):
|
||||
# create the widgets, add it to dialog
|
||||
self.dialog.show()
|
||||
|
||||
class _DeleteExtractor:
|
||||
def __init__(self, extractor, widget):
|
||||
self._extractor = extractor
|
||||
self._widget = widget
|
||||
|
||||
def __call__(self):
|
||||
# remove the document object
|
||||
doc = self._extractor.Document
|
||||
doc.removeObject(self._extractor.Name)
|
||||
doc.recompute()
|
||||
|
||||
# remove the widget
|
||||
self._widget.deleteLater()
|
||||
|
||||
class ExtractLinkView(QtGui.QWidget):
|
||||
|
||||
@@ -300,19 +454,19 @@ class ExtractLinkView(QtGui.QWidget):
|
||||
|
||||
if self._is_source:
|
||||
|
||||
self._add = TreeChoiceButton(build_add_to_visualization_tree_model())
|
||||
self._add = _TreeChoiceButton(build_add_to_visualization_tree_model())
|
||||
self._add.setText("Add data to")
|
||||
self._add.selection.connect(self.addExtractionToVisualization)
|
||||
hbox.addWidget(self._add)
|
||||
|
||||
self._create = TreeChoiceButton(build_new_visualization_tree_model())
|
||||
self._create = _TreeChoiceButton(build_new_visualization_tree_model())
|
||||
self._create.setText("New")
|
||||
self._create.selection.connect(self.newVisualization)
|
||||
hbox.addWidget(self._create)
|
||||
|
||||
else:
|
||||
vis_type = vis.get_visualization_type(self._object)
|
||||
self._add = TreeChoiceButton(build_add_from_data_tree_model(vis_type))
|
||||
self._add = _TreeChoiceButton(build_add_from_data_tree_model(vis_type))
|
||||
self._add.setText("Add data from")
|
||||
self._add.selection.connect(self.addExtractionToPostObject)
|
||||
hbox.addWidget(self._add)
|
||||
@@ -324,46 +478,31 @@ class ExtractLinkView(QtGui.QWidget):
|
||||
|
||||
self.setLayout(vbox)
|
||||
|
||||
|
||||
|
||||
# add the content
|
||||
self.repopulate()
|
||||
|
||||
def _build_summary_widget(self, extractor):
|
||||
|
||||
widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostExtractionSummaryWidget.ui"
|
||||
)
|
||||
|
||||
# add the separation line
|
||||
frame = QtGui.QFrame()
|
||||
frame.setFrameShape(QtGui.QFrame.HLine);
|
||||
widget.layout().addWidget(frame);
|
||||
|
||||
if self._is_source:
|
||||
st_object = extractor.getParentGroup()
|
||||
else:
|
||||
st_object = extractor.Source
|
||||
|
||||
widget.RemoveButton.setIcon(QtGui.QIcon.fromTheme("delete"))
|
||||
|
||||
widget.STButton.setIcon(st_object.ViewObject.Icon)
|
||||
widget.STButton.setText(st_object.Label)
|
||||
|
||||
widget.ExtractButton.setIcon(extractor.ViewObject.Icon)
|
||||
|
||||
extr_label = extr.get_extraction_dimension(extractor)
|
||||
extr_label += " " + extr.get_extraction_type(extractor)
|
||||
widget.ExtractButton.setText(extr_label)
|
||||
|
||||
# connect actions. We add functions to widget, as well as the data we need,
|
||||
# and use those as callback. This way every widget knows which objects to use
|
||||
widget.STButton.clicked.connect(_ShowVisualization(st_object))
|
||||
widget.ExtractButton.clicked.connect(_ShowEditDialog(extractor, self._post_dialog, widget))
|
||||
widget.RemoveButton.clicked.connect(_DeleteExtractor(extractor, widget))
|
||||
widget = _SummaryWidget(st_object, extractor, self._post_dialog)
|
||||
widget.delete.connect(self._delete_extraction)
|
||||
|
||||
return widget
|
||||
|
||||
def _delete_extraction(self, extractor, widget):
|
||||
# remove the document object
|
||||
doc = extractor.Document
|
||||
doc.removeObject(extractor.Name)
|
||||
doc.recompute()
|
||||
|
||||
# remove the widget
|
||||
self._widgets.remove(widget)
|
||||
widget.deleteLater()
|
||||
|
||||
def repopulate(self):
|
||||
# collect all links that are available and shows them
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ __url__ = "https://www.freecad.org"
|
||||
# \ingroup FEM
|
||||
# \brief base objects for data extractors
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkIntArray
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
from . import base_fempythonobject
|
||||
@@ -117,6 +119,10 @@ class Extractor(base_fempythonobject.BaseFemPythonObject):
|
||||
case _:
|
||||
return ["Not a vector"]
|
||||
|
||||
def get_representive_fieldname(self):
|
||||
# should return the representive field name, e.g. Position (X)
|
||||
return ""
|
||||
|
||||
|
||||
class Extractor1D(Extractor):
|
||||
|
||||
@@ -196,4 +202,179 @@ class Extractor1D(Extractor):
|
||||
|
||||
self._setup_x_component_property(obj, point_data)
|
||||
|
||||
def _x_array_component_to_table(self, obj, array, table):
|
||||
# extracts the component out of the array according to XComponent setting
|
||||
# Note: Uses the array name unchanged
|
||||
|
||||
if array.GetNumberOfComponents() == 1:
|
||||
table.AddColumn(array)
|
||||
else:
|
||||
component_array = vtkDoubleArray();
|
||||
component_array.SetNumberOfComponents(1)
|
||||
component_array.SetNumberOfTuples(array.GetNumberOfTuples())
|
||||
c_idx = obj.getEnumerationsOfProperty("XComponent").index(obj.XComponent)
|
||||
component_array.CopyComponent(0, array, c_idx)
|
||||
component_array.SetName(array.GetName())
|
||||
table.AddColumn(component_array)
|
||||
|
||||
def _x_array_from_dataset(self, obj, dataset):
|
||||
# extracts the relevant array from the dataset and returns a copy
|
||||
|
||||
match obj.XField:
|
||||
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)
|
||||
|
||||
case _:
|
||||
point_data = dataset.GetPointData()
|
||||
orig_array = point_data.GetAbstractArray(obj.XField)
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
|
||||
return array
|
||||
|
||||
def get_representive_fieldname(self, obj):
|
||||
# representive field is the x field
|
||||
label = obj.XField
|
||||
if not label:
|
||||
return ""
|
||||
|
||||
if len(obj.getEnumerationsOfProperty("XComponent")) > 1:
|
||||
label += f" ({obj.XComponent})"
|
||||
|
||||
return label
|
||||
|
||||
class Extractor2D(Extractor1D):
|
||||
|
||||
ExtractionDimension = "2D"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
|
||||
def _get_properties(self):
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YField",
|
||||
group="Y Data",
|
||||
doc="The field to use as Y data",
|
||||
value=[],
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YComponent",
|
||||
group="Y Data",
|
||||
doc="Which part of the Y field vector to use for the Y axis",
|
||||
value=[],
|
||||
),
|
||||
]
|
||||
|
||||
return super()._get_properties() + prop
|
||||
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
super().onChanged(obj, prop)
|
||||
|
||||
if prop == "YField" and obj.Source and obj.Source.getDataSet():
|
||||
point_data = obj.Source.getDataSet().GetPointData()
|
||||
self._setup_y_component_property(obj, point_data)
|
||||
|
||||
if prop == "Source":
|
||||
if obj.Source:
|
||||
dset = obj.Source.getDataSet()
|
||||
if dset:
|
||||
self._setup_y_properties(obj, dset)
|
||||
else:
|
||||
self._clear_y_properties(obj)
|
||||
else:
|
||||
self._clear_y_properties(obj)
|
||||
|
||||
def _setup_y_component_property(self, obj, point_data):
|
||||
|
||||
if obj.YField == "Position":
|
||||
obj.YComponent = self.component_options(3)
|
||||
else:
|
||||
array = point_data.GetAbstractArray(obj.YField)
|
||||
obj.YComponent = self.component_options(array.GetNumberOfComponents())
|
||||
|
||||
def _clear_y_properties(self, obj):
|
||||
if hasattr(obj, "YComponent"):
|
||||
obj.YComponent = []
|
||||
if hasattr(obj, "YField"):
|
||||
obj.YField = []
|
||||
|
||||
def _setup_y_properties(self, obj, dataset):
|
||||
# Set all X Data properties correctly for the given dataset
|
||||
fields = ["Position"]
|
||||
point_data = dataset.GetPointData()
|
||||
|
||||
for i in range(point_data.GetNumberOfArrays()):
|
||||
fields.append(point_data.GetArrayName(i))
|
||||
|
||||
current_field = obj.YField
|
||||
obj.YField = fields
|
||||
if current_field in fields:
|
||||
obj.YField = current_field
|
||||
|
||||
self._setup_y_component_property(obj, point_data)
|
||||
|
||||
def _y_array_component_to_table(self, obj, array, table):
|
||||
# extracts the component out of the array according to XComponent setting
|
||||
|
||||
if array.GetNumberOfComponents() == 1:
|
||||
table.AddColumn(array)
|
||||
else:
|
||||
component_array = vtkDoubleArray();
|
||||
component_array.SetNumberOfComponents(1)
|
||||
component_array.SetNumberOfTuples(array.GetNumberOfTuples())
|
||||
c_idx = obj.getEnumerationsOfProperty("YComponent").index(obj.YComponent)
|
||||
component_array.CopyComponent(0, array, c_idx)
|
||||
component_array.SetName(array.GetName())
|
||||
table.AddColumn(component_array)
|
||||
|
||||
def _y_array_from_dataset(self, obj, dataset):
|
||||
# extracts the relevant array from the dataset and returns a copy
|
||||
|
||||
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)
|
||||
|
||||
case _:
|
||||
point_data = dataset.GetPointData()
|
||||
orig_array = point_data.GetAbstractArray(obj.YField)
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
|
||||
return array
|
||||
|
||||
def get_representive_fieldname(self, obj):
|
||||
# representive field is the y field
|
||||
label = obj.YField
|
||||
if not label:
|
||||
return ""
|
||||
|
||||
if len(obj.getEnumerationsOfProperty("YComponent")) > 1:
|
||||
label += f" ({obj.YComponent})"
|
||||
|
||||
return label
|
||||
|
||||
@@ -33,10 +33,7 @@ from . import base_fempostextractors
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonCore import vtkIntArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
from vtkmodules.vtkCommonDataModel import vtkDataObject
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline
|
||||
|
||||
class PostFieldData1D(base_fempostextractors.Extractor1D):
|
||||
@@ -60,44 +57,6 @@ class PostFieldData1D(base_fempostextractors.Extractor1D):
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def __array_to_table(self, obj, array, table):
|
||||
if array.GetNumberOfComponents() == 1:
|
||||
table.AddColumn(array)
|
||||
else:
|
||||
component_array = vtkDoubleArray();
|
||||
component_array.SetNumberOfComponents(1)
|
||||
component_array.SetNumberOfTuples(array.GetNumberOfTuples())
|
||||
c_idx = obj.getEnumerationsOfProperty("XComponent").index(obj.XComponent)
|
||||
component_array.CopyComponent(0, array, c_idx)
|
||||
component_array.SetName(array.GetName())
|
||||
table.AddColumn(component_array)
|
||||
|
||||
def __array_from_dataset(self, obj, dataset):
|
||||
# extracts the relevant array from the dataset and returns a copy
|
||||
|
||||
match obj.XField:
|
||||
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)
|
||||
|
||||
case _:
|
||||
point_data = dataset.GetPointData()
|
||||
orig_array = point_data.GetAbstractArray(obj.XField)
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
|
||||
return array
|
||||
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
# on execution we populate the vtk table
|
||||
@@ -124,26 +83,26 @@ class PostFieldData1D(base_fempostextractors.Extractor1D):
|
||||
|
||||
if not frames:
|
||||
# get the dataset and extract the correct array
|
||||
array = self.__array_from_dataset(obj, dataset)
|
||||
array = self._x_array_from_dataset(obj, dataset)
|
||||
if array.GetNumberOfComponents() > 1:
|
||||
array.SetName(obj.XField + " (" + obj.XComponent + ")")
|
||||
else:
|
||||
array.SetName(obj.XField)
|
||||
|
||||
self.__array_to_table(obj, array, table)
|
||||
self._x_array_component_to_table(obj, array, table)
|
||||
|
||||
else:
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
for timestep in timesteps:
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self.__array_from_dataset(obj, dataset)
|
||||
array = self._x_array_from_dataset(obj, dataset)
|
||||
|
||||
if array.GetNumberOfComponents() > 1:
|
||||
array.SetName(f"{obj.XField} ({obj.XComponent}) - {timestep}")
|
||||
else:
|
||||
array.SetName(f"{obj.XField} - {timestep}")
|
||||
self.__array_to_table(obj, array, table)
|
||||
self._x_array_component_to_table(obj, array, table)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
|
||||
153
src/Mod/Fem/femobjects/post_extract2D.py
Normal file
153
src/Mod/Fem/femobjects/post_extract2D.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * 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 Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD post extractors 2D"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_histogram
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline
|
||||
|
||||
class PostFieldData2D(base_fempostextractors.Extractor2D):
|
||||
"""
|
||||
A post processing extraction of two dimensional field data
|
||||
"""
|
||||
|
||||
ExtractionType = "Field"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
prop =[ _PropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="ExtractFrames",
|
||||
group="Multiframe",
|
||||
doc="Specify if the field shall be extracted for every available frame",
|
||||
value=False,
|
||||
),
|
||||
]
|
||||
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
|
||||
|
||||
frames = False
|
||||
if obj.ExtractFrames:
|
||||
# check if we have timesteps
|
||||
info = obj.Source.getOutputAlgorithm().GetOutputInformation(0)
|
||||
if info.Has(vtkStreamingDemandDrivenPipeline.TIME_STEPS()):
|
||||
timesteps = info.Get(vtkStreamingDemandDrivenPipeline.TIME_STEPS())
|
||||
frames = True
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning("No frames available in data, ignoring \"ExtractFrames\" property")
|
||||
|
||||
if not frames:
|
||||
# get the dataset and extract the correct array
|
||||
xarray = self._x_array_from_dataset(obj, dataset)
|
||||
if xarray.GetNumberOfComponents() > 1:
|
||||
xarray.SetName(obj.XField + " (" + obj.XComponent + ")")
|
||||
else:
|
||||
xarray.SetName(obj.XField)
|
||||
|
||||
self._x_array_component_to_table(obj, xarray, table)
|
||||
|
||||
yarray = self._y_array_from_dataset(obj, dataset)
|
||||
if yarray.GetNumberOfComponents() > 1:
|
||||
yarray.SetName(obj.YField + " (" + obj.YComponent + ")")
|
||||
else:
|
||||
yarray.SetName(obj.YField)
|
||||
|
||||
self._y_array_component_to_table(obj, yarray, table)
|
||||
|
||||
else:
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
for timestep in timesteps:
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
|
||||
xarray = self._x_array_from_dataset(obj, dataset)
|
||||
if xarray.GetNumberOfComponents() > 1:
|
||||
xarray.SetName(f"X - {obj.XField} ({obj.XComponent}) - {timestep}")
|
||||
else:
|
||||
xarray.SetName(f"X - {obj.XField} - {timestep}")
|
||||
self._x_array_component_to_table(obj, xarray, table)
|
||||
|
||||
yarray = self._y_array_from_dataset(obj, dataset)
|
||||
if yarray.GetNumberOfComponents() > 1:
|
||||
yarray.SetName(f"{obj.YField} ({obj.YComponent}) - {timestep}")
|
||||
else:
|
||||
yarray.SetName(f"{obj.YField} - {timestep}")
|
||||
self._y_array_component_to_table(obj, yarray, table)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
|
||||
|
||||
class PostIndexData2D(base_fempostextractors.Extractor2D):
|
||||
"""
|
||||
A post processing extraction of one dimensional index data
|
||||
"""
|
||||
|
||||
ExtractionType = "Index"
|
||||
|
||||
def __init__(self, obj):
|
||||
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(
|
||||
type="App::PropertyInteger",
|
||||
name="XIndex",
|
||||
group="X Data",
|
||||
doc="Specify for which point index the data should be extracted",
|
||||
value=0,
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
@@ -21,13 +21,13 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD post line plot"
|
||||
__title__ = "FreeCAD post histogram"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_histogram
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
# \brief Post processing plot displaying histograms
|
||||
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
@@ -46,7 +46,7 @@ from femguiutils import post_visualization
|
||||
post_visualization.register_visualization("Histogram",
|
||||
":/icons/FEM_PostHistogram.svg",
|
||||
"ObjectsFem",
|
||||
"makePostVtkHistogram")
|
||||
"makePostHistogram")
|
||||
|
||||
post_visualization.register_extractor("Histogram",
|
||||
"HistogramFieldData",
|
||||
@@ -54,7 +54,7 @@ post_visualization.register_extractor("Histogram",
|
||||
"1D",
|
||||
"Field",
|
||||
"ObjectsFem",
|
||||
"makePostVtkHistogramFieldData")
|
||||
"makePostHistogramFieldData")
|
||||
|
||||
|
||||
# Implementation
|
||||
|
||||
@@ -32,41 +32,76 @@ __url__ = "https://www.freecad.org"
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
# helper function to extract plot object type
|
||||
def _get_extraction_subtype(obj):
|
||||
if hasattr(obj, 'Proxy') and hasattr(obj.Proxy, "Type"):
|
||||
return obj.Proxy.Type
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract2D
|
||||
|
||||
return "unknown"
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
|
||||
class PostLinePlot(base_fempythonobject.BaseFemPythonObject):
|
||||
from femguiutils import post_visualization
|
||||
|
||||
# register visualization and extractors
|
||||
post_visualization.register_visualization("Lineplot",
|
||||
":/icons/FEM_PostLineplot.svg",
|
||||
"ObjectsFem",
|
||||
"makePostLineplot")
|
||||
|
||||
post_visualization.register_extractor("Lineplot",
|
||||
"LineplotFieldData",
|
||||
":/icons/FEM_PostField.svg",
|
||||
"2D",
|
||||
"Field",
|
||||
"ObjectsFem",
|
||||
"makePostLineplotFieldData")
|
||||
|
||||
|
||||
# Implementation
|
||||
# ##############
|
||||
|
||||
def is_lineplot_extractor(obj):
|
||||
|
||||
if not base_fempostextractors.is_extractor_object(obj):
|
||||
return False
|
||||
|
||||
if not hasattr(obj.Proxy, "VisualizationType"):
|
||||
return False
|
||||
|
||||
return obj.Proxy.VisualizationType == "Lineplot"
|
||||
|
||||
|
||||
class PostLineplotFieldData(post_extract2D.PostFieldData2D):
|
||||
"""
|
||||
A post processing extraction for plotting lines
|
||||
A 2D Field extraction for lineplot.
|
||||
"""
|
||||
VisualizationType = "Lineplot"
|
||||
|
||||
|
||||
|
||||
class PostLineplot(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as line plots
|
||||
"""
|
||||
|
||||
Type = "App::FeaturePython"
|
||||
VisualizationType = "Lineplot"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
obj.addExtension("App::GroupExtension")
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
|
||||
self.ExtractionType = "LinePlot"
|
||||
|
||||
pl = obj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
if not prop.Name in pl:
|
||||
prop.add_to_object(obj)
|
||||
obj.addExtension("App::GroupExtensionPython")
|
||||
|
||||
def _get_properties(self):
|
||||
prop = []
|
||||
return prop
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="Fem::PropertyPostDataObject",
|
||||
name="Table",
|
||||
group="Base",
|
||||
doc="The data table that stores the plotted data, two columns per lineplot (x,y)",
|
||||
value=vtkTable(),
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self._setup_properties(self, obj):
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
@@ -75,137 +110,29 @@ class PostLinePlot(base_fempythonobject.BaseFemPythonObject):
|
||||
|
||||
children = obj.Group
|
||||
for child in obj.Group:
|
||||
if _get_extraction_subtype(child) not in ["Line"]:
|
||||
if not is_lineplot_extractor(child):
|
||||
FreeCAD.Console.PrintWarning(f"{child.Label} is not a data lineplot data extraction object, cannot be added")
|
||||
children.remove(child)
|
||||
|
||||
if len(obj.Group) != len(children):
|
||||
obj.Group = children
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
class PostPlotLine(base_fempythonobject.BaseFemPythonObject):
|
||||
# during execution we collect all child data into our table
|
||||
table = vtkTable()
|
||||
for child in obj.Group:
|
||||
|
||||
Type = "App::FeaturePython"
|
||||
c_table = child.Table
|
||||
for i in range(c_table.GetNumberOfColumns()):
|
||||
c_array = c_table.GetColumn(i)
|
||||
# TODO: check which array type it is and use that one
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(c_array)
|
||||
array.SetName(f"{child.Source.Label}: {c_array.GetName()}")
|
||||
table.AddColumn(array)
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._setup_properties(obj)
|
||||
obj.Table = table
|
||||
return False
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
|
||||
self.ExtractionType = "Line"
|
||||
|
||||
pl = obj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
if not prop.Name in pl:
|
||||
prop.add_to_object(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyLink",
|
||||
name="Source",
|
||||
group="Line",
|
||||
doc="The data source, the line uses",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XField",
|
||||
group="X Data",
|
||||
doc="The field to use as X data",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XComponent",
|
||||
group="X Data",
|
||||
doc="Which part of the X field vector to use for the X axis",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YField",
|
||||
group="Y Data",
|
||||
doc="The field to use as Y data for the line plot",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YComponent",
|
||||
group="Y Data",
|
||||
doc="Which part of the Y field vector to use for the X axis",
|
||||
value=None,
|
||||
),
|
||||
]
|
||||
return prop
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self._setup_properties(self, obj):
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
if prop == "Source":
|
||||
# check if the source is a Post object
|
||||
if obj.Source and not obj.Source.isDerivedFrom("Fem::FemPostObject"):
|
||||
FreeCAD.Console.PrintWarning("Invalid object: Line source must be FemPostObject")
|
||||
obj.XField = []
|
||||
obj.YField = []
|
||||
obj.Source = None
|
||||
|
||||
if prop == "XField":
|
||||
if not obj.Source:
|
||||
obj.XComponent = []
|
||||
return
|
||||
|
||||
point_data = obj.Source.Data.GetPointData()
|
||||
if not point_data.HasArray(obj.XField):
|
||||
obj.XComponent = []
|
||||
return
|
||||
|
||||
match point_data.GetArray(fields.index(obj.XField)).GetNumberOfComponents:
|
||||
case 1:
|
||||
obj.XComponent = ["Not a vector"]
|
||||
case 2:
|
||||
obj.XComponent = ["Magnitude", "X", "Y"]
|
||||
case 3:
|
||||
obj.XComponent = ["Magnitude", "X", "Y", "Z"]
|
||||
|
||||
if prop == "YField":
|
||||
if not obj.Source:
|
||||
obj.YComponent = []
|
||||
return
|
||||
|
||||
point_data = obj.Source.Data.GetPointData()
|
||||
if not point_data.HasArray(obj.YField):
|
||||
obj.YComponent = []
|
||||
return
|
||||
|
||||
match point_data.GetArray(fields.index(obj.YField)).GetNumberOfComponents:
|
||||
case 1:
|
||||
obj.YComponent = ["Not a vector"]
|
||||
case 2:
|
||||
obj.YComponent = ["Magnitude", "X", "Y"]
|
||||
case 3:
|
||||
obj.YComponent = ["Magnitude", "X", "Y", "Z"]
|
||||
|
||||
def onExecute(self, obj):
|
||||
# we need to make sure that we show the correct fields to the user as option for data extraction
|
||||
|
||||
fields = []
|
||||
if obj.Source:
|
||||
point_data = obj.Source.Data.GetPointData()
|
||||
fields = [point_data.GetArrayName(i) for i in range(point_data.GetNumberOfArrays())]
|
||||
|
||||
current_X = obj.XField
|
||||
obj.XField = fields
|
||||
if current_X in fields:
|
||||
obj.XField = current_X
|
||||
|
||||
current_Y = obj.YField
|
||||
obj.YField = fields
|
||||
if current_Y in fields:
|
||||
obj.YField = current_Y
|
||||
|
||||
return True
|
||||
|
||||
|
||||
211
src/Mod/Fem/femobjects/post_table.py
Normal file
211
src/Mod/Fem/femobjects/post_table.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * 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 Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD post line plot"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_lineplot
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
# helper function to extract plot object type
|
||||
def _get_extraction_subtype(obj):
|
||||
if hasattr(obj, 'Proxy') and hasattr(obj.Proxy, "Type"):
|
||||
return obj.Proxy.Type
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
class PostLinePlot(base_fempythonobject.BaseFemPythonObject):
|
||||
"""
|
||||
A post processing extraction for plotting lines
|
||||
"""
|
||||
|
||||
Type = "App::FeaturePython"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
obj.addExtension("App::GroupExtension")
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
|
||||
self.ExtractionType = "LinePlot"
|
||||
|
||||
pl = obj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
if not prop.Name in pl:
|
||||
prop.add_to_object(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
prop = []
|
||||
return prop
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self._setup_properties(self, obj):
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
if prop == "Group":
|
||||
# check if all objects are allowed
|
||||
|
||||
children = obj.Group
|
||||
for child in obj.Group:
|
||||
if _get_extraction_subtype(child) not in ["Line"]:
|
||||
children.remove(child)
|
||||
|
||||
if len(obj.Group) != len(children):
|
||||
obj.Group = children
|
||||
|
||||
|
||||
class PostPlotLine(base_fempythonobject.BaseFemPythonObject):
|
||||
|
||||
Type = "App::FeaturePython"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
|
||||
self.ExtractionType = "Line"
|
||||
|
||||
pl = obj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
if not prop.Name in pl:
|
||||
prop.add_to_object(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyLink",
|
||||
name="Source",
|
||||
group="Line",
|
||||
doc="The data source, the line uses",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XField",
|
||||
group="X Data",
|
||||
doc="The field to use as X data",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XComponent",
|
||||
group="X Data",
|
||||
doc="Which part of the X field vector to use for the X axis",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YField",
|
||||
group="Y Data",
|
||||
doc="The field to use as Y data for the line plot",
|
||||
value=None,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YComponent",
|
||||
group="Y Data",
|
||||
doc="Which part of the Y field vector to use for the X axis",
|
||||
value=None,
|
||||
),
|
||||
]
|
||||
return prop
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self._setup_properties(self, obj):
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
if prop == "Source":
|
||||
# check if the source is a Post object
|
||||
if obj.Source and not obj.Source.isDerivedFrom("Fem::FemPostObject"):
|
||||
FreeCAD.Console.PrintWarning("Invalid object: Line source must be FemPostObject")
|
||||
obj.XField = []
|
||||
obj.YField = []
|
||||
obj.Source = None
|
||||
|
||||
if prop == "XField":
|
||||
if not obj.Source:
|
||||
obj.XComponent = []
|
||||
return
|
||||
|
||||
point_data = obj.Source.Data.GetPointData()
|
||||
if not point_data.HasArray(obj.XField):
|
||||
obj.XComponent = []
|
||||
return
|
||||
|
||||
match point_data.GetArray(fields.index(obj.XField)).GetNumberOfComponents:
|
||||
case 1:
|
||||
obj.XComponent = ["Not a vector"]
|
||||
case 2:
|
||||
obj.XComponent = ["Magnitude", "X", "Y"]
|
||||
case 3:
|
||||
obj.XComponent = ["Magnitude", "X", "Y", "Z"]
|
||||
|
||||
if prop == "YField":
|
||||
if not obj.Source:
|
||||
obj.YComponent = []
|
||||
return
|
||||
|
||||
point_data = obj.Source.Data.GetPointData()
|
||||
if not point_data.HasArray(obj.YField):
|
||||
obj.YComponent = []
|
||||
return
|
||||
|
||||
match point_data.GetArray(fields.index(obj.YField)).GetNumberOfComponents:
|
||||
case 1:
|
||||
obj.YComponent = ["Not a vector"]
|
||||
case 2:
|
||||
obj.YComponent = ["Magnitude", "X", "Y"]
|
||||
case 3:
|
||||
obj.YComponent = ["Magnitude", "X", "Y", "Z"]
|
||||
|
||||
def onExecute(self, obj):
|
||||
# we need to make sure that we show the correct fields to the user as option for data extraction
|
||||
|
||||
fields = []
|
||||
if obj.Source:
|
||||
point_data = obj.Source.Data.GetPointData()
|
||||
fields = [point_data.GetArrayName(i) for i in range(point_data.GetNumberOfArrays())]
|
||||
|
||||
current_X = obj.XField
|
||||
obj.XField = fields
|
||||
if current_X in fields:
|
||||
obj.XField = current_X
|
||||
|
||||
current_Y = obj.YField
|
||||
obj.YField = fields
|
||||
if current_Y in fields:
|
||||
obj.YField = current_Y
|
||||
|
||||
return True
|
||||
|
||||
@@ -48,7 +48,14 @@ class _ExtractorTaskPanel(base_fempostpanel._BasePostTaskPanel):
|
||||
super().__init__(obj)
|
||||
|
||||
# form is used to display individual task panels
|
||||
self.form = obj.ViewObject.Proxy.get_edit_widgets(self)
|
||||
app = obj.ViewObject.Proxy.get_app_edit_widget(self)
|
||||
app.setWindowTitle("Data extraction")
|
||||
app.setWindowIcon(obj.ViewObject.Icon)
|
||||
view = obj.ViewObject.Proxy.get_view_edit_widget(self)
|
||||
view.setWindowTitle("Visualization settings")
|
||||
view.setWindowIcon(obj.ViewObject.Icon)
|
||||
|
||||
self.form = [app, view]
|
||||
|
||||
|
||||
|
||||
|
||||
163
src/Mod/Fem/femtaskpanels/task_post_lineplot.py
Normal file
163
src/Mod/Fem/femtaskpanels/task_post_lineplot.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * 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 Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM lineplot plot task panel"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package task_post_lineplot
|
||||
# \ingroup FEM
|
||||
# \brief task panel for post lineplot plot
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from . import base_fempostpanel
|
||||
from femguiutils import extract_link_view as elv
|
||||
from femguiutils import vtk_table_view
|
||||
|
||||
class _TaskPanel(base_fempostpanel._BasePostTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing properties of glyph filter
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj.Object)
|
||||
|
||||
# data widget
|
||||
self.data_widget = QtGui.QWidget()
|
||||
hbox = QtGui.QHBoxLayout()
|
||||
self.data_widget.show_plot = QtGui.QPushButton()
|
||||
self.data_widget.show_plot.setText("Show plot")
|
||||
hbox.addWidget(self.data_widget.show_plot)
|
||||
self.data_widget.show_table = QtGui.QPushButton()
|
||||
self.data_widget.show_table.setText("Show data")
|
||||
hbox.addWidget(self.data_widget.show_table)
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.addItem(hbox)
|
||||
vbox.addSpacing(10)
|
||||
|
||||
extracts = elv.ExtractLinkView(self.obj, False, self)
|
||||
vbox.addWidget(extracts)
|
||||
|
||||
self.data_widget.setLayout(vbox)
|
||||
self.data_widget.setWindowTitle("Lineplot data")
|
||||
|
||||
|
||||
# lineplot parameter widget
|
||||
self.view_widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostLineplot.ui"
|
||||
)
|
||||
self.view_widget.setWindowTitle("Lineplot view settings")
|
||||
self.__init_widgets()
|
||||
|
||||
# form made from param and selection widget
|
||||
self.form = [self.data_widget, self.view_widget]
|
||||
|
||||
|
||||
# Setup functions
|
||||
# ###############
|
||||
|
||||
def __init_widgets(self):
|
||||
|
||||
# connect data widget
|
||||
self.data_widget.show_plot.clicked.connect(self.showPlot)
|
||||
self.data_widget.show_table.clicked.connect(self.showTable)
|
||||
|
||||
# set current values to view widget
|
||||
viewObj = self.obj.ViewObject
|
||||
|
||||
self._enumPropertyToCombobox(viewObj, "Scale", self.view_widget.Scale)
|
||||
self.view_widget.Grid.setChecked(viewObj.Grid)
|
||||
|
||||
self.view_widget.Title.setText(viewObj.Title)
|
||||
self.view_widget.XLabel.setText(viewObj.XLabel)
|
||||
self.view_widget.YLabel.setText(viewObj.YLabel)
|
||||
|
||||
self.view_widget.LegendShow.setChecked(viewObj.Legend)
|
||||
self._enumPropertyToCombobox(viewObj, "LegendLocation", self.view_widget.LegendPos)
|
||||
|
||||
|
||||
# connect callbacks
|
||||
self.view_widget.Scale.activated.connect(self.scaleChanged)
|
||||
self.view_widget.Grid.toggled.connect(self.gridChanged)
|
||||
|
||||
self.view_widget.Title.editingFinished.connect(self.titleChanged)
|
||||
self.view_widget.XLabel.editingFinished.connect(self.xLabelChanged)
|
||||
self.view_widget.YLabel.editingFinished.connect(self.yLabelChanged)
|
||||
|
||||
self.view_widget.LegendShow.toggled.connect(self.legendShowChanged)
|
||||
self.view_widget.LegendPos.activated.connect(self.legendPosChanged)
|
||||
|
||||
|
||||
QtCore.Slot()
|
||||
def showPlot(self):
|
||||
self.obj.ViewObject.Proxy.show_visualization()
|
||||
|
||||
QtCore.Slot()
|
||||
def showTable(self):
|
||||
|
||||
# TODO: make data model update when object is recomputed
|
||||
data_model = vtk_table_view.VtkTableModel()
|
||||
data_model.setTable(self.obj.Table)
|
||||
|
||||
dialog = QtGui.QDialog(self.data_widget)
|
||||
widget = vtk_table_view.VtkTableView(data_model)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(widget)
|
||||
layout.setContentsMargins(0,0,0,0)
|
||||
|
||||
dialog.setLayout(layout)
|
||||
dialog.resize(1500, 900)
|
||||
dialog.show()
|
||||
|
||||
|
||||
QtCore.Slot(int)
|
||||
def scaleChanged(self, idx):
|
||||
self.obj.ViewObject.Scale = idx
|
||||
|
||||
QtCore.Slot(bool)
|
||||
def gridChanged(self, state):
|
||||
self.obj.ViewObject.Grid = state
|
||||
|
||||
QtCore.Slot()
|
||||
def titleChanged(self):
|
||||
self.obj.ViewObject.Title = self.view_widget.Title.text()
|
||||
|
||||
QtCore.Slot()
|
||||
def xLabelChanged(self):
|
||||
self.obj.ViewObject.XLabel = self.view_widget.XLabel.text()
|
||||
|
||||
QtCore.Slot()
|
||||
def yLabelChanged(self):
|
||||
self.obj.ViewObject.YLabel = self.view_widget.YLabel.text()
|
||||
|
||||
QtCore.Slot(int)
|
||||
def legendPosChanged(self, idx):
|
||||
self.obj.ViewObject.LegendLocation = idx
|
||||
|
||||
QtCore.Slot(bool)
|
||||
def legendShowChanged(self, state):
|
||||
self.obj.ViewObject.Legend = state
|
||||
@@ -109,18 +109,23 @@ class VPPostExtractor:
|
||||
# of the object
|
||||
return {}
|
||||
|
||||
def get_edit_widgets(self, post_dialog):
|
||||
# Returns a list of widgets for editing the object/viewprovider.
|
||||
def get_app_edit_widget(self, post_dialog):
|
||||
# Returns a widgets for editing the object (not viewprovider!)
|
||||
# The widget will be part of the provided post_dialog, and
|
||||
# should use its functionality to inform of changes.
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_preview_widget(self, post_dialog):
|
||||
# Returns a widget for editing the object/viewprovider.
|
||||
def get_view_edit_widget(self, post_dialog):
|
||||
# Returns a widgets for editing the viewprovider (not object!)
|
||||
# The widget will be part of the provided post_dialog, and
|
||||
# should use its functionality to inform of changes.
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_preview(self):
|
||||
# Returns the preview tuple of icon and label: (QPixmap, str)
|
||||
# Note: QPixmap in ratio 2:1
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
@@ -232,12 +232,14 @@ class VPPostHistogramFieldData(view_post_extract.VPPostExtractor):
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostField.svg"
|
||||
|
||||
def get_edit_widgets(self, post_dialog):
|
||||
return [ EditAppWidget(self.Object, post_dialog),
|
||||
EditViewWidget(self.Object, post_dialog)]
|
||||
def get_app_edit_widget(self, post_dialog):
|
||||
return EditAppWidget(self.Object, post_dialog)
|
||||
|
||||
def get_preview_widget(self, post_dialog):
|
||||
return QtGui.QComboBox()
|
||||
def get_view_edit_widget(self, post_dialog):
|
||||
return EditViewWidget(self.Object, post_dialog)
|
||||
|
||||
def get_preview(self):
|
||||
return (QtGui.QPixmap(), self.ViewObject.Legend)
|
||||
|
||||
def get_kw_args(self):
|
||||
# builds kw args from the properties
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
@@ -33,39 +32,445 @@ __url__ = "https://www.freecad.org"
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import Plot
|
||||
import FemGui
|
||||
from PySide import QtGui
|
||||
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
|
||||
|
||||
from . import view_post_extract
|
||||
from . import view_base_fempostvisualization
|
||||
from femtaskpanels import task_post_lineplot
|
||||
|
||||
_GuiPropHelper = view_base_fempostvisualization._GuiPropHelper
|
||||
|
||||
class EditViewWidget(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/PostLineplotFieldViewEdit.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, "LineStyle", self.widget.LineStyle)
|
||||
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.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")
|
||||
self._object.ViewObject.Color = color.getRgb()
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def lineWidthChanged(self, value):
|
||||
self._object.ViewObject.LineWidth = value
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def markerSizeChanged(self, value):
|
||||
self._object.ViewObject.MarkerSize = value
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def markerStyleChanged(self, index):
|
||||
self._object.ViewObject.MarkerStyle = 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 VPPostLinePlot:
|
||||
class EditAppWidget(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/PostLineplotFieldAppEdit.ui"
|
||||
)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.__init_widget()
|
||||
|
||||
def __init_widget(self):
|
||||
# set the other properties
|
||||
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XField", self.widget.XField)
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.XComponent)
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "YField", self.widget.YField)
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "YComponent", self.widget.YComponent)
|
||||
self.widget.Extract.setChecked(self._object.ExtractFrames)
|
||||
|
||||
self.widget.XField.activated.connect(self.xFieldChanged)
|
||||
self.widget.XComponent.activated.connect(self.xComponentChanged)
|
||||
self.widget.YField.activated.connect(self.yFieldChanged)
|
||||
self.widget.YComponent.activated.connect(self.yComponentChanged)
|
||||
self.widget.Extract.toggled.connect(self.extractionChanged)
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def xFieldChanged(self, index):
|
||||
self._object.XField = index
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.XComponent)
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def xComponentChanged(self, index):
|
||||
self._object.XComponent = index
|
||||
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()
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def extractionChanged(self, extract):
|
||||
self._object.ExtractFrames = extract
|
||||
self._post_dialog._recompute()
|
||||
|
||||
|
||||
class VPPostLineplotFieldData(view_post_extract.VPPostExtractor):
|
||||
"""
|
||||
A View Provider for the Post LinePlot object
|
||||
A View Provider for extraction of 1D field data specialy for histograms
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
vobj.Proxy = self
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Legend",
|
||||
group="Lineplot",
|
||||
doc="The name used in the plots legend",
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyColor",
|
||||
name="Color",
|
||||
group="Lineplot",
|
||||
doc="The color the line and the markers are drawn with",
|
||||
value=(0, 85, 255, 255),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="LineStyle",
|
||||
group="Lineplot",
|
||||
doc="The style the line is drawn in",
|
||||
value=['-', '--', '-.', ':', 'None'],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="LineWidth",
|
||||
group="Lineplot",
|
||||
doc="The width the line is drawn with",
|
||||
value=(1, 0.1, 99, 0.1),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="MarkerStyle",
|
||||
group="Lineplot",
|
||||
doc="The style the data markers are drawn with",
|
||||
value=['None', '*', '+', 's', '.', 'o', 'x'],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="MarkerSize",
|
||||
group="Lineplot",
|
||||
doc="The size the data markers are drawn in",
|
||||
value=(10, 0.1, 99, 0.1),
|
||||
),
|
||||
]
|
||||
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 EditAppWidget(self.Object, post_dialog)
|
||||
|
||||
def get_view_edit_widget(self, post_dialog):
|
||||
return EditViewWidget(self.Object, post_dialog)
|
||||
|
||||
def get_preview(self):
|
||||
# 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.])
|
||||
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()
|
||||
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.loadFromData(data.getvalue())
|
||||
|
||||
return (pixmap, self.ViewObject.Legend)
|
||||
|
||||
|
||||
def get_kw_args(self):
|
||||
# builds kw args from the properties
|
||||
kwargs = {}
|
||||
|
||||
# colors need a workaround, some error occurs with rgba tuple
|
||||
kwargs["color"] = self.ViewObject.Color
|
||||
kwargs["markeredgecolor"] = self.ViewObject.Color
|
||||
kwargs["markerfacecolor"] = self.ViewObject.Color
|
||||
kwargs["linestyle"] = self.ViewObject.LineStyle
|
||||
kwargs["linewidth"] = self.ViewObject.LineWidth
|
||||
kwargs["marker"] = self.ViewObject.MarkerStyle
|
||||
kwargs["markersize"] = self.ViewObject.MarkerSize
|
||||
return kwargs
|
||||
|
||||
|
||||
class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
"""
|
||||
A View Provider for Lineplot plots
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Grid",
|
||||
group="Lineplot",
|
||||
doc="If be the bars shoud show the cumulative sum left to rigth",
|
||||
value=False,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="Scale",
|
||||
group="Lineplot",
|
||||
doc="The scale the axis are drawn in",
|
||||
value=["linear","semi-log x", "semi-log y", "log"],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Title",
|
||||
group="Plot",
|
||||
doc="The histogram plot title",
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="XLabel",
|
||||
group="Plot",
|
||||
doc="The label shown for the histogram X axis",
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="YLabel",
|
||||
group="Plot",
|
||||
doc="The label shown for the histogram Y axis",
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Legend",
|
||||
group="Plot",
|
||||
doc="Determines if the legend is plotted",
|
||||
value=True,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="LegendLocation",
|
||||
group="Plot",
|
||||
doc="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_PostLineplot.svg"
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
# make sure we see what we edit
|
||||
vobj.show()
|
||||
def doubleClicked(self,vobj):
|
||||
|
||||
self.show_visualization()
|
||||
super().doubleClicked(vobj)
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
# build up the task panel
|
||||
#taskd = task_post_glyphfilter._TaskPanel(vobj)
|
||||
taskd = task_post_lineplot._TaskPanel(vobj)
|
||||
|
||||
#show it
|
||||
#FreeCADGui.Control.showDialog(taskd)
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def show_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
self._plot = Plot.Plot()
|
||||
self._dialog = QtGui.QDialog(Plot.getMainWindow())
|
||||
box = QtGui.QVBoxLayout()
|
||||
box.addWidget(self._plot)
|
||||
self._dialog.setLayout(box)
|
||||
|
||||
self.drawPlot()
|
||||
self._dialog.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 drawPlot(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
return
|
||||
|
||||
self._plot.axes.clear()
|
||||
|
||||
# we do not iterate the table, but iterate the children. This makes it possible
|
||||
# to attribute the correct styles
|
||||
for child in self.Object.Group:
|
||||
|
||||
table = child.Table
|
||||
kwargs = self.get_kw_args(child)
|
||||
|
||||
# iterate over the table and plot all (note: column 0 is always X!)
|
||||
color_factor = np.linspace(1,0.5,int(table.GetNumberOfColumns()/2))
|
||||
legend_multiframe = table.GetNumberOfColumns() > 2
|
||||
|
||||
for i in range(0,table.GetNumberOfColumns(),2):
|
||||
|
||||
# add the kw args, with some slide change over color for multiple frames
|
||||
tmp_args = {}
|
||||
for key in kwargs:
|
||||
if "color" in key:
|
||||
value = np.array(kwargs[key])*color_factor[int(i/2)]
|
||||
tmp_args[key] = mpl.colors.to_hex(value)
|
||||
else:
|
||||
tmp_args[key] = kwargs[key]
|
||||
|
||||
xdata = VTKArray(table.GetColumn(i))
|
||||
ydata = VTKArray(table.GetColumn(i+1))
|
||||
|
||||
# legend labels
|
||||
if child.ViewObject.Legend:
|
||||
if not legend_multiframe:
|
||||
label = child.ViewObject.Legend
|
||||
else:
|
||||
postfix = table.GetColumnName(i+1).split("-")[-1]
|
||||
label = child.ViewObject.Legend + " - " + postfix
|
||||
else:
|
||||
legend_prefix = ""
|
||||
if len(self.Object.Group) > 1:
|
||||
legend_prefix = child.Source.Label + ": "
|
||||
label = legend_prefix + table.GetColumnName(i+1)
|
||||
|
||||
match self.ViewObject.Scale:
|
||||
case "log":
|
||||
self._plot.axes.loglog(xdata, ydata, **tmp_args, label=label)
|
||||
case "semi-log x":
|
||||
self._plot.axes.semilogx(xdata, ydata, **tmp_args, label=label)
|
||||
case "semi-log y":
|
||||
self._plot.axes.semilogy(xdata, ydata, **tmp_args, label=label)
|
||||
case _:
|
||||
self._plot.axes.plot(xdata, ydata, **tmp_args, label=label)
|
||||
|
||||
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 self.Object.Group:
|
||||
self._plot.axes.legend(loc = self.ViewObject.LegendLocation)
|
||||
|
||||
self._plot.axes.grid(self.ViewObject.Grid)
|
||||
|
||||
self._plot.update()
|
||||
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
# we only react if the table changed, as then know that new data is available
|
||||
if prop == "Table":
|
||||
self.drawPlot()
|
||||
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
|
||||
# for all property changes we need to redraw the plot
|
||||
self.drawPlot()
|
||||
|
||||
def childViewPropertyChanged(self, vobj, prop):
|
||||
|
||||
# on of our extractors has a changed view property.
|
||||
self.drawPlot()
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user