# *************************************************************************** # * Copyright (c) 2025 Stefan Tröger * # * * # * 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 visualization base object" __author__ = "Stefan Tröger" __url__ = "https://www.freecad.org" ## @package base_fempostextractors # \ingroup FEM # \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) self.Type = "Fem::FemPostVisualization" obj.addExtension("App::GroupExtensionPython") 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): # 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 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 (child.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 j in range(c_array.GetNumberOfTuples()): array.SetTuple(j, c_array.GetTuple(j)) 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