FEM: Add table post data visualization
This commit is contained in:
@@ -223,6 +223,7 @@ if(BUILD_FEM_VTK_PYTHON)
|
||||
femobjects/post_extract2D.py
|
||||
femobjects/post_histogram.py
|
||||
femobjects/post_lineplot.py
|
||||
femobjects/post_table.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
@@ -637,6 +638,7 @@ if(BUILD_FEM_VTK_PYTHON)
|
||||
femtaskpanels/task_post_glyphfilter.py
|
||||
femtaskpanels/task_post_histogram.py
|
||||
femtaskpanels/task_post_lineplot.py
|
||||
femtaskpanels/task_post_table.py
|
||||
femtaskpanels/task_post_extractor.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
@@ -666,6 +668,7 @@ SET(FemGuiViewProvider_SRCS
|
||||
femviewprovider/view_base_femmeshelement.py
|
||||
femviewprovider/view_base_femobject.py
|
||||
femviewprovider/view_base_fempostvisualization.py
|
||||
femviewprovider/view_base_fempostextractors.py
|
||||
femviewprovider/view_constant_vacuumpermittivity.py
|
||||
femviewprovider/view_constraint_bodyheatsource.py
|
||||
femviewprovider/view_constraint_centrif.py
|
||||
@@ -704,6 +707,7 @@ if(BUILD_FEM_VTK_PYTHON)
|
||||
femviewprovider/view_post_extract.py
|
||||
femviewprovider/view_post_histogram.py
|
||||
femviewprovider/view_post_lineplot.py
|
||||
femviewprovider/view_post_table.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
|
||||
@@ -451,6 +451,7 @@ SET(FemGuiPythonUI_SRCS
|
||||
Resources/ui/PostLineplotFieldViewEdit.ui
|
||||
Resources/ui/PostLineplotFieldAppEdit.ui
|
||||
Resources/ui/PostLineplotIndexAppEdit.ui
|
||||
Resources/ui/PostTableFieldViewEdit.ui
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET(FemPythonUi ALL
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="Extract">
|
||||
<property name="text">
|
||||
<string>One field for all frames</string>
|
||||
<string>One field for each frames</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
43
src/Mod/Fem/Gui/Resources/ui/PostTableFieldViewEdit.ui
Normal file
43
src/Mod/Fem/Gui/Resources/ui/PostTableFieldViewEdit.ui
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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>259</width>
|
||||
<height>38</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="1" column="1">
|
||||
<widget class="QLineEdit" name="Name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -770,6 +770,48 @@ def makePostHistogramIndexOverFrames(doc, name="IndexOverFrames1D"):
|
||||
return obj
|
||||
|
||||
|
||||
def makePostTable(doc, name="Table"):
|
||||
"""makePostTable(document, [name]):
|
||||
creates a FEM post processing histogram plot
|
||||
"""
|
||||
obj = doc.addObject("App::FeaturePython", name)
|
||||
from femobjects import post_table
|
||||
|
||||
post_table.PostTable(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_table
|
||||
view_post_table.VPPostTable(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostTableFieldData(doc, name="FieldData1D"):
|
||||
"""makePostTableFieldData(document, [name]):
|
||||
creates a FEM post processing data extractor for 1D Field data
|
||||
"""
|
||||
obj = doc.addObject("App::FeaturePython", name)
|
||||
from femobjects import post_table
|
||||
|
||||
post_table.PostTableFieldData(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_table
|
||||
view_post_table.VPPostTableFieldData(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostTableIndexOverFrames(doc, name="IndexOverFrames1D"):
|
||||
"""makePostTableIndexOverFrames(document, [name]):
|
||||
creates a FEM post processing data extractor for 1D Field data
|
||||
"""
|
||||
obj = doc.addObject("App::FeaturePython", name)
|
||||
from femobjects import post_table
|
||||
|
||||
post_table.PostTableIndexOverFrames(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_table
|
||||
view_post_table.VPPostTableIndexOverFrames(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
# ********* solver objects ***********************************************************************
|
||||
def makeEquationDeformation(doc, base_solver=None, name="Deformation"):
|
||||
"""makeEquationDeformation(document, [base_solver], [name]):
|
||||
|
||||
@@ -1293,4 +1293,5 @@ if "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__:
|
||||
# setup all visualization commands (register by importing)
|
||||
import femobjects.post_lineplot
|
||||
import femobjects.post_histogram
|
||||
import femobjects.post_table
|
||||
post_visualization.setup_commands("FEM_PostVisualization")
|
||||
|
||||
@@ -252,11 +252,13 @@ class _SummaryWidget(QtGui.QWidget):
|
||||
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])
|
||||
|
||||
if not extr_repr[0].isNull():
|
||||
size = self.viewButton.iconSize()
|
||||
size.setWidth(size.width()*2)
|
||||
self.viewButton.setIconSize(size)
|
||||
self.viewButton.setIcon(extr_repr[0])
|
||||
else:
|
||||
self.viewButton.setIconSize(QtCore.QSize(0,0))
|
||||
|
||||
self.rmButton = QtGui.QToolButton(self)
|
||||
self.rmButton.setIcon(QtGui.QIcon.fromTheme("delete"))
|
||||
|
||||
@@ -34,14 +34,23 @@ from PySide import QtCore
|
||||
|
||||
class VtkTableModel(QtCore.QAbstractTableModel):
|
||||
# Simple table model. Only supports single component columns
|
||||
# One can supply a header_names dict to replace the table column names
|
||||
# in the header. It is a dict "column_idx (int)" to "new name"" or
|
||||
# "orig_name (str)" to "new name"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, header_names = None):
|
||||
super().__init__()
|
||||
self._table = None
|
||||
if header_names:
|
||||
self._header = header_names
|
||||
else:
|
||||
self._header = {}
|
||||
|
||||
def setTable(self, table):
|
||||
def setTable(self, table, header_names = None):
|
||||
self.beginResetModel()
|
||||
self._table = table
|
||||
if header_names:
|
||||
self._header = header_names
|
||||
self.endResetModel()
|
||||
|
||||
def rowCount(self, index):
|
||||
@@ -70,7 +79,14 @@ class VtkTableModel(QtCore.QAbstractTableModel):
|
||||
def headerData(self, section, orientation, role):
|
||||
|
||||
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||
return self._table.GetColumnName(section)
|
||||
if section in self._header:
|
||||
return self._header[section]
|
||||
|
||||
name = self._table.GetColumnName(section)
|
||||
if name in self._header:
|
||||
return self._header[name]
|
||||
|
||||
return name
|
||||
|
||||
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
|
||||
return section
|
||||
|
||||
@@ -21,42 +21,70 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM postprocessing data exxtractor base objcts"
|
||||
__title__ = "FreeCAD FEM postprocessing data visualization base object"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package base_fempostextractors
|
||||
# \ingroup FEM
|
||||
# \brief base objects for data extractors
|
||||
# \brief base objects for data visualizations
|
||||
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
|
||||
from . import base_fempythonobject
|
||||
from . import base_fempostextractors
|
||||
|
||||
# helper functions
|
||||
# ################
|
||||
|
||||
def is_visualization_object(obj):
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
if not hasattr(obj, "Proxy"):
|
||||
return False
|
||||
|
||||
return hasattr(obj.Proxy, "VisualizationType")
|
||||
|
||||
|
||||
def get_visualization_type(obj):
|
||||
# returns the extractor type string, or throws exception if
|
||||
# not a extractor
|
||||
return obj.Proxy.VisualizationType
|
||||
|
||||
|
||||
def is_visualization_extractor_type(obj, vistype):
|
||||
|
||||
# must be extractor
|
||||
if not base_fempostextractors.is_extractor_object(obj):
|
||||
return False
|
||||
|
||||
# must be visualization object
|
||||
if not is_visualization_object(obj):
|
||||
return False
|
||||
|
||||
# must be correct type
|
||||
if get_visualization_type(obj) != vistype:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
# Base class for all visualizations
|
||||
# It collects all data from its extraction objects into a table.
|
||||
# Note: Never use directly, always subclass! This class does not create a
|
||||
# Visualization variable, hence will not work correctly.
|
||||
class PostVisualization(base_fempythonobject.BaseFemPythonObject):
|
||||
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
obj.addExtension("App::GroupExtensionPython")
|
||||
self._setup_properties(obj)
|
||||
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
@@ -64,8 +92,96 @@ class PostVisualization(base_fempythonobject.BaseFemPythonObject):
|
||||
prop.add_to_object(obj)
|
||||
|
||||
|
||||
def _get_properties(self):
|
||||
# override if subclass wants to add additional properties
|
||||
|
||||
prop = [
|
||||
base_fempostextractors._PropHelper(
|
||||
type="Fem::PropertyPostDataObject",
|
||||
name="Table",
|
||||
group="Base",
|
||||
doc="The data table that stores the data for visualization",
|
||||
value=vtkTable(),
|
||||
),
|
||||
]
|
||||
return prop
|
||||
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
# if a new property was added we handle it by setup
|
||||
# Override if subclass needs to handle changed property type
|
||||
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
return []
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
# Ensure only correct child object types are in the group
|
||||
|
||||
if prop == "Group":
|
||||
# check if all objects are allowed
|
||||
|
||||
children = obj.Group
|
||||
for child in obj.Group:
|
||||
if not is_visualization_extractor_type(child, self.VisualizationType):
|
||||
FreeCAD.Console.PrintWarning(f"{child.Label} is not a {self.VisualizationType} extraction object, cannot be added")
|
||||
children.remove(child)
|
||||
|
||||
if len(obj.Group) != len(children):
|
||||
obj.Group = children
|
||||
|
||||
|
||||
def execute(self, obj):
|
||||
# Collect all extractor child data into our table
|
||||
# Note: Each childs table can have different number of rows. We need
|
||||
# to pad the date for our table in this case
|
||||
|
||||
rows = self.getLongestColumnLength(obj)
|
||||
table = vtkTable()
|
||||
for child in obj.Group:
|
||||
|
||||
# If child has no Source, its table should be empty. However,
|
||||
# it would theoretical be possible that child source was set
|
||||
# to none without recompute, and the visualization was manually
|
||||
# recomputed afterwards
|
||||
if not child.Source and (c_table.GetNumberOfColumns() > 0):
|
||||
FreeCAD.Console.PrintWarning(f"{child.Label} has data, but no Source object. Will be ignored")
|
||||
continue
|
||||
|
||||
c_table = child.Table
|
||||
for i in range(c_table.GetNumberOfColumns()):
|
||||
c_array = c_table.GetColumn(i)
|
||||
array = vtkDoubleArray()
|
||||
|
||||
if c_array.GetNumberOfTuples() == rows:
|
||||
# simple deep copy is enough
|
||||
array.DeepCopy(c_array)
|
||||
|
||||
else:
|
||||
array.SetNumberOfComponents(c_array.GetNumberOfComponents())
|
||||
array.SetNumberOfTuples(rows)
|
||||
array.Fill(0) # so that all non-used entries are set to 0
|
||||
for i in range(c_array.GetNumberOfTuples()):
|
||||
array.SetTuple(i, c_array.GetTuple(i))
|
||||
|
||||
array.SetName(f"{child.Source.Name}: {c_array.GetName()}")
|
||||
table.AddColumn(array)
|
||||
|
||||
|
||||
obj.Table = table
|
||||
return False
|
||||
|
||||
|
||||
def getLongestColumnLength(self, obj):
|
||||
# iterate all extractor children and get the column lengths
|
||||
|
||||
length = 0
|
||||
for child in obj.Group:
|
||||
if base_fempostextractors.is_extractor_object(child):
|
||||
table = child.Table
|
||||
if table.GetNumberOfColumns() > 0:
|
||||
# we assume all columns of an extractor have same length
|
||||
num = table.GetColumn(0).GetNumberOfTuples()
|
||||
if num > length:
|
||||
length = num
|
||||
|
||||
return length
|
||||
|
||||
@@ -29,6 +29,8 @@ __url__ = "https://www.freecad.org"
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
@@ -176,9 +178,9 @@ class PostIndexOverFrames1D(base_fempostextractors.Extractor1D):
|
||||
frame_array.SetTuple(i, idx, array)
|
||||
|
||||
if frame_array.GetNumberOfComponents() > 1:
|
||||
frame_array.SetName(f"{obj.XField} ({obj.XComponent})")
|
||||
frame_array.SetName(f"{obj.XField} ({obj.XComponent}) @Idx {obj.Index}")
|
||||
else:
|
||||
frame_array.SetName(f"{obj.XField}")
|
||||
frame_array.SetName(f"{obj.XField} @Idx {obj.Index}")
|
||||
|
||||
self._x_array_component_to_table(obj, frame_array, table)
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ __url__ = "https://www.freecad.org"
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
@@ -207,9 +209,9 @@ class PostIndexOverFrames2D(base_fempostextractors.Extractor2D):
|
||||
|
||||
frame_x_array.SetName("Frames")
|
||||
if frame_y_array.GetNumberOfComponents() > 1:
|
||||
frame_y_array.SetName(f"{obj.YField} ({obj.YComponent})")
|
||||
frame_y_array.SetName(f"{obj.YField} ({obj.YComponent}) @Idx {obj.Index}")
|
||||
else:
|
||||
frame_y_array.SetName(obj.YField)
|
||||
frame_y_array.SetName(f"{obj.YField} @Idx {obj.Index}")
|
||||
|
||||
table.AddColumn(frame_x_array)
|
||||
self._y_array_component_to_table(obj, frame_y_array, table)
|
||||
|
||||
@@ -29,17 +29,11 @@ __url__ = "https://www.freecad.org"
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying histograms
|
||||
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract1D
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
|
||||
from femguiutils import post_visualization
|
||||
|
||||
# register visualization and extractors
|
||||
@@ -85,6 +79,7 @@ class PostHistogramFieldData(post_extract1D.PostFieldData1D):
|
||||
"""
|
||||
VisualizationType = "Histogram"
|
||||
|
||||
|
||||
class PostHistogramIndexOverFrames(post_extract1D.PostIndexOverFrames1D):
|
||||
"""
|
||||
A 1D index extraction for histogram.
|
||||
@@ -96,57 +91,11 @@ class PostHistogram(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as histograms
|
||||
"""
|
||||
|
||||
VisualizationType = "Histogram"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
obj.addExtension("App::GroupExtensionPython")
|
||||
|
||||
def _get_properties(self):
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="Fem::PropertyPostDataObject",
|
||||
name="Table",
|
||||
group="Base",
|
||||
doc="The data table that stores the plotted data, one column per histogram",
|
||||
value=vtkTable(),
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
if prop == "Group":
|
||||
# check if all objects are allowed
|
||||
|
||||
children = obj.Group
|
||||
for child in obj.Group:
|
||||
if not is_histogram_extractor(child):
|
||||
FreeCAD.Console.PrintWarning(f"{child.Label} is not a data histogram data extraction object, cannot be added")
|
||||
children.remove(child)
|
||||
|
||||
if len(obj.Group) != len(children):
|
||||
obj.Group = children
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
# during execution we collect all child data into our table
|
||||
table = vtkTable()
|
||||
for child in obj.Group:
|
||||
|
||||
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)
|
||||
|
||||
obj.Table = table
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,17 +29,10 @@ __url__ = "https://www.freecad.org"
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract2D
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
|
||||
from femguiutils import post_visualization
|
||||
|
||||
# register visualization and extractors
|
||||
@@ -85,6 +78,7 @@ class PostLineplotFieldData(post_extract2D.PostFieldData2D):
|
||||
"""
|
||||
VisualizationType = "Lineplot"
|
||||
|
||||
|
||||
class PostLineplotIndexOverFrames(post_extract2D.PostIndexOverFrames2D):
|
||||
"""
|
||||
A 2D index extraction for lineplot.
|
||||
@@ -97,56 +91,7 @@ class PostLineplot(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as line plots
|
||||
"""
|
||||
|
||||
VisualizationType = "Lineplot"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
obj.addExtension("App::GroupExtensionPython")
|
||||
|
||||
def _get_properties(self):
|
||||
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 onChanged(self, obj, prop):
|
||||
|
||||
if prop == "Group":
|
||||
# check if all objects are allowed
|
||||
|
||||
children = obj.Group
|
||||
for child in obj.Group:
|
||||
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):
|
||||
|
||||
# during execution we collect all child data into our table
|
||||
table = vtkTable()
|
||||
for child in obj.Group:
|
||||
|
||||
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)
|
||||
|
||||
obj.Table = table
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -21,191 +21,76 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD post line plot"
|
||||
__title__ = "FreeCAD post table"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_lineplot
|
||||
## @package post_table
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
# \brief Post processing plot displaying tables
|
||||
|
||||
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"
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract1D
|
||||
|
||||
|
||||
class PostLinePlot(base_fempythonobject.BaseFemPythonObject):
|
||||
from femguiutils import post_visualization
|
||||
|
||||
# register visualization and extractors
|
||||
post_visualization.register_visualization("Table",
|
||||
":/icons/FEM_PostSpreadsheet.svg",
|
||||
"ObjectsFem",
|
||||
"makePostTable")
|
||||
|
||||
post_visualization.register_extractor("Table",
|
||||
"TableFieldData",
|
||||
":/icons/FEM_PostField.svg",
|
||||
"1D",
|
||||
"Field",
|
||||
"ObjectsFem",
|
||||
"makePostTableFieldData")
|
||||
|
||||
|
||||
post_visualization.register_extractor("Table",
|
||||
"TableIndexOverFrames",
|
||||
":/icons/FEM_PostIndex.svg",
|
||||
"1D",
|
||||
"Index",
|
||||
"ObjectsFem",
|
||||
"makePostTableIndexOverFrames")
|
||||
|
||||
# Implementation
|
||||
# ##############
|
||||
|
||||
def is_table_extractor(obj):
|
||||
|
||||
if not base_fempostextractors.is_extractor_object(obj):
|
||||
return False
|
||||
|
||||
if not hasattr(obj.Proxy, "VisualizationType"):
|
||||
return False
|
||||
|
||||
return obj.Proxy.VisualizationType == "Table"
|
||||
|
||||
|
||||
class PostTableFieldData(post_extract1D.PostFieldData1D):
|
||||
"""
|
||||
A post processing extraction for plotting lines
|
||||
A 1D Field extraction for tables.
|
||||
"""
|
||||
|
||||
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
|
||||
VisualizationType = "Table"
|
||||
|
||||
|
||||
class PostPlotLine(base_fempythonobject.BaseFemPythonObject):
|
||||
class PostTableIndexOverFrames(post_extract1D.PostIndexOverFrames1D):
|
||||
"""
|
||||
A 1D index extraction for table.
|
||||
"""
|
||||
VisualizationType = "Table"
|
||||
|
||||
Type = "App::FeaturePython"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._setup_properties(obj)
|
||||
class PostTable(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as tables
|
||||
"""
|
||||
VisualizationType = "Table"
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -60,6 +60,13 @@ class _BasePostTaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.obj.Document.recompute()
|
||||
|
||||
def accept(self):
|
||||
print("accept")
|
||||
return super().accept()
|
||||
|
||||
def reject(self):
|
||||
print("reject")
|
||||
return super().reject()
|
||||
|
||||
# Helper functions
|
||||
# ################
|
||||
|
||||
94
src/Mod/Fem/femtaskpanels/task_post_table.py
Normal file
94
src/Mod/Fem/femtaskpanels/task_post_table.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# ***************************************************************************
|
||||
# * 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 histogram plot task panel"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package task_post_histogram
|
||||
# \ingroup FEM
|
||||
# \brief task panel for post histogram 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()
|
||||
self.data_widget.show_table = QtGui.QPushButton()
|
||||
self.data_widget.show_table.setText("Show table")
|
||||
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.addWidget(self.data_widget.show_table)
|
||||
vbox.addSpacing(10)
|
||||
|
||||
extracts = elv.ExtractLinkView(self.obj, False, self)
|
||||
vbox.addWidget(extracts)
|
||||
|
||||
self.data_widget.setLayout(vbox)
|
||||
self.data_widget.setWindowTitle("Table data")
|
||||
self.data_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostSpreadsheet.svg"))
|
||||
|
||||
|
||||
# histogram parameter widget
|
||||
#self.view_widget = FreeCADGui.PySideUic.loadUi(
|
||||
# FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostTable.ui"
|
||||
#)
|
||||
#self.view_widget.setWindowTitle("Table view settings")
|
||||
#self.view_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostTable.svg"))
|
||||
|
||||
self.__init_widgets()
|
||||
|
||||
# form made from param and selection widget
|
||||
self.form = [self.data_widget]
|
||||
|
||||
|
||||
# Setup functions
|
||||
# ###############
|
||||
|
||||
def __init_widgets(self):
|
||||
|
||||
# connect data widget
|
||||
self.data_widget.show_table.clicked.connect(self.showTable)
|
||||
|
||||
# set current values to view widget
|
||||
viewObj = self.obj.ViewObject
|
||||
|
||||
|
||||
@QtCore.Slot()
|
||||
def showTable(self):
|
||||
self.obj.ViewObject.Proxy.show_visualization()
|
||||
|
||||
@@ -66,7 +66,7 @@ class VPPostExtractor:
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
|
||||
# one of our view properties was changed. Lets inform our parent plot
|
||||
# one of our view properties was changed. Lets inform our parent visualization
|
||||
# that this happend, as this is the one that needs to redraw
|
||||
|
||||
if prop == "Proxy":
|
||||
@@ -92,6 +92,10 @@ class VPPostExtractor:
|
||||
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode=0):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
guidoc = FreeCADGui.getDocument(vobj.Object.Document)
|
||||
|
||||
@@ -104,10 +108,20 @@ class VPPostExtractor:
|
||||
|
||||
return True
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
|
||||
# To be implemented by subclasses:
|
||||
# ################################
|
||||
|
||||
def get_kw_args(self):
|
||||
# should return the plot keyword arguments that represent the properties
|
||||
# of the object
|
||||
return {}
|
||||
# Returns the matplotlib plot keyword arguments that represent the
|
||||
# properties of the object.
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_app_edit_widget(self, post_dialog):
|
||||
# Returns a widgets for editing the object (not viewprovider!)
|
||||
@@ -125,10 +139,3 @@ class VPPostExtractor:
|
||||
# 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
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
@@ -45,6 +45,8 @@ class VPPostVisualization:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self._setup_properties(vobj)
|
||||
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
||||
|
||||
|
||||
def _setup_properties(self, vobj):
|
||||
pl = vobj.PropertiesList
|
||||
@@ -52,16 +54,21 @@ class VPPostVisualization:
|
||||
if not prop.name in pl:
|
||||
prop.add_to_object(vobj)
|
||||
|
||||
|
||||
def _get_properties(self):
|
||||
return []
|
||||
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
self.ViewObject = vobj
|
||||
|
||||
|
||||
def isShow(self):
|
||||
# Mark ourself as visible in the tree
|
||||
return True
|
||||
|
||||
|
||||
def doubleClicked(self,vobj):
|
||||
|
||||
guidoc = FreeCADGui.getDocument(vobj.Object.Document)
|
||||
@@ -71,21 +78,47 @@ class VPPostVisualization:
|
||||
FreeCADGui.Control.closeDialog()
|
||||
guidoc.resetEdit()
|
||||
|
||||
# open task dialog
|
||||
guidoc.setEdit(vobj.Object.Name)
|
||||
|
||||
# show visualization
|
||||
self.show_visualization()
|
||||
|
||||
return True
|
||||
|
||||
def show_visualization(self):
|
||||
# shows the visualization without going into edit mode
|
||||
# to be implemented by subclasses
|
||||
pass
|
||||
def unsetEdit(self, vobj, mode=0):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def get_kw_args(self, obj):
|
||||
# returns a dictionary with all visualization options needed for plotting
|
||||
# based on the view provider properties
|
||||
return {}
|
||||
def updateData(self, obj, prop):
|
||||
# If the data changed we need to update the visualization
|
||||
if prop == "Table":
|
||||
self.update_visualization()
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
# for all property changes we need to update the visualization
|
||||
self.update_visualization()
|
||||
|
||||
def childViewPropertyChanged(self, vobj, prop):
|
||||
# One of the extractors view properties has changed, we need to
|
||||
# update the visualization
|
||||
self.update_visualization()
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
|
||||
# To be implemented by subclasses:
|
||||
# ################################
|
||||
|
||||
def update_visualization(self):
|
||||
# The visualization data or any relevant view property has changed,
|
||||
# and the visualization itself needs to update to reflect that
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def show_visualization(self):
|
||||
# Shows the visualization without going into edit mode
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
@@ -42,7 +42,7 @@ import matplotlib as mpl
|
||||
|
||||
from vtkmodules.numpy_interface.dataset_adapter import VTKArray
|
||||
|
||||
from . import view_post_extract
|
||||
from . import view_base_fempostextractors
|
||||
from . import view_base_fempostvisualization
|
||||
from femtaskpanels import task_post_histogram
|
||||
|
||||
@@ -244,7 +244,7 @@ class EditIndexAppWidget(QtGui.QWidget):
|
||||
self._post_dialog._recompute()
|
||||
|
||||
|
||||
class VPPostHistogramFieldData(view_post_extract.VPPostExtractor):
|
||||
class VPPostHistogramFieldData(view_base_fempostextractors.VPPostExtractor):
|
||||
"""
|
||||
A View Provider for extraction of 1D field data specialy for histograms
|
||||
"""
|
||||
@@ -378,7 +378,7 @@ class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization):
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
||||
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
@@ -458,13 +458,10 @@ class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization):
|
||||
]
|
||||
return prop
|
||||
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostHistogram.svg"
|
||||
|
||||
def doubleClicked(self,vobj):
|
||||
|
||||
self.show_visualization()
|
||||
super().doubleClicked(vobj)
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
@@ -482,24 +479,12 @@ class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization):
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
main = Plot.getMainWindow()
|
||||
self._plot = Plot.Plot()
|
||||
self._plot.destroyed.connect(self.destroyed)
|
||||
self._dialog = QtGui.QDialog(main)
|
||||
box = QtGui.QVBoxLayout()
|
||||
box.addWidget(self._plot)
|
||||
self._dialog.resize(main.size().height()/2, main.size().height()/3) # keep it square
|
||||
self._dialog.setLayout(box)
|
||||
self._plot.resize(main.size().height()/2, main.size().height()/3) # keep it square
|
||||
self.update_visualization()
|
||||
|
||||
self.drawPlot()
|
||||
self._dialog.show()
|
||||
self._plot.show()
|
||||
|
||||
|
||||
def destroyed(self, obj):
|
||||
print("*********************************************************")
|
||||
print("**************** ******************")
|
||||
print("**************** destroy ******************")
|
||||
print("**************** ******************")
|
||||
print("*********************************************************")
|
||||
|
||||
def get_kw_args(self, obj):
|
||||
view = obj.ViewObject
|
||||
if not view or not hasattr(view, "Proxy"):
|
||||
@@ -508,7 +493,8 @@ class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization):
|
||||
return {}
|
||||
return view.Proxy.get_kw_args()
|
||||
|
||||
def drawPlot(self):
|
||||
|
||||
def update_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
return
|
||||
@@ -579,26 +565,3 @@ class VPPostHistogram(view_base_fempostvisualization.VPPostVisualization):
|
||||
|
||||
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
|
||||
|
||||
@@ -42,9 +42,10 @@ import matplotlib as mpl
|
||||
|
||||
from vtkmodules.numpy_interface.dataset_adapter import VTKArray
|
||||
|
||||
from . import view_post_extract
|
||||
from . import view_base_fempostextractors
|
||||
from . import view_base_fempostvisualization
|
||||
from femtaskpanels import task_post_lineplot
|
||||
from femguiutils import post_visualization as pv
|
||||
|
||||
_GuiPropHelper = view_base_fempostvisualization._GuiPropHelper
|
||||
|
||||
@@ -248,7 +249,7 @@ class EditIndexAppWidget(QtGui.QWidget):
|
||||
self._post_dialog._recompute()
|
||||
|
||||
|
||||
class VPPostLineplotFieldData(view_post_extract.VPPostExtractor):
|
||||
class VPPostLineplotFieldData(view_base_fempostextractors.VPPostExtractor):
|
||||
"""
|
||||
A View Provider for extraction of 2D field data specialy for histograms
|
||||
"""
|
||||
@@ -376,7 +377,7 @@ class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
||||
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
@@ -435,13 +436,10 @@ class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
]
|
||||
return prop
|
||||
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostLineplot.svg"
|
||||
|
||||
def doubleClicked(self,vobj):
|
||||
|
||||
self.show_visualization()
|
||||
super().doubleClicked(vobj)
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
@@ -457,16 +455,13 @@ class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
def show_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
self._plot = Plot.Plot()
|
||||
main = Plot.getMainWindow()
|
||||
self._dialog = QtGui.QDialog(main)
|
||||
box = QtGui.QVBoxLayout()
|
||||
box.addWidget(self._plot)
|
||||
self._dialog.resize(main.size().height()/2, main.size().height()/3) # keep aspect ratio constant
|
||||
self._dialog.setLayout(box)
|
||||
self._plot = Plot.Plot()
|
||||
self._plot.resize(main.size().height()/2, main.size().height()/3) # keep the aspect ratio
|
||||
self.update_visualization()
|
||||
|
||||
self._plot.show()
|
||||
|
||||
self.drawPlot()
|
||||
self._dialog.show()
|
||||
|
||||
def get_kw_args(self, obj):
|
||||
view = obj.ViewObject
|
||||
@@ -476,7 +471,8 @@ class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
return {}
|
||||
return view.Proxy.get_kw_args()
|
||||
|
||||
def drawPlot(self):
|
||||
|
||||
def update_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
return
|
||||
@@ -545,26 +541,3 @@ class VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
|
||||
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
|
||||
|
||||
287
src/Mod/Fem/femviewprovider/view_post_table.py
Normal file
287
src/Mod/Fem/femviewprovider/view_post_table.py
Normal file
@@ -0,0 +1,287 @@
|
||||
# ***************************************************************************
|
||||
# * 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 postprocessing table ViewProvider for the document object"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package view_post_table
|
||||
# \ingroup FEM
|
||||
# \brief view provider for post table object
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import Plot
|
||||
import FemGui
|
||||
from PySide import QtGui, QtCore
|
||||
|
||||
import io
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
|
||||
from vtkmodules.numpy_interface.dataset_adapter import VTKArray
|
||||
|
||||
from . import view_base_fempostextractors
|
||||
from . import view_base_fempostvisualization
|
||||
from femtaskpanels import task_post_table
|
||||
from femguiutils import vtk_table_view as vtv
|
||||
|
||||
_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/PostTableFieldViewEdit.ui"
|
||||
)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.__init_widget()
|
||||
|
||||
def __init_widget(self):
|
||||
# set the other properties
|
||||
self.widget.Name.setText(self._object.ViewObject.Name)
|
||||
self.widget.Name.editingFinished.connect(self.legendChanged)
|
||||
|
||||
@QtCore.Slot()
|
||||
def legendChanged(self):
|
||||
self._object.ViewObject.Name = self.widget.Name.text()
|
||||
|
||||
|
||||
class EditFieldAppWidget(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 (we reuse histogram, as we need the exact same)
|
||||
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):
|
||||
# set the other properties
|
||||
|
||||
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
|
||||
|
||||
# load the ui and set it up (we reuse histogram, as we need the exact same)
|
||||
self.widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostHistogramIndexAppEdit.ui"
|
||||
)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.__init_widget()
|
||||
|
||||
def __init_widget(self):
|
||||
# set the other properties
|
||||
|
||||
self.widget.Index.setValue(self._object.Index)
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XField", self.widget.Field)
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.Component)
|
||||
|
||||
self.widget.Index.valueChanged.connect(self.indexChanged)
|
||||
self.widget.Field.activated.connect(self.fieldChanged)
|
||||
self.widget.Component.activated.connect(self.componentChanged)
|
||||
|
||||
# sometimes wierd sizes occur with spinboxes
|
||||
self.widget.Index.setMaximumHeight(self.widget.Field.sizeHint().height())
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def fieldChanged(self, index):
|
||||
self._object.XField = index
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XComponent", self.widget.Component)
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def componentChanged(self, index):
|
||||
self._object.XComponent = index
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def indexChanged(self, value):
|
||||
self._object.Index = value
|
||||
self._post_dialog._recompute()
|
||||
|
||||
|
||||
class VPPostTableFieldData(view_base_fempostextractors.VPPostExtractor):
|
||||
"""
|
||||
A View Provider for extraction of 1D field data specialy for tables
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Name",
|
||||
group="Table",
|
||||
doc="The name used in the table header. Default name is used if empty",
|
||||
value="",
|
||||
),
|
||||
]
|
||||
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):
|
||||
name = "----"
|
||||
if self.ViewObject.Name:
|
||||
name = self.ViewObject.Name
|
||||
return (QtGui.QPixmap(), name)
|
||||
|
||||
|
||||
class VPPostTableIndexOverFrames(VPPostTableFieldData):
|
||||
"""
|
||||
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 VPPostTable(view_base_fempostvisualization.VPPostVisualization):
|
||||
"""
|
||||
A View Provider for Table plots
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostSpreadsheet.svg"
|
||||
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
# build up the task panel
|
||||
taskd = task_post_table._TaskPanel(vobj)
|
||||
|
||||
#show it
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def show_visualization(self):
|
||||
|
||||
if not hasattr(self, "_tableview") or not self._tableview:
|
||||
self._tableModel = vtv.VtkTableModel()
|
||||
self._tableview = vtv.VtkTableView(self._tableModel)
|
||||
self.update_visualization()
|
||||
|
||||
self._tableview.show()
|
||||
|
||||
|
||||
def update_visualization(self):
|
||||
|
||||
if not hasattr(self, "_tableModel") or not self._tableModel:
|
||||
return
|
||||
|
||||
# we collect the header names from the viewproviders
|
||||
table = self.Object.Table
|
||||
header = {}
|
||||
for child in self.Object.Group:
|
||||
|
||||
if not child.Source:
|
||||
continue
|
||||
|
||||
new_name = child.ViewObject.Name
|
||||
if new_name:
|
||||
# this child uses a custom name. We try to find all
|
||||
# columns that are from this child and use custom header for it
|
||||
for i in range(table.GetNumberOfColumns()):
|
||||
if child.Source.Name in table.GetColumnName(i):
|
||||
header[table.GetColumnName(i)] = new_name
|
||||
|
||||
self._tableModel.setTable(self.Object.Table, header)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user