Fem: Implement lineplot visualization

This commit is contained in:
Stefan Tröger
2025-04-16 18:47:01 +02:00
parent 6e4fab1f50
commit c1b11a19f7
22 changed files with 1995 additions and 346 deletions

View File

@@ -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