FEM: Draft architecture of post data extraction with histogram example
This commit is contained in:
199
src/Mod/Fem/femobjects/base_fempostextractors.py
Normal file
199
src/Mod/Fem/femobjects/base_fempostextractors.py
Normal file
@@ -0,0 +1,199 @@
|
||||
# ***************************************************************************
|
||||
# * 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 data exxtractor base objcts"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package base_fempostextractors
|
||||
# \ingroup FEM
|
||||
# \brief base objects for data extractors
|
||||
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
from . import base_fempythonobject
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
# helper functions
|
||||
# ################
|
||||
|
||||
def is_extractor_object(obj):
|
||||
if not hasattr(obj, "Proxy"):
|
||||
return False
|
||||
|
||||
return hasattr(obj.Proxy, "ExtractionType")
|
||||
|
||||
def get_extraction_type(obj):
|
||||
# returns the extractor type string, or throws exception if
|
||||
# not a extractor
|
||||
return obj.Proxy.ExtractionType
|
||||
|
||||
def get_extraction_dimension(obj):
|
||||
# returns the extractor dimension string, or throws exception if
|
||||
# not a extractor
|
||||
return obj.Proxy.ExtractionDimension
|
||||
|
||||
|
||||
# Base class for all extractors with common source and table handling functionality
|
||||
# Note: Never use directly, always subclass! This class does not create a
|
||||
# ExtractionType/Dimension variable, hence will not work correctly.
|
||||
class Extractor(base_fempythonobject.BaseFemPythonObject):
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
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="Fem::PropertyPostDataObject",
|
||||
name="Table",
|
||||
group="Base",
|
||||
doc="The data table that stores the extracted data",
|
||||
value=vtkTable(),
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyLink",
|
||||
name="Source",
|
||||
group="Base",
|
||||
doc="The data source from which the data is extracted",
|
||||
value=None,
|
||||
),
|
||||
]
|
||||
return prop
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self._setup_properties(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.Source = None
|
||||
|
||||
|
||||
def get_vtk_table(self, obj):
|
||||
if not obj.DataTable:
|
||||
obj.DataTable = vtkTable()
|
||||
|
||||
return obj.DataTable
|
||||
|
||||
def component_options(self, num):
|
||||
|
||||
match num:
|
||||
case 2:
|
||||
return ["X", "Y"]
|
||||
case 3:
|
||||
return ["X", "Y", "Z"]
|
||||
case _:
|
||||
return ["Not a vector"]
|
||||
|
||||
|
||||
class Extractor1D(Extractor):
|
||||
|
||||
ExtractionDimension = "1D"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
|
||||
def _get_properties(self):
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XField",
|
||||
group="X Data",
|
||||
doc="The field to use as X data",
|
||||
value=[],
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XComponent",
|
||||
group="X Data",
|
||||
doc="Which part of the X field vector to use for the X axis",
|
||||
value=[],
|
||||
),
|
||||
]
|
||||
|
||||
return super()._get_properties() + prop
|
||||
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
super().onChanged(obj, prop)
|
||||
|
||||
if prop == "XField" and obj.Source and obj.Source.getDataSet():
|
||||
point_data = obj.Source.getDataSet().GetPointData()
|
||||
self._setup_x_component_property(obj, point_data)
|
||||
|
||||
if prop == "Source":
|
||||
if obj.Source:
|
||||
dset = obj.Source.getDataSet()
|
||||
if dset:
|
||||
self._setup_x_properties(obj, dset)
|
||||
else:
|
||||
self._clear_x_properties(obj)
|
||||
else:
|
||||
self._clear_x_properties(obj)
|
||||
|
||||
def _setup_x_component_property(self, obj, point_data):
|
||||
|
||||
if obj.XField == "Index":
|
||||
obj.XComponent = self.component_options(1)
|
||||
elif obj.XField == "Position":
|
||||
obj.XComponent = self.component_options(3)
|
||||
else:
|
||||
array = point_data.GetAbstractArray(obj.XField)
|
||||
obj.XComponent = self.component_options(array.GetNumberOfComponents())
|
||||
|
||||
def _clear_x_properties(self, obj):
|
||||
if hasattr(obj, "XComponent"):
|
||||
obj.XComponent = []
|
||||
if hasattr(obj, "XField"):
|
||||
obj.XField = []
|
||||
|
||||
def _setup_x_properties(self, obj, dataset):
|
||||
# Set all X Data properties correctly for the given dataset
|
||||
fields = ["Index", "Position"]
|
||||
point_data = dataset.GetPointData()
|
||||
|
||||
for i in range(point_data.GetNumberOfArrays()):
|
||||
fields.append(point_data.GetArrayName(i))
|
||||
|
||||
current_field = obj.XField
|
||||
obj.XField = fields
|
||||
if current_field in fields:
|
||||
obj.XField = current_field
|
||||
|
||||
self._setup_x_component_property(obj, point_data)
|
||||
|
||||
|
||||
71
src/Mod/Fem/femobjects/base_fempostvisualizations.py
Normal file
71
src/Mod/Fem/femobjects/base_fempostvisualizations.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# ***************************************************************************
|
||||
# * 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 data exxtractor base objcts"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package base_fempostextractors
|
||||
# \ingroup FEM
|
||||
# \brief base objects for data extractors
|
||||
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
from . import base_fempythonobject
|
||||
|
||||
# helper functions
|
||||
# ################
|
||||
|
||||
def is_visualization_object(obj):
|
||||
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
|
||||
|
||||
|
||||
# Base class for all visualizations
|
||||
# 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)
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _setup_properties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
if not prop.name in pl:
|
||||
prop.add_to_object(obj)
|
||||
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self._setup_properties(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
return []
|
||||
@@ -54,6 +54,7 @@ class _PropHelper:
|
||||
Helper class to manage property data inside proxy objects.
|
||||
Initialization keywords are the same used with PropertyContainer
|
||||
to add dynamics properties plus "value" for the initial value.
|
||||
Note: Is used as base for a GUI version, be aware when refactoring
|
||||
"""
|
||||
|
||||
def __init__(self, **kwds):
|
||||
|
||||
178
src/Mod/Fem/femobjects/post_extract1D.py
Normal file
178
src/Mod/Fem/femobjects/post_extract1D.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# ***************************************************************************
|
||||
# * 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_histogram
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
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):
|
||||
"""
|
||||
A post processing extraction of one 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 __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
|
||||
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
|
||||
array = self.__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)
|
||||
|
||||
else:
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
for timestep in timesteps:
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self.__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)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
|
||||
|
||||
class PostIndexData1D(base_fempostextractors.Extractor1D):
|
||||
"""
|
||||
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
|
||||
142
src/Mod/Fem/femobjects/post_histogram.py
Normal file
142
src/Mod/Fem/femobjects/post_histogram.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# ***************************************************************************
|
||||
# * 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_histogram
|
||||
# \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_extract1D
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
|
||||
from femguiutils import post_visualization
|
||||
|
||||
# register visualization and extractors
|
||||
post_visualization.register_visualization("Histogram",
|
||||
":/icons/FEM_PostHistogram.svg",
|
||||
"ObjectsFem",
|
||||
"makePostVtkHistogram")
|
||||
|
||||
post_visualization.register_extractor("Histogram",
|
||||
"HistogramFieldData",
|
||||
":/icons/FEM_PostField.svg",
|
||||
"1D",
|
||||
"Field",
|
||||
"ObjectsFem",
|
||||
"makePostVtkHistogramFieldData")
|
||||
|
||||
|
||||
# Implementation
|
||||
# ##############
|
||||
|
||||
def is_histogram_extractor(obj):
|
||||
|
||||
if not base_fempostextractors.is_extractor_object(obj):
|
||||
return False
|
||||
|
||||
if not hasattr(obj.Proxy, "VisualizationType"):
|
||||
return False
|
||||
|
||||
return obj.Proxy.VisualizationType == "Histogram"
|
||||
|
||||
|
||||
class PostHistogramFieldData(post_extract1D.PostFieldData1D):
|
||||
"""
|
||||
A 1D Field extraction for histograms.
|
||||
"""
|
||||
VisualizationType = "Histogram"
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
211
src/Mod/Fem/femobjects/post_lineplot.py
Normal file
211
src/Mod/Fem/femobjects/post_lineplot.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
|
||||
|
||||
Reference in New Issue
Block a user