Merge pull request #21221 from ickby/FEM_extract_data
FEM: Data extraction and visualization
This commit is contained in:
@@ -382,18 +382,21 @@ FemPostDataAlongLineFilter::FemPostDataAlongLineFilter()
|
||||
m_line->SetPoint2(vec2.x, vec2.y, vec2.z);
|
||||
m_line->SetResolution(Resolution.getValue());
|
||||
|
||||
m_arclength = vtkSmartPointer<vtkAppendArcLength>::New();
|
||||
m_arclength->SetInputConnection(m_line->GetOutputPort(0));
|
||||
|
||||
|
||||
auto passthrough = vtkSmartPointer<vtkPassThrough>::New();
|
||||
m_probe = vtkSmartPointer<vtkProbeFilter>::New();
|
||||
m_probe->SetSourceConnection(passthrough->GetOutputPort(0));
|
||||
m_probe->SetInputConnection(m_line->GetOutputPort());
|
||||
m_probe->SetValidPointMaskArrayName("ValidPointArray");
|
||||
m_probe->SetInputConnection(m_arclength->GetOutputPort());
|
||||
m_probe->SetPassPointArrays(1);
|
||||
m_probe->SetPassCellArrays(1);
|
||||
m_probe->ComputeToleranceOff();
|
||||
m_probe->SetTolerance(0.01);
|
||||
|
||||
clip.source = passthrough;
|
||||
clip.algorithmStorage.push_back(m_arclength);
|
||||
clip.target = m_probe;
|
||||
|
||||
addFilterPipeline(clip, "DataAlongLine");
|
||||
@@ -488,12 +491,7 @@ void FemPostDataAlongLineFilter::GetAxisData()
|
||||
return;
|
||||
}
|
||||
|
||||
vtkDataArray* tcoords = dset->GetPointData()->GetTCoords("Texture Coordinates");
|
||||
|
||||
const Base::Vector3d& vec1 = Point1.getValue();
|
||||
const Base::Vector3d& vec2 = Point2.getValue();
|
||||
const Base::Vector3d diff = vec1 - vec2;
|
||||
double Len = diff.Length();
|
||||
vtkDataArray* alength = dset->GetPointData()->GetArray("arc_length");
|
||||
|
||||
for (vtkIdType i = 0; i < dset->GetNumberOfPoints(); ++i) {
|
||||
double value = 0;
|
||||
@@ -517,8 +515,7 @@ void FemPostDataAlongLineFilter::GetAxisData()
|
||||
}
|
||||
|
||||
values.push_back(value);
|
||||
double tcoord = tcoords->GetComponent(i, 0);
|
||||
coords.push_back(tcoord * Len);
|
||||
coords.push_back(alength->GetTuple1(i));
|
||||
}
|
||||
|
||||
YAxisData.setValues(values);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <vtkLineSource.h>
|
||||
#include <vtkPointSource.h>
|
||||
#include <vtkProbeFilter.h>
|
||||
#include <vtkAppendArcLength.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkTableBasedClipDataSet.h>
|
||||
#include <vtkVectorNorm.h>
|
||||
@@ -181,6 +182,7 @@ protected:
|
||||
|
||||
private:
|
||||
vtkSmartPointer<vtkLineSource> m_line;
|
||||
vtkSmartPointer<vtkAppendArcLength> m_arclength;
|
||||
vtkSmartPointer<vtkProbeFilter> m_probe;
|
||||
};
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ Note: Can lead to a full recompute of the whole pipeline, hence best to call thi
|
||||
<UserDocu>
|
||||
Returns the names of all scalar fields available on this filter's input.
|
||||
Note: Can lead to a full recompute of the whole pipeline, hence best to call this only in "execute", where the user expects long calculation cycles.
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getOutputAlgorithm">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Returns the filters vtk algorithm currently used as output (the one generating the Data field). Note that the output algorithm may change depending on filter settings.
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>"
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
#include <vtkUnstructuredGrid.h>
|
||||
#include <vtkPythonUtil.h>
|
||||
#include <vtkPolyData.h>
|
||||
#endif // BUILD_FEM_VTK
|
||||
|
||||
using namespace Fem;
|
||||
@@ -129,6 +130,9 @@ PyObject* FemPostFilterPy::getInputData(PyObject* args)
|
||||
case VTK_UNSTRUCTURED_GRID:
|
||||
copy = vtkUnstructuredGrid::New();
|
||||
break;
|
||||
case VTK_POLY_DATA:
|
||||
copy = vtkPolyData::New();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"cannot return datatype object; not unstructured grid");
|
||||
@@ -183,6 +187,25 @@ PyObject* FemPostFilterPy::getInputScalarFields(PyObject* args)
|
||||
return Py::new_reference_to(list);
|
||||
}
|
||||
|
||||
PyObject* FemPostFilterPy::getOutputAlgorithm(PyObject* args)
|
||||
{
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
// we take no arguments
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// return python object for the algorithm
|
||||
auto algorithm = getFemPostFilterPtr()->getFilterOutput();
|
||||
PyObject* py_algorithm = vtkPythonUtil::GetObjectFromPointer(algorithm);
|
||||
|
||||
return Py::new_reference_to(py_algorithm);
|
||||
#else
|
||||
PyErr_SetString(PyExc_NotImplementedError, "VTK python wrapper not available");
|
||||
Py_Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
PyObject* FemPostFilterPy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@@ -23,6 +23,13 @@ filename: str
|
||||
File extension is automatically detected from data type.</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getDataSet">
|
||||
<Documentation>
|
||||
<UserDocu>getDataset() -> vtkDataSet
|
||||
|
||||
Returns the current output dataset. For normal filters this is equal to the objects Data property output. However, a pipelines Data property could store multiple frames, and hence Data can be of type vtkCompositeData, which is not a vtkDataset. To simplify implementations this function always returns a vtkDataSet, and for a pipeline it will be the dataset of the currently selected frame. Note that the returned value could be None, if no data is set at all.</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#include "FemPostObjectPy.h"
|
||||
#include "FemPostObjectPy.cpp"
|
||||
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
#include <vtkDataSet.h>
|
||||
#include <vtkPythonUtil.h>
|
||||
#endif // BUILD_FEM_VTK
|
||||
|
||||
using namespace Fem;
|
||||
|
||||
@@ -55,6 +59,27 @@ PyObject* FemPostObjectPy::writeVTK(PyObject* args)
|
||||
Py_Return;
|
||||
}
|
||||
|
||||
PyObject* FemPostObjectPy::getDataSet(PyObject* args)
|
||||
{
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
// we take no arguments
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// return python object for the dataset
|
||||
auto dataset = getFemPostObjectPtr()->getDataSet();
|
||||
if (dataset) {
|
||||
PyObject* py_algorithm = vtkPythonUtil::GetObjectFromPointer(dataset);
|
||||
return Py::new_reference_to(py_algorithm);
|
||||
}
|
||||
return Py_None;
|
||||
#else
|
||||
PyErr_SetString(PyExc_NotImplementedError, "VTK python wrapper not available");
|
||||
Py_Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
PyObject* FemPostObjectPy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@@ -118,6 +118,12 @@ public:
|
||||
unsigned int getFrameNumber();
|
||||
std::vector<double> getFrameValues();
|
||||
|
||||
// output algorithm handling
|
||||
vtkSmartPointer<vtkAlgorithm> getOutputAlgorithm()
|
||||
{
|
||||
return m_source_algorithm;
|
||||
}
|
||||
|
||||
protected:
|
||||
void onChanged(const App::Property* prop) override;
|
||||
bool allowObject(App::DocumentObject* obj) override;
|
||||
|
||||
@@ -71,5 +71,12 @@ Load a single result object or create a multiframe result by loading multiple re
|
||||
<UserDocu>Change name of data arrays</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getOutputAlgorithm">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
Returns the pipeline vtk algorithm, which generates the data passed to the pipelines filters. Note that the output algorithm may change depending on pipeline settings.
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>"
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#include "FemPostPipelinePy.cpp"
|
||||
// clang-format on
|
||||
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
#include <vtkPythonUtil.h>
|
||||
#endif // BUILD_FEM_VTK
|
||||
|
||||
|
||||
using namespace Fem;
|
||||
|
||||
@@ -313,6 +317,25 @@ PyObject* FemPostPipelinePy::renameArrays(PyObject* args)
|
||||
Py_Return;
|
||||
}
|
||||
|
||||
PyObject* FemPostPipelinePy::getOutputAlgorithm(PyObject* args)
|
||||
{
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
// we take no arguments
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// return python object for the algorithm
|
||||
auto algorithm = getFemPostPipelinePtr()->getOutputAlgorithm();
|
||||
PyObject* py_algorithm = vtkPythonUtil::GetObjectFromPointer(algorithm);
|
||||
|
||||
return Py::new_reference_to(py_algorithm);
|
||||
#else
|
||||
PyErr_SetString(PyExc_NotImplementedError, "VTK python wrapper not available");
|
||||
Py_Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
PyObject* FemPostPipelinePy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
#include <vtkStructuredGrid.h>
|
||||
#include <vtkUniformGrid.h>
|
||||
#include <vtkUnstructuredGrid.h>
|
||||
#include <vtkTable.h>
|
||||
#include <vtkXMLTableWriter.h>
|
||||
#include <vtkXMLDataSetWriter.h>
|
||||
#include <vtkXMLMultiBlockDataWriter.h>
|
||||
#include <vtkXMLTableReader.h>
|
||||
#include <vtkXMLMultiBlockDataReader.h>
|
||||
#include <vtkXMLImageDataReader.h>
|
||||
#include <vtkXMLPolyDataReader.h>
|
||||
@@ -243,6 +246,9 @@ void PropertyPostDataObject::createDataObjectByExternalType(vtkSmartPointer<vtkD
|
||||
case VTK_MULTIPIECE_DATA_SET:
|
||||
m_dataObject = vtkSmartPointer<vtkMultiPieceDataSet>::New();
|
||||
break;
|
||||
case VTK_TABLE:
|
||||
m_dataObject = vtkSmartPointer<vtkTable>::New();
|
||||
break;
|
||||
default:
|
||||
throw Base::TypeError("Unsupported VTK data type");
|
||||
};
|
||||
@@ -313,6 +319,9 @@ void PropertyPostDataObject::Save(Base::Writer& writer) const
|
||||
case VTK_MULTIBLOCK_DATA_SET:
|
||||
extension = "zip";
|
||||
break;
|
||||
case VTK_TABLE:
|
||||
extension = ".vtt";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
@@ -382,13 +391,16 @@ void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const
|
||||
xmlWriter = vtkSmartPointer<vtkXMLMultiBlockDataWriter>::New();
|
||||
xmlWriter->SetInputDataObject(m_dataObject);
|
||||
xmlWriter->SetFileName(datafile.filePath().c_str());
|
||||
xmlWriter->SetDataModeToBinary();
|
||||
}
|
||||
else if (m_dataObject->IsA("vtkTable")) {
|
||||
xmlWriter = vtkSmartPointer<vtkXMLTableWriter>::New();
|
||||
xmlWriter->SetInputDataObject(m_dataObject);
|
||||
xmlWriter->SetFileName(fi.filePath().c_str());
|
||||
}
|
||||
else {
|
||||
xmlWriter = vtkSmartPointer<vtkXMLDataSetWriter>::New();
|
||||
xmlWriter->SetInputDataObject(m_dataObject);
|
||||
xmlWriter->SetFileName(fi.filePath().c_str());
|
||||
xmlWriter->SetDataModeToBinary();
|
||||
|
||||
#ifdef VTK_CELL_ARRAY_V2
|
||||
// Looks like an invalid data object that causes a crash with vtk9
|
||||
@@ -399,6 +411,7 @@ void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const
|
||||
}
|
||||
#endif
|
||||
}
|
||||
xmlWriter->SetDataModeToBinary();
|
||||
|
||||
if (xmlWriter->Write() != 1) {
|
||||
// Note: Do NOT throw an exception here because if the tmp. file could
|
||||
@@ -481,6 +494,9 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader)
|
||||
else if (extension == "vti") {
|
||||
xmlReader = vtkSmartPointer<vtkXMLImageDataReader>::New();
|
||||
}
|
||||
else if (extension == "vtt") {
|
||||
xmlReader = vtkSmartPointer<vtkXMLTableReader>::New();
|
||||
}
|
||||
else if (extension == "zip") {
|
||||
|
||||
// first unzip the file into a datafolder
|
||||
|
||||
@@ -214,9 +214,15 @@ SET(FemObjects_SRCS
|
||||
)
|
||||
|
||||
if(BUILD_FEM_VTK_PYTHON)
|
||||
SET(FemObjects_SRCS
|
||||
${FemObjects_SRCS}
|
||||
list(APPEND FemObjects_SRCS
|
||||
femobjects/base_fempostextractors.py
|
||||
femobjects/base_fempostvisualizations.py
|
||||
femobjects/post_glyphfilter.py
|
||||
femobjects/post_extract1D.py
|
||||
femobjects/post_extract2D.py
|
||||
femobjects/post_histogram.py
|
||||
femobjects/post_lineplot.py
|
||||
femobjects/post_table.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
@@ -597,6 +603,7 @@ SET(FemGuiTaskPanels_SRCS
|
||||
femtaskpanels/__init__.py
|
||||
femtaskpanels/base_femtaskpanel.py
|
||||
femtaskpanels/base_femlogtaskpanel.py
|
||||
femtaskpanels/base_fempostpanel.py
|
||||
femtaskpanels/task_constraint_bodyheatsource.py
|
||||
femtaskpanels/task_constraint_centrif.py
|
||||
femtaskpanels/task_constraint_currentdensity.py
|
||||
@@ -625,9 +632,12 @@ SET(FemGuiTaskPanels_SRCS
|
||||
)
|
||||
|
||||
if(BUILD_FEM_VTK_PYTHON)
|
||||
SET(FemGuiTaskPanels_SRCS
|
||||
${FemGuiTaskPanels_SRCS}
|
||||
list(APPEND FemGuiTaskPanels_SRCS
|
||||
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)
|
||||
|
||||
@@ -641,9 +651,18 @@ SET(FemGuiUtils_SRCS
|
||||
femguiutils/disambiguate_solid_selection.py
|
||||
femguiutils/migrate_gui.py
|
||||
femguiutils/selection_widgets.py
|
||||
femguiutils/vtk_module_handling.py
|
||||
)
|
||||
|
||||
if(BUILD_FEM_VTK_PYTHON)
|
||||
list(APPEND FemGuiUtils_SRCS
|
||||
femguiutils/vtk_module_handling.py
|
||||
femguiutils/vtk_table_view.py
|
||||
femguiutils/data_extraction.py
|
||||
femguiutils/extract_link_view.py
|
||||
femguiutils/post_visualization.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
SET(FemGuiViewProvider_SRCS
|
||||
femviewprovider/__init__.py
|
||||
femviewprovider/view_base_femconstraint.py
|
||||
@@ -683,9 +702,13 @@ SET(FemGuiViewProvider_SRCS
|
||||
)
|
||||
|
||||
if(BUILD_FEM_VTK_PYTHON)
|
||||
SET(FemGuiViewProvider_SRCS
|
||||
${FemGuiViewProvider_SRCS}
|
||||
list(APPEND FemGuiViewProvider_SRCS
|
||||
femviewprovider/view_base_fempostextractors.py
|
||||
femviewprovider/view_base_fempostvisualization.py
|
||||
femviewprovider/view_post_glyphfilter.py
|
||||
femviewprovider/view_post_histogram.py
|
||||
femviewprovider/view_post_lineplot.py
|
||||
femviewprovider/view_post_table.py
|
||||
)
|
||||
endif(BUILD_FEM_VTK_PYTHON)
|
||||
|
||||
|
||||
@@ -291,6 +291,8 @@ if(BUILD_FEM_VTK)
|
||||
SphereWidget.ui
|
||||
TaskPostBoxes.h
|
||||
TaskPostBoxes.cpp
|
||||
TaskPostExtraction.h
|
||||
TaskPostExtraction.cpp
|
||||
TaskPostCalculator.ui
|
||||
TaskPostClip.ui
|
||||
TaskPostContours.ui
|
||||
@@ -440,6 +442,16 @@ SET(FemGuiPythonUI_SRCS
|
||||
Resources/ui/SolverCalculiX.ui
|
||||
Resources/ui/SolverCcxTools.ui
|
||||
Resources/ui/TaskPostGlyph.ui
|
||||
Resources/ui/TaskPostExtraction.ui
|
||||
Resources/ui/TaskPostHistogram.ui
|
||||
Resources/ui/TaskPostLineplot.ui
|
||||
Resources/ui/PostHistogramFieldViewEdit.ui
|
||||
Resources/ui/PostHistogramFieldAppEdit.ui
|
||||
Resources/ui/PostHistogramIndexAppEdit.ui
|
||||
Resources/ui/PostLineplotFieldViewEdit.ui
|
||||
Resources/ui/PostLineplotFieldAppEdit.ui
|
||||
Resources/ui/PostLineplotIndexAppEdit.ui
|
||||
Resources/ui/PostTableFieldViewEdit.ui
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET(FemPythonUi ALL
|
||||
|
||||
@@ -86,6 +86,11 @@
|
||||
<file>icons/FEM_PostFrames.svg</file>
|
||||
<file>icons/FEM_PostBranchFilter.svg</file>
|
||||
<file>icons/FEM_PostPipelineFromResult.svg</file>
|
||||
<file>icons/FEM_PostLineplot.svg</file>
|
||||
<file>icons/FEM_PostHistogram.svg</file>
|
||||
<file>icons/FEM_PostSpreadsheet.svg</file>
|
||||
<file>icons/FEM_PostField.svg</file>
|
||||
<file>icons/FEM_PostIndex.svg</file>
|
||||
<file>icons/FEM_ResultShow.svg</file>
|
||||
<file>icons/FEM_ResultsPurge.svg</file>
|
||||
|
||||
|
||||
102
src/Mod/Fem/Gui/Resources/icons/FEM_PostField.svg
Normal file
102
src/Mod/Fem/Gui/Resources/icons/FEM_PostField.svg
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-table"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="FEM_PostField.svg"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="34.537747"
|
||||
inkscape:cx="8.8598715"
|
||||
inkscape:cy="9.6561018"
|
||||
inkscape:window-width="3132"
|
||||
inkscape:window-height="1772"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.463544;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect5"
|
||||
width="10.440784"
|
||||
height="2.0364563"
|
||||
x="2.1338818"
|
||||
y="-5.0367694"
|
||||
rx="1.9162874"
|
||||
ry="2.0364563"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.463544;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect6"
|
||||
width="10.440784"
|
||||
height="2.0364563"
|
||||
x="2.2207432"
|
||||
y="-13.056973"
|
||||
rx="1.9162874"
|
||||
ry="2.0364563"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.463544;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect4"
|
||||
width="10.440784"
|
||||
height="2.0364563"
|
||||
x="2.7107689"
|
||||
y="11.254348"
|
||||
rx="1.9162874"
|
||||
ry="2.0364563" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.463537;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect3"
|
||||
width="10.440463"
|
||||
height="2.0364628"
|
||||
x="2.5080891"
|
||||
y="2.7419074"
|
||||
rx="1.9162284"
|
||||
ry="2.0364628" />
|
||||
<ellipse
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="path1"
|
||||
cx="4.0924597"
|
||||
cy="3.7761521"
|
||||
rx="2.7138755"
|
||||
ry="2.7138758" />
|
||||
<ellipse
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="circle1"
|
||||
cx="12.092448"
|
||||
cy="3.7761521"
|
||||
rx="2.7138755"
|
||||
ry="2.7138758" />
|
||||
<ellipse
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="circle2"
|
||||
cx="4.0927677"
|
||||
cy="12.27616"
|
||||
rx="2.7138755"
|
||||
ry="2.7138758" />
|
||||
<ellipse
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="circle3"
|
||||
cx="12.092757"
|
||||
cy="12.27616"
|
||||
rx="2.7138755"
|
||||
ry="2.7138758" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
69
src/Mod/Fem/Gui/Resources/icons/FEM_PostHistogram.svg
Normal file
69
src/Mod/Fem/Gui/Resources/icons/FEM_PostHistogram.svg
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-bar-chart"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="FEM_PostHistogram.svg"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="48.84375"
|
||||
inkscape:cx="9.4996801"
|
||||
inkscape:cy="6.1932182"
|
||||
inkscape:window-width="3132"
|
||||
inkscape:window-height="1772"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.461535;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect2"
|
||||
width="3.5384648"
|
||||
height="4.3404469"
|
||||
x="0.67558533"
|
||||
y="9.1421566"
|
||||
ry="0.68956506" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.459332;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect3"
|
||||
width="3.540668"
|
||||
height="8.5446272"
|
||||
x="6.2196369"
|
||||
y="4.9390783"
|
||||
ry="0.62339282" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.459208;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect4"
|
||||
width="3.5407922"
|
||||
height="12.789965"
|
||||
x="11.764729"
|
||||
y="0.69380343"
|
||||
ry="0.64040416" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;fill-opacity:1"
|
||||
id="rect5"
|
||||
width="14.966091"
|
||||
height="1.412668"
|
||||
x="0.53230965"
|
||||
y="14.167626"
|
||||
rx="0.12284084"
|
||||
ry="0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
42
src/Mod/Fem/Gui/Resources/icons/FEM_PostIndex.svg
Normal file
42
src/Mod/Fem/Gui/Resources/icons/FEM_PostIndex.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-table"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="FEM_PostIndex.svg"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="34.537747"
|
||||
inkscape:cx="7.7885798"
|
||||
inkscape:cy="9.6561018"
|
||||
inkscape:window-width="1960"
|
||||
inkscape:window-height="1308"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<circle
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="path1"
|
||||
cx="7.9564848"
|
||||
cy="7.9564848"
|
||||
r="4.2860532" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
46
src/Mod/Fem/Gui/Resources/icons/FEM_PostLineplot.svg
Normal file
46
src/Mod/Fem/Gui/Resources/icons/FEM_PostLineplot.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-graph-up"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="FEM_PostLineplot.svg"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="1.6793786"
|
||||
inkscape:cy="9.534893"
|
||||
inkscape:window-width="3132"
|
||||
inkscape:window-height="1772"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
id="rect1"
|
||||
style="fill:#e5007e;fill-opacity:1;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 0.5234375,0.5390625 V 14.324219 15.548828 H 2.0332031 15.248047 V 14.324219 H 2.0332031 V 0.5390625 Z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:#e5007e;fill-opacity:1;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 2.6295533,11.313708 6.8942911,5.4137863 9.8994949,7.6234949 13.788583,1.8119611 15.490059,2.9831067 10.496116,10.54031 7.2920386,8.1980192 4.0879611,12.484854 Z"
|
||||
id="path8"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
64
src/Mod/Fem/Gui/Resources/icons/FEM_PostSpreadsheet.svg
Normal file
64
src/Mod/Fem/Gui/Resources/icons/FEM_PostSpreadsheet.svg
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-table"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="FEM_PostSpreadsheet.svg"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="48.84375"
|
||||
inkscape:cx="2.9277031"
|
||||
inkscape:cy="8.4248241"
|
||||
inkscape:window-width="3132"
|
||||
inkscape:window-height="1772"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<rect
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.4;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
id="rect2"
|
||||
width="14.571755"
|
||||
height="14.865558"
|
||||
x="0.72790933"
|
||||
y="0.64063662"
|
||||
ry="2.3616853" />
|
||||
<path
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.694259;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="M 1.013422,5.2704734 H 15.11965"
|
||||
id="path2" />
|
||||
<path
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.694244;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="M 0.96706864,8.6996694 H 15.033695"
|
||||
id="path2-8" />
|
||||
<path
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.688579;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="M 5.572664,15.121805 V 5.3933605"
|
||||
id="path2-8-3" />
|
||||
<path
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.690432;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="M 1.0023108,12.128865 H 14.992054"
|
||||
id="path2-8-6" />
|
||||
<path
|
||||
style="fill:#e5007e;stroke:#260013;stroke-width:0.688011;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="M 10.451292,15.119885 V 5.313384"
|
||||
id="path3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
78
src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldAppEdit.ui
Normal file
78
src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldAppEdit.ui
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>317</width>
|
||||
<height>118</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="Field">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="Component">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Frames:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="Extract">
|
||||
<property name="text">
|
||||
<string>One field for each frames</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
167
src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldViewEdit.ui
Normal file
167
src/Mod/Fem/Gui/Resources/ui/PostHistogramFieldViewEdit.ui
Normal file
@@ -0,0 +1,167 @@
|
||||
<?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>278</width>
|
||||
<height>110</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="2">
|
||||
<widget class="QComboBox" name="LineStyle">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Outline draw style (None does not draw outlines)</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QDoubleSpinBox" name="LineWidth">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Width of all lines (outline and hatch)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>99.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="Hatch">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Hatch pattern</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Lines:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QSpinBox" name="HatchDensity">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Density of hatch pattern</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Bars:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="Legend"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Legend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QToolButton" name="LineColor">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Color of all lines (bar outline and hatches)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QToolButton" name="BarColor">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Color of the bars in histogram</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Legend</tabstop>
|
||||
<tabstop>BarColor</tabstop>
|
||||
<tabstop>Hatch</tabstop>
|
||||
<tabstop>HatchDensity</tabstop>
|
||||
<tabstop>LineColor</tabstop>
|
||||
<tabstop>LineStyle</tabstop>
|
||||
<tabstop>LineWidth</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
84
src/Mod/Fem/Gui/Resources/ui/PostHistogramIndexAppEdit.ui
Normal file
84
src/Mod/Fem/Gui/Resources/ui/PostHistogramIndexAppEdit.ui
Normal file
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>261</width>
|
||||
<height>110</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="Field">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="Component">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Index:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="Index">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
101
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui
Normal file
101
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldAppEdit.ui
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>271</width>
|
||||
<height>174</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>X Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="XField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="XComponent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Y Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="YField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="YComponent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Frames:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="Extract">
|
||||
<property name="text">
|
||||
<string>One Y field for each frames</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
151
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui
Normal file
151
src/Mod/Fem/Gui/Resources/ui/PostLineplotFieldViewEdit.ui
Normal file
@@ -0,0 +1,151 @@
|
||||
<?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>274</width>
|
||||
<height>114</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Marker:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="LineStyle">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Hatch pattern</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="4">
|
||||
<widget class="QLineEdit" name="Legend"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Legend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QComboBox" name="MarkerStyle">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Outline draw style (None does not draw outlines)</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Line:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QToolButton" name="Color">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Color of the bars in histogram</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QDoubleSpinBox" name="LineWidth">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>99.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QDoubleSpinBox" name="MarkerSize">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Width of all lines (outline and hatch)</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>99.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Legend</tabstop>
|
||||
<tabstop>Color</tabstop>
|
||||
<tabstop>LineStyle</tabstop>
|
||||
<tabstop>MarkerStyle</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
85
src/Mod/Fem/Gui/Resources/ui/PostLineplotIndexAppEdit.ui
Normal file
85
src/Mod/Fem/Gui/Resources/ui/PostLineplotIndexAppEdit.ui
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>310</width>
|
||||
<height>108</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="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Y Field:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="YField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="YComponent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Index:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="Index">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Index</tabstop>
|
||||
<tabstop>YField</tabstop>
|
||||
<tabstop>YComponent</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
50
src/Mod/Fem/Gui/Resources/ui/PostTableFieldViewEdit.ui
Normal file
50
src/Mod/Fem/Gui/Resources/ui/PostTableFieldViewEdit.ui
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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>279</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">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
54
src/Mod/Fem/Gui/Resources/ui/TaskPostExtraction.ui
Normal file
54
src/Mod/Fem/Gui/Resources/ui/TaskPostExtraction.ui
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPostExtraction</class>
|
||||
<widget class="QWidget" name="TaskPostExtraction">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>515</width>
|
||||
<height>36</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="Summary">
|
||||
<property name="text">
|
||||
<string>Data Summary</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="Data">
|
||||
<property name="text">
|
||||
<string>Show Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
241
src/Mod/Fem/Gui/Resources/ui/TaskPostHistogram.ui
Normal file
241
src/Mod/Fem/Gui/Resources/ui/TaskPostHistogram.ui
Normal file
@@ -0,0 +1,241 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPostGlyph</class>
|
||||
<widget class="QWidget" name="TaskPostGlyph">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>343</width>
|
||||
<height>498</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Glyph settings</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="toolTip">
|
||||
<string>The form of the glyph</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bins</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="Bins">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="toolTip">
|
||||
<string>Which vector field is used to orient the glyphs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="Type">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Which vector field is used to orient the glyphs</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="Cumulative">
|
||||
<property name="text">
|
||||
<string>Cumulative</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Legend</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="LegendShow">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="LegendPos">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="Scale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Labels</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="Title"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Y Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="YLabel"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="toolTip">
|
||||
<string>If the scale data is a vector this property decides if the glyph is scaled by vector magnitude or by the individual components</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>X Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="toolTip">
|
||||
<string>A constant multiplier the glyphs are scaled with</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="XLabel"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Visuals</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QDoubleSpinBox" name="BarWidth">
|
||||
<property name="maximum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.050000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Hatch Line Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Bar width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="HatchWidth"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Bins</tabstop>
|
||||
<tabstop>Type</tabstop>
|
||||
<tabstop>Cumulative</tabstop>
|
||||
<tabstop>LegendShow</tabstop>
|
||||
<tabstop>LegendPos</tabstop>
|
||||
<tabstop>Title</tabstop>
|
||||
<tabstop>XLabel</tabstop>
|
||||
<tabstop>YLabel</tabstop>
|
||||
<tabstop>BarWidth</tabstop>
|
||||
<tabstop>HatchWidth</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
181
src/Mod/Fem/Gui/Resources/ui/TaskPostLineplot.ui
Normal file
181
src/Mod/Fem/Gui/Resources/ui/TaskPostLineplot.ui
Normal file
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPostGlyph</class>
|
||||
<widget class="QWidget" name="TaskPostGlyph">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>302</width>
|
||||
<height>302</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Glyph settings</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="toolTip">
|
||||
<string>The form of the glyph</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Grid</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="Grid">
|
||||
<property name="text">
|
||||
<string>Show</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="LegendShow">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Legend</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="LegendPos">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="toolTip">
|
||||
<string>Which vector field is used to orient the glyphs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scale</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="Scale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Which vector field is used to orient the glyphs</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="Form">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Labels</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="Title"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Y Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="YLabel"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="toolTip">
|
||||
<string>If the scale data is a vector this property decides if the glyph is scaled by vector magnitude or by the individual components</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>X Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="toolTip">
|
||||
<string>A constant multiplier the glyphs are scaled with</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="XLabel"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>Grid</tabstop>
|
||||
<tabstop>LegendShow</tabstop>
|
||||
<tabstop>LegendPos</tabstop>
|
||||
<tabstop>Scale</tabstop>
|
||||
<tabstop>Title</tabstop>
|
||||
<tabstop>XLabel</tabstop>
|
||||
<tabstop>YLabel</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -64,7 +64,6 @@
|
||||
#include "ui_TaskPostFrames.h"
|
||||
#include "ui_TaskPostBranch.h"
|
||||
|
||||
|
||||
#include "FemSettings.h"
|
||||
#include "TaskPostBoxes.h"
|
||||
#include "ViewProviderFemPostFilter.h"
|
||||
@@ -72,7 +71,6 @@
|
||||
#include "ViewProviderFemPostObject.h"
|
||||
#include "ViewProviderFemPostBranchFilter.h"
|
||||
|
||||
|
||||
using namespace FemGui;
|
||||
using namespace Gui;
|
||||
|
||||
@@ -214,9 +212,18 @@ TaskPostWidget::TaskPostWidget(Gui::ViewProviderDocumentObject* view,
|
||||
setWindowTitle(title);
|
||||
setWindowIcon(icon);
|
||||
m_icon = icon;
|
||||
|
||||
m_connection =
|
||||
m_object->signalChanged.connect(boost::bind(&TaskPostWidget::handlePropertyChange,
|
||||
this,
|
||||
boost::placeholders::_1,
|
||||
boost::placeholders::_2));
|
||||
}
|
||||
|
||||
TaskPostWidget::~TaskPostWidget() = default;
|
||||
TaskPostWidget::~TaskPostWidget()
|
||||
{
|
||||
m_connection.disconnect();
|
||||
};
|
||||
|
||||
bool TaskPostWidget::autoApply()
|
||||
{
|
||||
@@ -256,6 +263,14 @@ void TaskPostWidget::updateEnumerationList(App::PropertyEnumeration& prop, QComb
|
||||
box->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void TaskPostWidget::handlePropertyChange(const App::DocumentObject& obj, const App::Property& prop)
|
||||
{
|
||||
if (auto postobj = m_object.get<Fem::FemPostObject>()) {
|
||||
if (&prop == &postobj->Data) {
|
||||
this->onPostDataChanged(postobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// simulation dialog for the TaskView
|
||||
@@ -393,6 +408,24 @@ void TaskDlgPost::modifyStandardButtons(QDialogButtonBox* box)
|
||||
}
|
||||
}
|
||||
|
||||
void TaskDlgPost::processCollapsedWidgets()
|
||||
{
|
||||
|
||||
for (auto& widget : Content) {
|
||||
auto* task_box = dynamic_cast<Gui::TaskView::TaskBox*>(widget);
|
||||
if (!task_box) {
|
||||
continue;
|
||||
}
|
||||
// get the task widget and check if it is a post widget
|
||||
auto* taskwidget = task_box->groupLayout()->itemAt(0)->widget();
|
||||
auto* post_widget = dynamic_cast<TaskPostWidget*>(taskwidget);
|
||||
if (!post_widget || !post_widget->initiallyCollapsed()) {
|
||||
continue;
|
||||
}
|
||||
post_widget->setGeometry(QRect(QPoint(0, 0), post_widget->sizeHint()));
|
||||
task_box->hideGroupBox();
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// box to set the coloring
|
||||
@@ -475,7 +508,6 @@ void TaskPostDisplay::onTransparencyValueChanged(int i)
|
||||
void TaskPostDisplay::applyPythonCode()
|
||||
{}
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// functions
|
||||
TaskPostFunction::TaskPostFunction(ViewProviderFemPostFunction* view, QWidget* parent)
|
||||
@@ -557,6 +589,11 @@ void TaskPostFrames::applyPythonCode()
|
||||
// we apply the views widgets python code
|
||||
}
|
||||
|
||||
bool TaskPostFrames::initiallyCollapsed()
|
||||
{
|
||||
|
||||
return (ui->FrameTable->rowCount() == 0);
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// in the following, the different filters sorted alphabetically
|
||||
|
||||
@@ -42,6 +42,7 @@ class Ui_TaskPostWarpVector;
|
||||
class Ui_TaskPostCut;
|
||||
class Ui_TaskPostFrames;
|
||||
class Ui_TaskPostBranch;
|
||||
class Ui_TaskPostExtraction;
|
||||
|
||||
class SoFontStyle;
|
||||
class SoText2;
|
||||
@@ -155,6 +156,12 @@ public:
|
||||
// executed when the apply button is pressed in the task dialog
|
||||
virtual void apply() {};
|
||||
|
||||
// returns if the widget shall be collapsed when opening the task dialog
|
||||
virtual bool initiallyCollapsed()
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
protected:
|
||||
App::DocumentObject* getObject() const
|
||||
{
|
||||
@@ -187,10 +194,15 @@ protected:
|
||||
|
||||
static void updateEnumerationList(App::PropertyEnumeration&, QComboBox* box);
|
||||
|
||||
// object update handling
|
||||
void handlePropertyChange(const App::DocumentObject&, const App::Property&);
|
||||
virtual void onPostDataChanged(Fem::FemPostObject*) {};
|
||||
|
||||
private:
|
||||
QPixmap m_icon;
|
||||
App::DocumentObjectWeakPtrT m_object;
|
||||
Gui::ViewProviderWeakPtrT m_view;
|
||||
boost::signals2::connection m_connection;
|
||||
};
|
||||
|
||||
|
||||
@@ -229,6 +241,9 @@ public:
|
||||
/// returns for Close and Help button
|
||||
QDialogButtonBox::StandardButtons getStandardButtons() const override;
|
||||
|
||||
/// makes sure all widgets are collapsed, if they want to be
|
||||
void processCollapsedWidgets();
|
||||
|
||||
protected:
|
||||
void recompute();
|
||||
|
||||
@@ -267,7 +282,6 @@ private:
|
||||
std::unique_ptr<Ui_TaskPostDisplay> ui;
|
||||
};
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// functions
|
||||
class ViewProviderFemPostFunction;
|
||||
@@ -295,6 +309,8 @@ public:
|
||||
|
||||
void applyPythonCode() override;
|
||||
|
||||
bool initiallyCollapsed() override;
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void onSelectionChanged();
|
||||
|
||||
181
src/Mod/Fem/Gui/TaskPostExtraction.cpp
Normal file
181
src/Mod/Fem/Gui/TaskPostExtraction.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2015 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library 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 library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
|
||||
#endif
|
||||
|
||||
#include <Gui/PythonWrapper.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Mod/Fem/App/FemPostFilter.h>
|
||||
#include <Mod/Fem/App/FemPostPipeline.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QTableView>
|
||||
#include <QHeaderView>
|
||||
#include <QDialog>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "ViewProviderFemPostObject.h"
|
||||
#include "TaskPostExtraction.h"
|
||||
|
||||
using namespace FemGui;
|
||||
using namespace Gui;
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// box to handle data extractions
|
||||
|
||||
TaskPostExtraction::TaskPostExtraction(ViewProviderFemPostObject* view, QWidget* parent)
|
||||
: TaskPostWidget(view, Gui::BitmapFactory().pixmap("FEM_PostHistogram"), QString(), parent)
|
||||
{
|
||||
// we load the python implementation, and try to get the widget from it, to add
|
||||
// directly our widget
|
||||
|
||||
setWindowTitle(tr("Data and extractions"));
|
||||
|
||||
Base::PyGILStateLocker lock;
|
||||
|
||||
|
||||
try {
|
||||
Py::Module mod(PyImport_ImportModule("femguiutils.data_extraction"), true);
|
||||
if (mod.isNull()) {
|
||||
Base::Console().error("Unable to import data extraction widget\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Py::Callable method(mod.getAttr(std::string("DataExtraction")));
|
||||
Py::Tuple args(1);
|
||||
args.setItem(0, Py::Object(view->getPyObject()));
|
||||
m_panel = Py::Object(method.apply(args));
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
}
|
||||
|
||||
if (m_panel.hasAttr(std::string("widget"))) {
|
||||
Py::Object pywidget(m_panel.getAttr(std::string("widget")));
|
||||
|
||||
Gui::PythonWrapper wrap;
|
||||
if (wrap.loadCoreModule()) {
|
||||
if (auto* widget = qobject_cast<QWidget*>(wrap.toQObject(pywidget))) {
|
||||
// finally we have the usable QWidget. Add to us!
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->addWidget(widget);
|
||||
setLayout(layout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we are here something went wrong!
|
||||
Base::Console().error("Unable to import data extraction widget\n");
|
||||
};
|
||||
|
||||
TaskPostExtraction::~TaskPostExtraction()
|
||||
{
|
||||
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (m_panel.hasAttr(std::string("widget"))) {
|
||||
m_panel.setAttr(std::string("widget"), Py::None());
|
||||
}
|
||||
m_panel = Py::None();
|
||||
}
|
||||
catch (Py::AttributeError& e) {
|
||||
e.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskPostExtraction::onPostDataChanged(Fem::FemPostObject* obj)
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (m_panel.hasAttr(std::string("onPostDataChanged"))) {
|
||||
Py::Callable method(m_panel.getAttr(std::string("onPostDataChanged")));
|
||||
Py::Tuple args(1);
|
||||
args.setItem(0, Py::Object(obj->getPyObject()));
|
||||
method.apply(args);
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
}
|
||||
};
|
||||
|
||||
bool TaskPostExtraction::isGuiTaskOnly()
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (m_panel.hasAttr(std::string("isGuiTaskOnly"))) {
|
||||
Py::Callable method(m_panel.getAttr(std::string("isGuiTaskOnly")));
|
||||
auto result = Py::Boolean(method.apply());
|
||||
return result.as_bool();
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
void TaskPostExtraction::apply()
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (m_panel.hasAttr(std::string("apply"))) {
|
||||
Py::Callable method(m_panel.getAttr(std::string("apply")));
|
||||
method.apply();
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskPostExtraction::initiallyCollapsed()
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
try {
|
||||
if (m_panel.hasAttr(std::string("initiallyCollapsed"))) {
|
||||
Py::Callable method(m_panel.getAttr(std::string("initiallyCollapsed")));
|
||||
auto result = Py::Boolean(method.apply());
|
||||
return result.as_bool();
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e; // extract the Python error text
|
||||
e.reportException();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "moc_TaskPostExtraction.cpp"
|
||||
68
src/Mod/Fem/Gui/TaskPostExtraction.h
Normal file
68
src/Mod/Fem/Gui/TaskPostExtraction.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library 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 library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef GUI_TASKVIEW_TaskPostExtraction_H
|
||||
#define GUI_TASKVIEW_TaskPostExtraction_H
|
||||
|
||||
#include <Gui/DocumentObserver.h>
|
||||
#include <Gui/TaskView/TaskDialog.h>
|
||||
#include <Gui/TaskView/TaskView.h>
|
||||
#include <Gui/ViewProviderDocumentObject.h>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include "TaskPostBoxes.h"
|
||||
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkTableAlgorithm.h>
|
||||
|
||||
class Ui_TaskPostExtraction;
|
||||
|
||||
|
||||
namespace FemGui
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// box to handle data extractions: It is implemented in python, the c++
|
||||
// code is used to access it and manage it for the c++ task panels
|
||||
class TaskPostExtraction: public TaskPostWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TaskPostExtraction(ViewProviderFemPostObject* view, QWidget* parent = nullptr);
|
||||
~TaskPostExtraction();
|
||||
|
||||
protected:
|
||||
bool isGuiTaskOnly() override;
|
||||
void apply() override;
|
||||
void onPostDataChanged(Fem::FemPostObject* obj) override;
|
||||
bool initiallyCollapsed() override;
|
||||
|
||||
private:
|
||||
Py::Object m_panel;
|
||||
};
|
||||
|
||||
|
||||
} // namespace FemGui
|
||||
|
||||
#endif // GUI_TASKVIEW_TaskPostExtraction_H
|
||||
135
src/Mod/Fem/Gui/TaskPostExtraction.ui
Normal file
135
src/Mod/Fem/Gui/TaskPostExtraction.ui
Normal file
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPostExtraction</class>
|
||||
<widget class="QWidget" name="TaskPostExtraction">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>375</width>
|
||||
<height>302</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="Summary">
|
||||
<property name="text">
|
||||
<string>Data Summary</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="Data">
|
||||
<property name="text">
|
||||
<string>Show Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Data used in:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignBottom|Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="AddBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Add data to</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="CreateBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Create and add</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="ExtractionArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="ExtractionContent">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>359</width>
|
||||
<height>188</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -32,6 +32,9 @@
|
||||
#include "ViewProviderFemPostFilter.h"
|
||||
#include "ViewProviderFemPostFilterPy.h"
|
||||
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
#include "TaskPostExtraction.h"
|
||||
#endif
|
||||
|
||||
using namespace FemGui;
|
||||
|
||||
@@ -89,6 +92,12 @@ void ViewProviderFemPostDataAlongLine::setupTaskDialog(TaskDlgPost* dlg)
|
||||
assert(dlg->getView() == this);
|
||||
auto panel = new TaskPostDataAlongLine(this);
|
||||
dlg->addTaskBox(panel->getIcon(), panel);
|
||||
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
// and the extraction
|
||||
auto extr_panel = new TaskPostExtraction(this);
|
||||
dlg->addTaskBox(extr_panel->windowIcon().pixmap(32), extr_panel);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +147,12 @@ void ViewProviderFemPostDataAtPoint::setupTaskDialog(TaskDlgPost* dlg)
|
||||
assert(dlg->getView() == this);
|
||||
auto panel = new TaskPostDataAtPoint(this);
|
||||
dlg->addTaskBox(panel->getIcon(), panel);
|
||||
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
// and the extraction
|
||||
auto extr_panel = new TaskPostExtraction(this);
|
||||
dlg->addTaskBox(extr_panel->windowIcon().pixmap(32), extr_panel);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,5 +20,10 @@
|
||||
<UserDocu>Returns the display option task panel for a post processing edit task dialog.</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="createExtractionTaskWidget">
|
||||
<Documentation>
|
||||
<UserDocu>Returns the data extraction task panel for a post processing edit task dialog.</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include <Gui/PythonWrapper.h>
|
||||
#include "ViewProviderFemPostFilter.h"
|
||||
#include "TaskPostBoxes.h"
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
#include "TaskPostExtraction.h"
|
||||
#endif
|
||||
// inclusion of the generated files (generated out of ViewProviderFemPostFilterPy.xml)
|
||||
#include "ViewProviderFemPostFilterPy.h"
|
||||
#include "ViewProviderFemPostFilterPy.cpp"
|
||||
@@ -60,6 +63,29 @@ PyObject* ViewProviderFemPostFilterPy::createDisplayTaskWidget(PyObject* args)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject* ViewProviderFemPostFilterPy::createExtractionTaskWidget(PyObject* args)
|
||||
{
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
// we take no arguments
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto panel = new TaskPostExtraction(getViewProviderFemPostObjectPtr());
|
||||
|
||||
Gui::PythonWrapper wrap;
|
||||
if (wrap.loadCoreModule()) {
|
||||
return Py::new_reference_to(wrap.fromQWidget(panel));
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_TypeError, "creating the panel failed");
|
||||
return nullptr;
|
||||
#else
|
||||
PyErr_SetString(PyExc_NotImplementedError, "VTK python wrapper not available");
|
||||
Py_Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
PyObject* ViewProviderFemPostFilterPy::getCustomAttributes(const char* /*attr*/) const
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
#include <Mod/Fem/App/FemPostFilter.h>
|
||||
|
||||
#include "TaskPostBoxes.h"
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
#include "TaskPostExtraction.h"
|
||||
#endif
|
||||
#include "ViewProviderAnalysis.h"
|
||||
#include "ViewProviderFemPostObject.h"
|
||||
|
||||
@@ -1006,6 +1009,7 @@ bool ViewProviderFemPostObject::setEdit(int ModNum)
|
||||
postDlg = new TaskDlgPost(this);
|
||||
setupTaskDialog(postDlg);
|
||||
postDlg->connectSlots();
|
||||
postDlg->processCollapsedWidgets();
|
||||
Gui::Control().showDialog(postDlg);
|
||||
}
|
||||
|
||||
@@ -1019,8 +1023,13 @@ bool ViewProviderFemPostObject::setEdit(int ModNum)
|
||||
void ViewProviderFemPostObject::setupTaskDialog(TaskDlgPost* dlg)
|
||||
{
|
||||
assert(dlg->getView() == this);
|
||||
auto panel = new TaskPostDisplay(this);
|
||||
dlg->addTaskBox(panel->windowIcon().pixmap(32), panel);
|
||||
auto dispPanel = new TaskPostDisplay(this);
|
||||
dlg->addTaskBox(dispPanel->windowIcon().pixmap(32), dispPanel);
|
||||
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
auto extrPanel = new TaskPostExtraction(this);
|
||||
dlg->addTaskBox(extrPanel->windowIcon().pixmap(32), extrPanel);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ViewProviderFemPostObject::unsetEdit(int ModNum)
|
||||
|
||||
@@ -214,7 +214,11 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
|
||||
<< "FEM_PostFilterDataAtPoint"
|
||||
<< "FEM_PostFilterCalculator"
|
||||
<< "Separator"
|
||||
<< "FEM_PostCreateFunctions";
|
||||
<< "FEM_PostCreateFunctions"
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
<< "FEM_PostVisualization"
|
||||
#endif
|
||||
;
|
||||
#endif
|
||||
|
||||
Gui::ToolBarItem* utils = new Gui::ToolBarItem(root);
|
||||
@@ -366,7 +370,11 @@ Gui::MenuItem* Workbench::setupMenuBar() const
|
||||
<< "FEM_PostFilterDataAtPoint"
|
||||
<< "FEM_PostFilterCalculator"
|
||||
<< "Separator"
|
||||
<< "FEM_PostCreateFunctions";
|
||||
<< "FEM_PostCreateFunctions"
|
||||
#ifdef FC_USE_VTK_PYTHON
|
||||
<< "FEM_PostVisualization"
|
||||
#endif
|
||||
;
|
||||
#endif
|
||||
|
||||
Gui::MenuItem* utils = new Gui::MenuItem;
|
||||
|
||||
@@ -81,9 +81,10 @@ class FemWorkbench(Workbench):
|
||||
False if femcommands.commands.__name__ else True
|
||||
|
||||
# check vtk version to potentially find missmatchs
|
||||
from femguiutils.vtk_module_handling import vtk_module_handling
|
||||
if "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__:
|
||||
from femguiutils.vtk_module_handling import vtk_module_handling
|
||||
|
||||
vtk_module_handling()
|
||||
vtk_module_handling()
|
||||
|
||||
def GetClassName(self):
|
||||
# see https://forum.freecad.org/viewtopic.php?f=10&t=43300
|
||||
|
||||
@@ -686,6 +686,141 @@ def makePostVtkResult(doc, result_data, name="VtkResult"):
|
||||
return obj
|
||||
|
||||
|
||||
def makePostLineplot(doc, name="Lineplot"):
|
||||
"""makePostLineplot(document, [name]):
|
||||
creates a FEM post processing line plot
|
||||
"""
|
||||
obj = doc.addObject("Fem::FeaturePython", name)
|
||||
from femobjects import post_lineplot
|
||||
|
||||
post_lineplot.PostLineplot(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_lineplot
|
||||
|
||||
view_post_lineplot.VPPostLineplot(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostLineplotFieldData(doc, name="FieldData2D"):
|
||||
"""makePostLineplotFieldData(document, [name]):
|
||||
creates a FEM post processing data extractor for 2D Field data
|
||||
"""
|
||||
obj = doc.addObject("Fem::FeaturePython", name)
|
||||
from femobjects import post_lineplot
|
||||
|
||||
post_lineplot.PostLineplotFieldData(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_lineplot
|
||||
|
||||
view_post_lineplot.VPPostLineplotFieldData(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostLineplotIndexOverFrames(doc, name="IndexOverFrames2D"):
|
||||
"""makePostLineplotIndexOverFrames(document, [name]):
|
||||
creates a FEM post processing data extractor for 2D index data
|
||||
"""
|
||||
obj = doc.addObject("Fem::FeaturePython", name)
|
||||
from femobjects import post_lineplot
|
||||
|
||||
post_lineplot.PostLineplotIndexOverFrames(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_lineplot
|
||||
|
||||
view_post_lineplot.VPPostLineplotIndexOverFrames(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostHistogram(doc, name="Histogram"):
|
||||
"""makePostHistogram(document, [name]):
|
||||
creates a FEM post processing histogram plot
|
||||
"""
|
||||
obj = doc.addObject("Fem::FeaturePython", name)
|
||||
from femobjects import post_histogram
|
||||
|
||||
post_histogram.PostHistogram(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_histogram
|
||||
|
||||
view_post_histogram.VPPostHistogram(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostHistogramFieldData(doc, name="FieldData1D"):
|
||||
"""makePostHistogramFieldData(document, [name]):
|
||||
creates a FEM post processing data extractor for 1D Field data
|
||||
"""
|
||||
obj = doc.addObject("Fem::FeaturePython", name)
|
||||
from femobjects import post_histogram
|
||||
|
||||
post_histogram.PostHistogramFieldData(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_histogram
|
||||
|
||||
view_post_histogram.VPPostHistogramFieldData(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostHistogramIndexOverFrames(doc, name="IndexOverFrames1D"):
|
||||
"""makePostHistogramIndexOverFrames(document, [name]):
|
||||
creates a FEM post processing data extractor for 1D Field data
|
||||
"""
|
||||
obj = doc.addObject("Fem::FeaturePython", name)
|
||||
from femobjects import post_histogram
|
||||
|
||||
post_histogram.PostHistogramIndexOverFrames(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_post_histogram
|
||||
|
||||
view_post_histogram.VPPostHistogramIndexOverFrames(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
def makePostTable(doc, name="Table"):
|
||||
"""makePostTable(document, [name]):
|
||||
creates a FEM post processing histogram plot
|
||||
"""
|
||||
obj = doc.addObject("Fem::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("Fem::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("Fem::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]):
|
||||
|
||||
@@ -1289,3 +1289,12 @@ FreeCADGui.addCommand("FEM_SolverZ88", _SolverZ88())
|
||||
|
||||
if "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__:
|
||||
FreeCADGui.addCommand("FEM_PostFilterGlyph", _PostFilterGlyph())
|
||||
|
||||
# setup all visualization commands (register by importing)
|
||||
import femobjects.post_lineplot
|
||||
import femobjects.post_histogram
|
||||
import femobjects.post_table
|
||||
|
||||
from femguiutils import post_visualization
|
||||
|
||||
post_visualization.setup_commands("FEM_PostVisualization")
|
||||
|
||||
@@ -34,7 +34,6 @@ import FreeCAD
|
||||
|
||||
from femtools.femutils import expandParentObject
|
||||
from femtools.femutils import is_of_type
|
||||
from femguiutils.vtk_module_handling import vtk_compatibility_abort
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
from PySide import QtCore
|
||||
@@ -381,6 +380,8 @@ class CommandManager:
|
||||
# and the selobj is expanded in the tree to see the added obj
|
||||
|
||||
# check if we should use python filter
|
||||
from femguiutils.vtk_module_handling import vtk_compatibility_abort
|
||||
|
||||
if vtk_compatibility_abort(True):
|
||||
return
|
||||
|
||||
|
||||
163
src/Mod/Fem/femguiutils/data_extraction.py
Normal file
163
src/Mod/Fem/femguiutils/data_extraction.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM postprocessing ldata view and extraction widget"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package data_extraction
|
||||
# \ingroup FEM
|
||||
# \brief A widget for data extraction. Used in the PostObject task panel.
|
||||
|
||||
from . import vtk_table_view
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkVersion
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
from vtkmodules.vtkFiltersGeneral import vtkSplitColumnComponents
|
||||
|
||||
if vtkVersion.GetVTKMajorVersion() > 9 and vtkVersion.GetVTKMinorVersion() > 3:
|
||||
from vtkmodules.vtkFiltersCore import vtkAttributeDataToTableFilter
|
||||
else:
|
||||
from vtkmodules.vtkInfovisCore import vtkDataObjectToTable
|
||||
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import femobjects.base_fempostextractors as extr
|
||||
from femtaskpanels.base_fempostpanel import _BasePostTaskPanel
|
||||
|
||||
from . import extract_link_view
|
||||
|
||||
ExtractLinkView = extract_link_view.ExtractLinkView
|
||||
|
||||
|
||||
class DataExtraction(_BasePostTaskPanel):
|
||||
# The class is not a widget itself, but provides a widget. It implements
|
||||
# all required callbacks for the widget and the task dialog.
|
||||
# Note: This object is created and used from c++! See PostTaskExtraction
|
||||
|
||||
def __init__(self, vobj):
|
||||
|
||||
super().__init__(vobj.Object)
|
||||
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
|
||||
self.widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostExtraction.ui"
|
||||
)
|
||||
|
||||
# connect all signals as required
|
||||
self.widget.Data.clicked.connect(self.showData)
|
||||
self.widget.Summary.clicked.connect(self.showSummary)
|
||||
|
||||
# setup the data models
|
||||
self.data_model = vtk_table_view.VtkTableModel()
|
||||
self.summary_model = vtk_table_view.VtkTableSummaryModel()
|
||||
|
||||
# generate the data
|
||||
self.onPostDataChanged(self.Object)
|
||||
|
||||
# setup the extraction widget
|
||||
self._extraction_view = ExtractLinkView(self.Object, True, self)
|
||||
self.widget.layout().addSpacing(self.widget.Data.size().height() / 3)
|
||||
self.widget.layout().addWidget(self._extraction_view)
|
||||
self._extraction_view.repopulate()
|
||||
|
||||
@QtCore.Slot()
|
||||
def showData(self):
|
||||
|
||||
dialog = QtGui.QDialog(self.widget)
|
||||
dialog.setWindowTitle(f"Data of {self.Object.Label}")
|
||||
widget = vtk_table_view.VtkTableView(self.data_model)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(widget)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
dialog.setLayout(layout)
|
||||
dialog.resize(1500, 900)
|
||||
dialog.show()
|
||||
|
||||
@QtCore.Slot()
|
||||
def showSummary(self):
|
||||
|
||||
dialog = QtGui.QDialog(self.widget)
|
||||
dialog.setWindowTitle(f"Data summary of {self.Object.Label}")
|
||||
widget = vtk_table_view.VtkTableView(self.summary_model)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(widget)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
dialog.setLayout(layout)
|
||||
dialog.resize(600, 900)
|
||||
dialog.show()
|
||||
|
||||
def isGuiTaskOnly(self):
|
||||
# If all panels return true it omits the Apply button in the dialog
|
||||
return True
|
||||
|
||||
def onPostDataChanged(self, obj):
|
||||
|
||||
algo = obj.getOutputAlgorithm()
|
||||
if not algo:
|
||||
self.data_model.setTable(vtkTable())
|
||||
|
||||
if vtkVersion.GetVTKMajorVersion() > 9 and vtkVersion.GetVTKMinorVersion() > 3:
|
||||
filter = vtkAttributeDataToTableFilter()
|
||||
else:
|
||||
filter = vtkDataObjectToTable()
|
||||
filter.SetFieldType(vtkDataObjectToTable.POINT_DATA)
|
||||
|
||||
filter.SetInputConnection(0, algo.GetOutputPort(0))
|
||||
filter.Update()
|
||||
table = filter.GetOutputDataObject(0)
|
||||
|
||||
# add the points
|
||||
points = algo.GetOutputDataObject(0).GetPoints().GetData()
|
||||
table.AddColumn(points)
|
||||
|
||||
# split the components
|
||||
splitter = vtkSplitColumnComponents()
|
||||
splitter.SetNamingModeToNamesWithParens()
|
||||
splitter.SetInputData(0, table)
|
||||
|
||||
splitter.Update()
|
||||
table = splitter.GetOutputDataObject(0)
|
||||
|
||||
self.data_model.setTable(table)
|
||||
self.summary_model.setTable(table)
|
||||
|
||||
def apply(self):
|
||||
pass
|
||||
|
||||
def initiallyCollapsed(self):
|
||||
# if we do not have any extractions to show we hide initially to remove clutter
|
||||
|
||||
for obj in self.Object.InList:
|
||||
if extr.is_extractor_object(obj):
|
||||
return False
|
||||
|
||||
return True
|
||||
715
src/Mod/Fem/femguiutils/extract_link_view.py
Normal file
715
src/Mod/Fem/femguiutils/extract_link_view.py
Normal file
@@ -0,0 +1,715 @@
|
||||
# ***************************************************************************
|
||||
# * 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 view for summarizing extractor links"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package data_extraction
|
||||
# \ingroup FEM
|
||||
# \brief A widget that shows summaries of all available links to extractors
|
||||
|
||||
from PySide import QtGui, QtCore
|
||||
|
||||
import femobjects.base_fempostextractors as extr
|
||||
import femobjects.base_fempostvisualizations as vis
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from . import post_visualization as pv
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
# a model showing available visualizations and possible extractions
|
||||
# #################################################################
|
||||
|
||||
|
||||
def build_new_visualization_tree_model():
|
||||
# model that shows all options to create new visualizations
|
||||
|
||||
model = QtGui.QStandardItemModel()
|
||||
|
||||
visualizations = pv.get_registered_visualizations()
|
||||
for vis_name in visualizations:
|
||||
vis_icon = FreeCADGui.getIcon(visualizations[vis_name].icon)
|
||||
vis_item = QtGui.QStandardItem(vis_icon, translate("FEM", "New {}").format(vis_name))
|
||||
vis_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
vis_item.setData(visualizations[vis_name])
|
||||
|
||||
for ext in visualizations[vis_name].extractions:
|
||||
icon = FreeCADGui.getIcon(ext.icon)
|
||||
name = ext.name.removeprefix(vis_name)
|
||||
ext_item = QtGui.QStandardItem(icon, translate("FEM", "with {}").format(name))
|
||||
ext_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
ext_item.setData(ext)
|
||||
vis_item.appendRow(ext_item)
|
||||
model.appendRow(vis_item)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def build_add_to_visualization_tree_model():
|
||||
# model that shows all possible visualization objects to add data to
|
||||
|
||||
visualizations = pv.get_registered_visualizations()
|
||||
model = QtGui.QStandardItemModel()
|
||||
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if obj.isDerivedFrom("Fem::FemAnalysis"):
|
||||
ana_item = QtGui.QStandardItem(obj.ViewObject.Icon, obj.Label)
|
||||
ana_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
|
||||
# check all children it it is a visualization
|
||||
for child in obj.Group:
|
||||
if vis.is_visualization_object(child):
|
||||
|
||||
vis_item = QtGui.QStandardItem(child.ViewObject.Icon, child.Label)
|
||||
vis_type = vis.get_visualization_type(child)
|
||||
vis_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
vis_item.setData(child)
|
||||
ana_item.appendRow(vis_item)
|
||||
|
||||
# add extractor items
|
||||
for ext in visualizations[vis_type].extractions:
|
||||
icon = FreeCADGui.getIcon(ext.icon)
|
||||
name = ext.name.removeprefix(vis_type)
|
||||
ext_item = QtGui.QStandardItem(
|
||||
icon, translate("FEM", "Add {}").format(name)
|
||||
)
|
||||
ext_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
ext_item.setData(ext)
|
||||
vis_item.appendRow(ext_item)
|
||||
|
||||
if ana_item.rowCount():
|
||||
model.appendRow(ana_item)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def build_post_object_item(post_object, extractions, vis_type):
|
||||
|
||||
# definitely build a item and add the extractions
|
||||
post_item = QtGui.QStandardItem(
|
||||
post_object.ViewObject.Icon, translate("FEM", "From {}").format(post_object.Label)
|
||||
)
|
||||
post_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
post_item.setData(post_object)
|
||||
|
||||
# add extractor items
|
||||
for ext in extractions:
|
||||
icon = FreeCADGui.getIcon(ext.icon)
|
||||
name = ext.name.removeprefix(vis_type)
|
||||
ext_item = QtGui.QStandardItem(icon, translate("FEM", "add {}").format(name))
|
||||
ext_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
ext_item.setData(ext)
|
||||
post_item.appendRow(ext_item)
|
||||
|
||||
# if we are a post group, we need to add the children
|
||||
if post_object.hasExtension("Fem::FemPostGroupExtension"):
|
||||
|
||||
for child in post_object.Group:
|
||||
if child.isDerivedFrom("Fem::FemPostObject"):
|
||||
item = build_post_object_item(child, extractions, vis_type)
|
||||
post_item.appendRow(item)
|
||||
|
||||
return post_item
|
||||
|
||||
|
||||
def build_add_from_data_tree_model(vis_type):
|
||||
# model that shows all Post data objects from which data can be extracted
|
||||
extractions = pv.get_registered_visualizations()[vis_type].extractions
|
||||
|
||||
model = QtGui.QStandardItemModel()
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if obj.isDerivedFrom("Fem::FemAnalysis"):
|
||||
ana_item = QtGui.QStandardItem(obj.ViewObject.Icon, obj.Label)
|
||||
ana_item.setFlags(QtGui.Qt.ItemIsEnabled)
|
||||
|
||||
# check all children if it is a post object
|
||||
for child in obj.Group:
|
||||
if child.isDerivedFrom("Fem::FemPostObject"):
|
||||
item = build_post_object_item(child, extractions, vis_type)
|
||||
ana_item.appendRow(item)
|
||||
|
||||
if ana_item.rowCount():
|
||||
model.appendRow(ana_item)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
# implementation of GUI and its functionality
|
||||
# ###########################################
|
||||
|
||||
|
||||
class _ElideToolButton(QtGui.QToolButton):
|
||||
# tool button that elides its text, and left align icon and text
|
||||
|
||||
def __init__(self, icon, text, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self._text = text
|
||||
self._icon = icon
|
||||
|
||||
def setCustomText(self, text):
|
||||
self._text = text
|
||||
self.repaint()
|
||||
|
||||
def setCustomIcon(self, icon):
|
||||
self._icon = icon
|
||||
self.repaint()
|
||||
|
||||
def sizeHint(self):
|
||||
button_size = super().sizeHint()
|
||||
icn_size = self.iconSize()
|
||||
min_margin = max((button_size - icn_size).height(), 6)
|
||||
return QtCore.QSize(self.iconSize().width() + 10, icn_size.height() + min_margin)
|
||||
|
||||
def paintEvent(self, event):
|
||||
|
||||
# draw notmal button, without text and icon
|
||||
super().paintEvent(event)
|
||||
|
||||
# add icon and elided text
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
|
||||
painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
|
||||
|
||||
margin = (self.height() - self.iconSize().height()) / 2
|
||||
icn_width = self.iconSize().width()
|
||||
if self._icon.isNull():
|
||||
icn_width = 0
|
||||
|
||||
fm = self.fontMetrics()
|
||||
txt_size = self.width() - icn_width - 2 * margin
|
||||
if not self._icon.isNull():
|
||||
# we add the margin between icon and text
|
||||
txt_size -= margin
|
||||
|
||||
txt_min = fm.boundingRect("...").width()
|
||||
|
||||
# should we center the icon?
|
||||
xpos = margin
|
||||
if not self._icon.isNull() and txt_size < txt_min:
|
||||
# center icon
|
||||
xpos = self.width() / 2 - self.iconSize().width() / 2
|
||||
|
||||
if not self._icon.isNull():
|
||||
match type(self._icon):
|
||||
case QtGui.QPixmap:
|
||||
painter.drawPixmap(xpos, margin, self._icon.scaled(self.iconSize()))
|
||||
xpos += self.iconSize().width()
|
||||
case QtGui.QIcon:
|
||||
self._icon.paint(
|
||||
painter, QtCore.QRect(QtCore.QPoint(margin, margin), self.iconSize())
|
||||
)
|
||||
xpos += self.iconSize().width()
|
||||
|
||||
xpos += margin # the margin to the text
|
||||
|
||||
if txt_size >= txt_min:
|
||||
text = fm.elidedText(self._text, QtGui.Qt.ElideMiddle, txt_size)
|
||||
painter.drawText(xpos, margin + fm.ascent(), text)
|
||||
|
||||
painter.end()
|
||||
|
||||
|
||||
class _TreeChoiceButton(QtGui.QToolButton):
|
||||
|
||||
selection = QtCore.Signal(object, object)
|
||||
|
||||
def __init__(self, model):
|
||||
super().__init__()
|
||||
|
||||
self.model = model
|
||||
self.setEnabled(bool(model.rowCount()))
|
||||
|
||||
self.__skip_next_hide = False
|
||||
|
||||
self.tree_view = QtGui.QTreeView(self)
|
||||
self.tree_view.setModel(model)
|
||||
|
||||
self.tree_view.setFrameShape(QtGui.QFrame.NoFrame)
|
||||
self.tree_view.setHeaderHidden(True)
|
||||
self.tree_view.setSelectionBehavior(QtGui.QTreeView.SelectionBehavior.SelectRows)
|
||||
self.tree_view.expandAll()
|
||||
self.tree_view.clicked.connect(self.selectIndex)
|
||||
|
||||
style = self.style()
|
||||
if not style.styleHint(QtGui.QStyle.SH_ItemView_ActivateItemOnSingleClick):
|
||||
self.tree_view.activated.connect(self.selectIndex)
|
||||
|
||||
# set a complex menu
|
||||
self.popup = QtGui.QWidgetAction(self)
|
||||
self.popup.setDefaultWidget(self.tree_view)
|
||||
self.setPopupMode(QtGui.QToolButton.InstantPopup)
|
||||
self.addAction(self.popup)
|
||||
|
||||
QtCore.Slot(QtCore.QModelIndex)
|
||||
|
||||
def selectIndex(self, index):
|
||||
item = self.model.itemFromIndex(index)
|
||||
|
||||
if item and not item.hasChildren():
|
||||
extraction = item.data()
|
||||
parent = item.parent().data()
|
||||
self.selection.emit(parent, extraction)
|
||||
self.popup.trigger()
|
||||
|
||||
def setModel(self, model):
|
||||
self.model = model
|
||||
self.tree_view.setModel(model)
|
||||
self.tree_view.expandAll()
|
||||
|
||||
# check if we should be disabled
|
||||
self.setEnabled(bool(model.rowCount()))
|
||||
|
||||
|
||||
class _SettingsPopup(QtGui.QMenu):
|
||||
|
||||
close = QtCore.Signal()
|
||||
|
||||
def __init__(self, setting, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self._setting = setting
|
||||
self.setWindowFlags(QtGui.Qt.Popup)
|
||||
self.setFocusPolicy(QtGui.Qt.ClickFocus)
|
||||
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.addWidget(setting)
|
||||
|
||||
buttonBox = QtGui.QDialogButtonBox()
|
||||
buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
|
||||
buttonBox.accepted.connect(self.hide)
|
||||
vbox.addWidget(buttonBox)
|
||||
|
||||
widget = QtGui.QFrame()
|
||||
widget.setLayout(vbox)
|
||||
|
||||
vbox2 = QtGui.QVBoxLayout()
|
||||
vbox2.setContentsMargins(0, 0, 0, 0)
|
||||
vbox2.addWidget(widget)
|
||||
self.setLayout(vbox2)
|
||||
|
||||
def size(self):
|
||||
return self._setting.sizeHint()
|
||||
|
||||
def showEvent(self, event):
|
||||
# required to get keyboard events
|
||||
self.setFocus()
|
||||
|
||||
def hideEvent(self, event):
|
||||
# emit on hide: this happens for OK button as well as
|
||||
# "click away" closing of the popup
|
||||
self.close.emit()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
# close on hitting enter
|
||||
if event.key() == QtGui.Qt.Key_Enter or event.key() == QtGui.Qt.Key_Return:
|
||||
self.hide()
|
||||
|
||||
|
||||
class _SummaryWidget(QtGui.QWidget):
|
||||
|
||||
delete = QtCore.Signal(object, object) # to delete: document object, summary widget
|
||||
|
||||
def __init__(self, st_object, extractor, post_dialog):
|
||||
super().__init__()
|
||||
|
||||
self._st_object = st_object
|
||||
self._extractor = extractor
|
||||
self._post_dialog = post_dialog
|
||||
|
||||
extr_label = extractor.Proxy.get_representive_fieldname(extractor)
|
||||
extr_repr = extractor.ViewObject.Proxy.get_preview()
|
||||
|
||||
# build the UI
|
||||
hbox = QtGui.QHBoxLayout()
|
||||
hbox.setContentsMargins(6, 0, 6, 0)
|
||||
hbox.setSpacing(2)
|
||||
|
||||
self.extrButton = self._button(extractor.ViewObject.Icon, extr_label)
|
||||
self.viewButton = self._button(extr_repr[0], extr_repr[1], 1)
|
||||
|
||||
size = self.viewButton.iconSize()
|
||||
size.setWidth(size.width() * 2)
|
||||
self.viewButton.setIconSize(size)
|
||||
|
||||
if st_object:
|
||||
self.stButton = self._button(st_object.ViewObject.Icon, st_object.Label)
|
||||
hbox.addWidget(self.stButton)
|
||||
|
||||
else:
|
||||
# that happens if the source of the extractor was deleted and now
|
||||
# that property is set to None
|
||||
self.extrButton.hide()
|
||||
self.viewButton.hide()
|
||||
|
||||
self.warning = QtGui.QLabel(self)
|
||||
self.warning.full_text = translate("FEM", "{}: Data source not available").format(
|
||||
extractor.Label
|
||||
)
|
||||
hbox.addWidget(self.warning)
|
||||
|
||||
self.rmButton = QtGui.QToolButton(self)
|
||||
self.rmButton.setIcon(FreeCADGui.getIcon("delete.svg"))
|
||||
self.rmButton.setAutoRaise(True)
|
||||
|
||||
hbox.addWidget(self.extrButton)
|
||||
hbox.addWidget(self.viewButton)
|
||||
hbox.addSpacing(15)
|
||||
hbox.addWidget(self.rmButton)
|
||||
|
||||
# add the separation line
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
vbox.setSpacing(5)
|
||||
vbox.addItem(hbox)
|
||||
self.frame = QtGui.QFrame(self)
|
||||
self.frame.setFrameShape(QtGui.QFrame.HLine)
|
||||
vbox.addWidget(self.frame)
|
||||
|
||||
policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
|
||||
self.setSizePolicy(policy)
|
||||
# self.setMinimumSize(self.extrButton.sizeHint()+self.frame.sizeHint()*3)
|
||||
self.setLayout(vbox)
|
||||
|
||||
# connect actions. We add functions to widget, as well as the data we need,
|
||||
# and use those as callback. This way every widget knows which objects to use
|
||||
if st_object:
|
||||
self.stButton.clicked.connect(self.showVisualization)
|
||||
self.extrButton.clicked.connect(self.editApp)
|
||||
self.viewButton.clicked.connect(self.editView)
|
||||
|
||||
self.rmButton.clicked.connect(self.deleteTriggered)
|
||||
|
||||
# make sure initial drawing happened
|
||||
# self._redraw()
|
||||
|
||||
def _button(self, icon, text, stretch=2):
|
||||
|
||||
btn = _ElideToolButton(icon, text, self)
|
||||
btn.setMinimumWidth(0)
|
||||
btn.setAutoRaise(True)
|
||||
btn.setToolTip(text)
|
||||
|
||||
policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
|
||||
policy.setHorizontalStretch(stretch)
|
||||
btn.setSizePolicy(policy)
|
||||
return btn
|
||||
|
||||
@QtCore.Slot()
|
||||
def showVisualization(self):
|
||||
if vis.is_visualization_object(self._st_object):
|
||||
# show the visualization
|
||||
self._st_object.ViewObject.Proxy.show_visualization()
|
||||
else:
|
||||
# for now just select the thing
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCADGui.Selection.addSelection(self._st_object)
|
||||
|
||||
def _position_dialog(self, dialog):
|
||||
|
||||
# the scroll area does mess the mapping to global up, somehow
|
||||
# the transformation from the widget ot the scroll area gives
|
||||
# very weird values. Hence we build the coords of the widget
|
||||
# ourself
|
||||
|
||||
summary = dialog.parent() # == self
|
||||
base_widget = summary.parent()
|
||||
viewport = summary.parent()
|
||||
scroll = viewport.parent()
|
||||
|
||||
top_left = (
|
||||
summary.geometry().topLeft()
|
||||
+ base_widget.geometry().topLeft()
|
||||
+ viewport.geometry().topLeft()
|
||||
)
|
||||
delta = (summary.width() - dialog.size().width()) / 2
|
||||
local_point = QtCore.QPoint(top_left.x() + delta, top_left.y() + summary.height())
|
||||
global_point = scroll.mapToGlobal(local_point)
|
||||
|
||||
dialog.setGeometry(QtCore.QRect(global_point, dialog.sizeHint()))
|
||||
|
||||
@QtCore.Slot()
|
||||
def editApp(self):
|
||||
if not hasattr(self, "appDialog"):
|
||||
widget = self._extractor.ViewObject.Proxy.get_app_edit_widget(self._post_dialog)
|
||||
self.appDialog = _SettingsPopup(widget, self)
|
||||
self.appDialog.close.connect(self.appAccept)
|
||||
|
||||
if not self.appDialog.isVisible():
|
||||
# position correctly and show
|
||||
self._position_dialog(self.appDialog)
|
||||
self.appDialog.show()
|
||||
|
||||
@QtCore.Slot()
|
||||
def editView(self):
|
||||
|
||||
if not hasattr(self, "viewDialog"):
|
||||
widget = self._extractor.ViewObject.Proxy.get_view_edit_widget(self._post_dialog)
|
||||
self.viewDialog = _SettingsPopup(widget, self)
|
||||
self.viewDialog.close.connect(self.viewAccept)
|
||||
|
||||
if not self.viewDialog.isVisible():
|
||||
# position correctly and show
|
||||
self._position_dialog(self.viewDialog)
|
||||
self.viewDialog.show()
|
||||
|
||||
@QtCore.Slot()
|
||||
def deleteTriggered(self):
|
||||
self.delete.emit(self._extractor, self)
|
||||
|
||||
@QtCore.Slot()
|
||||
def viewAccept(self):
|
||||
|
||||
# update the preview
|
||||
extr_repr = self._extractor.ViewObject.Proxy.get_preview()
|
||||
self.viewButton.setCustomIcon(extr_repr[0])
|
||||
self.viewButton.setCustomText(extr_repr[1])
|
||||
self.viewButton.setToolTip(extr_repr[1])
|
||||
|
||||
@QtCore.Slot()
|
||||
def appAccept(self):
|
||||
|
||||
# update the preview
|
||||
extr_label = self._extractor.Proxy.get_representive_fieldname(self._extractor)
|
||||
self.extrButton.setCustomText(extr_label)
|
||||
self.extrButton.setToolTip(extr_label)
|
||||
|
||||
|
||||
class ExtractLinkView(QtGui.QWidget):
|
||||
|
||||
def __init__(self, obj, is_source, post_dialog):
|
||||
# initializes the view.
|
||||
# obj: The object for which the links should be shown / summarized
|
||||
# is_source: Bool, if the object is the data source (e.g. postobject), or the target (e.g. plots)
|
||||
|
||||
super().__init__()
|
||||
|
||||
self._object = obj
|
||||
self._is_source = is_source
|
||||
self._post_dialog = post_dialog
|
||||
self._widgets = []
|
||||
|
||||
# build the layout:
|
||||
self._scroll_view = QtGui.QScrollArea(self)
|
||||
self._scroll_view.setHorizontalScrollBarPolicy(QtGui.Qt.ScrollBarAlwaysOff)
|
||||
self._scroll_view.setWidgetResizable(True)
|
||||
self._scroll_widget = QtGui.QWidget(self._scroll_view)
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 6, 0, 0)
|
||||
vbox.addStretch()
|
||||
self._scroll_widget.setLayout(vbox)
|
||||
self._scroll_view.setWidget(self._scroll_widget)
|
||||
|
||||
hbox = QtGui.QHBoxLayout()
|
||||
hbox.setSpacing(6)
|
||||
label = QtGui.QLabel(translate("FEM", "Data used in:"))
|
||||
if not self._is_source:
|
||||
label.setText(translate("FEM", "Data used from:"))
|
||||
|
||||
label.setAlignment(QtGui.Qt.AlignBottom)
|
||||
hbox.addWidget(label)
|
||||
hbox.addStretch()
|
||||
|
||||
if self._is_source:
|
||||
|
||||
self._add = _TreeChoiceButton(build_add_to_visualization_tree_model())
|
||||
self._add.setText(translate("FEM", "Add data to"))
|
||||
self._add.selection.connect(self.addExtractionToVisualization)
|
||||
hbox.addWidget(self._add)
|
||||
|
||||
self._create = _TreeChoiceButton(build_new_visualization_tree_model())
|
||||
self._create.setText(translate("FEM", "New"))
|
||||
self._create.selection.connect(self.newVisualization)
|
||||
hbox.addWidget(self._create)
|
||||
|
||||
else:
|
||||
vis_type = vis.get_visualization_type(self._object)
|
||||
self._add = _TreeChoiceButton(build_add_from_data_tree_model(vis_type))
|
||||
self._add.setText(translate("FEM", "Add data from"))
|
||||
self._add.selection.connect(self.addExtractionToPostObject)
|
||||
hbox.addWidget(self._add)
|
||||
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
vbox.addItem(hbox)
|
||||
vbox.addWidget(self._scroll_view)
|
||||
|
||||
self.setLayout(vbox)
|
||||
|
||||
# add the content
|
||||
self.repopulate()
|
||||
|
||||
def _build_summary_widget(self, extractor):
|
||||
|
||||
if self._is_source:
|
||||
st_object = extractor.getParentGroup()
|
||||
else:
|
||||
st_object = extractor.Source
|
||||
|
||||
widget = _SummaryWidget(st_object, extractor, self._post_dialog)
|
||||
widget.delete.connect(self._delete_extraction)
|
||||
|
||||
return widget
|
||||
|
||||
def _delete_extraction(self, extractor, widget):
|
||||
# remove the document object
|
||||
doc = extractor.Document
|
||||
doc.removeObject(extractor.Name)
|
||||
doc.recompute()
|
||||
|
||||
# remove the widget
|
||||
self._widgets.remove(widget)
|
||||
widget.deleteLater()
|
||||
|
||||
def repopulate(self):
|
||||
# collect all links that are available and shows them
|
||||
|
||||
# clear the view
|
||||
for widget in self._widgets:
|
||||
widget.hide()
|
||||
widget.deleteLater()
|
||||
|
||||
self._widgets = []
|
||||
|
||||
# rebuild the widgets
|
||||
|
||||
if self._is_source:
|
||||
candidates = self._object.InList
|
||||
else:
|
||||
candidates = self._object.OutList
|
||||
|
||||
# get all widgets from the candidates
|
||||
for candidate in candidates:
|
||||
if extr.is_extractor_object(candidate):
|
||||
summary = self._build_summary_widget(candidate)
|
||||
self._widgets.append(summary)
|
||||
|
||||
# fill the scroll area
|
||||
vbox = self._scroll_widget.layout()
|
||||
for widget in reversed(self._widgets):
|
||||
vbox.insertWidget(0, widget)
|
||||
|
||||
# also reset the add button model
|
||||
if self._is_source:
|
||||
self._add.setModel(build_add_to_visualization_tree_model())
|
||||
|
||||
def _find_parent_analysis(self, obj):
|
||||
# iterate upwards, till we find a analysis
|
||||
for parent in obj.InList:
|
||||
if parent.isDerivedFrom("Fem::FemAnalysis"):
|
||||
return parent
|
||||
|
||||
analysis = self._find_parent_analysis(parent)
|
||||
if analysis:
|
||||
return analysis
|
||||
|
||||
return None
|
||||
|
||||
QtCore.Slot(object, object) # visualization data, extraction data
|
||||
|
||||
def newVisualization(self, vis_data, ext_data):
|
||||
|
||||
FreeCADGui.addModule(vis_data.module)
|
||||
FreeCADGui.addModule(ext_data.module)
|
||||
FreeCADGui.addModule("FemGui")
|
||||
|
||||
# create visualization
|
||||
FreeCADGui.doCommand(
|
||||
f"visualization = {vis_data.module}.{vis_data.factory}(FreeCAD.ActiveDocument)"
|
||||
)
|
||||
|
||||
analysis = self._find_parent_analysis(self._object)
|
||||
if analysis:
|
||||
FreeCADGui.doCommand(f"FreeCAD.ActiveDocument.{analysis.Name}.addObject(visualization)")
|
||||
|
||||
# create extraction and add it
|
||||
FreeCADGui.doCommand(
|
||||
f"extraction = {ext_data.module}.{ext_data.factory}(FreeCAD.ActiveDocument)"
|
||||
)
|
||||
FreeCADGui.doCommand(f"extraction.Source = FreeCAD.ActiveDocument.{self._object.Name}")
|
||||
# default values: color
|
||||
color_prop = FreeCADGui.ActiveDocument.ActiveObject.Proxy.get_default_color_property()
|
||||
if color_prop:
|
||||
FreeCADGui.doCommand(
|
||||
f"extraction.ViewObject.{color_prop} = visualization.ViewObject.Proxy.get_next_default_color()"
|
||||
)
|
||||
|
||||
FreeCADGui.doCommand(f"visualization.addObject(extraction)")
|
||||
|
||||
self._post_dialog._recompute()
|
||||
self.repopulate()
|
||||
|
||||
QtCore.Slot(object, object) # visualization object, extraction data
|
||||
|
||||
def addExtractionToVisualization(self, vis_obj, ext_data):
|
||||
|
||||
FreeCADGui.addModule(ext_data.module)
|
||||
FreeCADGui.addModule("FemGui")
|
||||
|
||||
# create extraction and add it
|
||||
FreeCADGui.doCommand(
|
||||
f"extraction = {ext_data.module}.{ext_data.factory}(FreeCAD.ActiveDocument)"
|
||||
)
|
||||
FreeCADGui.doCommand(f"extraction.Source = FreeCAD.ActiveDocument.{self._object.Name}")
|
||||
|
||||
# default values: color
|
||||
color_prop = FreeCADGui.ActiveDocument.ActiveObject.Proxy.get_default_color_property()
|
||||
if color_prop:
|
||||
FreeCADGui.doCommand(
|
||||
f"extraction.ViewObject.{color_prop} = (Gui.ActiveDocument.{vis_obj.Name}.Proxy.get_next_default_color())"
|
||||
)
|
||||
|
||||
FreeCADGui.doCommand(f"App.ActiveDocument.{vis_obj.Name}.addObject(extraction)")
|
||||
|
||||
self._post_dialog._recompute()
|
||||
self.repopulate()
|
||||
|
||||
QtCore.Slot(object, object) # post object, extraction data
|
||||
|
||||
def addExtractionToPostObject(self, post_obj, ext_data):
|
||||
|
||||
FreeCADGui.addModule(ext_data.module)
|
||||
FreeCADGui.addModule("FemGui")
|
||||
|
||||
# create extraction and add it
|
||||
FreeCADGui.doCommand(
|
||||
f"extraction = {ext_data.module}.{ext_data.factory}(FreeCAD.ActiveDocument)"
|
||||
)
|
||||
FreeCADGui.doCommand(f"extraction.Source = FreeCAD.ActiveDocument.{post_obj.Name}")
|
||||
|
||||
# default values for color
|
||||
color_prop = FreeCADGui.ActiveDocument.ActiveObject.Proxy.get_default_color_property()
|
||||
if color_prop:
|
||||
FreeCADGui.doCommand(
|
||||
f"extraction.ViewObject.{color_prop} = Gui.ActiveDocument.{self._object.Name}.Proxy.get_next_default_color()"
|
||||
)
|
||||
|
||||
FreeCADGui.doCommand(f"App.ActiveDocument.{self._object.Name}.addObject(extraction)")
|
||||
|
||||
self._post_dialog._recompute()
|
||||
self.repopulate()
|
||||
179
src/Mod/Fem/femguiutils/post_visualization.py
Normal file
179
src/Mod/Fem/femguiutils/post_visualization.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# ***************************************************************************
|
||||
# * 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 visualization registry"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_visualization
|
||||
# \ingroup FEM
|
||||
# \brief A registry to collect visualizations for use in menus
|
||||
|
||||
# Note: This file is imported from FreeCAD App files. Do not import any FreeCADGui
|
||||
# directly to support cmd line use.
|
||||
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
import FreeCAD
|
||||
|
||||
|
||||
# Registry to handle visualization commands
|
||||
# #########################################
|
||||
|
||||
_registry = {}
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Extraction:
|
||||
|
||||
name: str
|
||||
icon: str
|
||||
dimension: str
|
||||
extracttype: str
|
||||
module: str
|
||||
factory: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Visualization:
|
||||
|
||||
name: str
|
||||
icon: str
|
||||
module: str
|
||||
factory: str
|
||||
extractions: list[_Extraction]
|
||||
|
||||
|
||||
# Register a visualization by type, icon and factory function
|
||||
def register_visualization(visualization_type, icon, module, factory):
|
||||
if visualization_type in _registry:
|
||||
raise ValueError("Visualization type already registered")
|
||||
|
||||
_registry[visualization_type] = _Visualization(visualization_type, icon, module, factory, [])
|
||||
|
||||
|
||||
def register_extractor(
|
||||
visualization_type, extraction_type, icon, dimension, etype, module, factory
|
||||
):
|
||||
|
||||
if not visualization_type in _registry:
|
||||
raise ValueError("visualization not registered yet")
|
||||
|
||||
extraction = _Extraction(extraction_type, icon, dimension, etype, module, factory)
|
||||
_registry[visualization_type].extractions.append(extraction)
|
||||
|
||||
|
||||
def get_registered_visualizations():
|
||||
return copy.deepcopy(_registry)
|
||||
|
||||
|
||||
def _to_command_name(name):
|
||||
return "FEM_PostVisualization" + name
|
||||
|
||||
|
||||
class _VisualizationGroupCommand:
|
||||
|
||||
def GetCommands(self):
|
||||
visus = _registry.keys()
|
||||
cmds = [_to_command_name(v) for v in visus]
|
||||
return cmds
|
||||
|
||||
def GetDefaultCommand(self):
|
||||
return 0
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"MenuText": QtCore.QT_TRANSLATE_NOOP("FEM", "Data Visualizations"),
|
||||
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
|
||||
"FEM", "Different visualizations to show post processing data in"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if not FreeCAD.ActiveDocument:
|
||||
return False
|
||||
|
||||
import FemGui
|
||||
|
||||
return bool(FemGui.getActiveAnalysis())
|
||||
|
||||
|
||||
class _VisualizationCommand:
|
||||
|
||||
def __init__(self, visualization_type):
|
||||
self._visualization_type = visualization_type
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
cmd = _to_command_name(self._visualization_type)
|
||||
vis = _registry[self._visualization_type]
|
||||
tooltip = f"Create a {self._visualization_type} post processing data visualization"
|
||||
|
||||
return {
|
||||
"Pixmap": vis.icon,
|
||||
"MenuText": QtCore.QT_TRANSLATE_NOOP(cmd, "Create {}".format(self._visualization_type)),
|
||||
"Accel": "",
|
||||
"ToolTip": QtCore.QT_TRANSLATE_NOOP(cmd, tooltip),
|
||||
"CmdType": "AlterDoc",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
# active analysis available
|
||||
if not FreeCAD.ActiveDocument:
|
||||
return False
|
||||
|
||||
import FemGui
|
||||
|
||||
return bool(FemGui.getActiveAnalysis())
|
||||
|
||||
def Activated(self):
|
||||
import FreeCADGui
|
||||
|
||||
vis = _registry[self._visualization_type]
|
||||
FreeCAD.ActiveDocument.openTransaction(f"Create {vis.name}")
|
||||
|
||||
FreeCADGui.addModule(vis.module)
|
||||
FreeCADGui.addModule("FemGui")
|
||||
|
||||
FreeCADGui.doCommand(f"obj = {vis.module}.{vis.factory}(FreeCAD.ActiveDocument)")
|
||||
FreeCADGui.doCommand(f"FemGui.getActiveAnalysis().addObject(obj)")
|
||||
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCADGui.doCommand("FreeCADGui.ActiveDocument.setEdit(obj)")
|
||||
|
||||
|
||||
def setup_commands(toplevel_name):
|
||||
# creates all visualization commands and registers them. The
|
||||
# toplevel group command will have the name provided to this function.
|
||||
|
||||
import FreeCADGui
|
||||
|
||||
# first all visualization and extraction commands
|
||||
for vis in _registry:
|
||||
FreeCADGui.addCommand(_to_command_name(vis), _VisualizationCommand(vis))
|
||||
|
||||
# build the group command!
|
||||
FreeCADGui.addCommand("FEM_PostVisualization", _VisualizationGroupCommand())
|
||||
@@ -47,6 +47,10 @@ __title__ = "FEM GUI vtk python module check"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
|
||||
# Note: This file is imported from FreeCAD App files. Do not import any FreeCADGui
|
||||
# directly to support cmd line use.
|
||||
|
||||
__user_input_received = False
|
||||
|
||||
|
||||
|
||||
253
src/Mod/Fem/femguiutils/vtk_table_view.py
Normal file
253
src/Mod/Fem/femguiutils/vtk_table_view.py
Normal file
@@ -0,0 +1,253 @@
|
||||
# ***************************************************************************
|
||||
# * 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 table view widget to visualize vtkTable"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package vtk_table_view
|
||||
# \ingroup FEM
|
||||
# \brief A Qt widget to show a vtkTable
|
||||
|
||||
from PySide import QtGui
|
||||
from PySide import QtCore
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from vtkmodules.vtkIOCore import vtkDelimitedTextWriter
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
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, header_names=None):
|
||||
super().__init__()
|
||||
self._table = None
|
||||
if header_names:
|
||||
self._header = header_names
|
||||
else:
|
||||
self._header = {}
|
||||
|
||||
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):
|
||||
|
||||
if not self._table:
|
||||
return 0
|
||||
|
||||
return self._table.GetNumberOfRows()
|
||||
|
||||
def columnCount(self, index):
|
||||
|
||||
if not self._table:
|
||||
return 0
|
||||
|
||||
return self._table.GetNumberOfColumns()
|
||||
|
||||
def data(self, index, role):
|
||||
|
||||
if not self._table:
|
||||
return None
|
||||
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
col = self._table.GetColumn(index.column())
|
||||
return col.GetTuple(index.row())[0]
|
||||
|
||||
return None
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
|
||||
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||
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
|
||||
|
||||
return None
|
||||
|
||||
def getTable(self):
|
||||
return self._table
|
||||
|
||||
|
||||
class VtkTableSummaryModel(QtCore.QAbstractTableModel):
|
||||
# Simple model showing a summary of the table.
|
||||
# Only supports single component columns
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._table = None
|
||||
|
||||
def setTable(self, table):
|
||||
self.beginResetModel()
|
||||
self._table = table
|
||||
self.endResetModel()
|
||||
|
||||
def rowCount(self, index):
|
||||
|
||||
if not self._table:
|
||||
return 0
|
||||
|
||||
return self._table.GetNumberOfColumns()
|
||||
|
||||
def columnCount(self, index):
|
||||
return 2 # min, max
|
||||
|
||||
def data(self, index, role):
|
||||
|
||||
if not self._table:
|
||||
return None
|
||||
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
col = self._table.GetColumn(index.row())
|
||||
range = col.GetRange()
|
||||
return range[index.column()]
|
||||
|
||||
return None
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
|
||||
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||
return ["Min", "Max"][section]
|
||||
|
||||
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
|
||||
return self._table.GetColumnName(section)
|
||||
|
||||
return None
|
||||
|
||||
def getTable(self):
|
||||
return self._table
|
||||
|
||||
|
||||
class VtkTableView(QtGui.QWidget):
|
||||
|
||||
def __init__(self, model):
|
||||
super().__init__()
|
||||
|
||||
self.model = model
|
||||
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# start with the toolbar
|
||||
self.toolbar = QtGui.QToolBar()
|
||||
csv_action = QtGui.QAction(self)
|
||||
csv_action.triggered.connect(self.exportCsv)
|
||||
csv_action.setIcon(FreeCADGui.getIcon("Std_Export"))
|
||||
csv_action.setToolTip(translate("FEM", "Export to CSV"))
|
||||
self.toolbar.addAction(csv_action)
|
||||
|
||||
copy_action = QtGui.QAction(self)
|
||||
copy_action.triggered.connect(self.copyToClipboard)
|
||||
copy_action.setIcon(FreeCADGui.getIcon("edit-copy"))
|
||||
shortcut = QtGui.QKeySequence(QtGui.QKeySequence.Copy)
|
||||
copy_action.setToolTip(
|
||||
translate("FEM", "Copy selection to clipboard ({})".format(shortcut.toString()))
|
||||
)
|
||||
copy_action.setShortcut(shortcut)
|
||||
self.toolbar.addAction(copy_action)
|
||||
|
||||
layout.addWidget(self.toolbar)
|
||||
|
||||
# now the table view
|
||||
self.table_view = QtGui.QTableView()
|
||||
self.table_view.setModel(model)
|
||||
self.model.modelReset.connect(self.modelReset)
|
||||
|
||||
# fast initial resize and manual resizing still allowed!
|
||||
header = self.table_view.horizontalHeader()
|
||||
header.setResizeContentsPrecision(10)
|
||||
self.table_view.resizeColumnsToContents()
|
||||
|
||||
layout.addWidget(self.table_view)
|
||||
self.setLayout(layout)
|
||||
|
||||
@QtCore.Slot()
|
||||
def modelReset(self):
|
||||
# The model is reset, make sure the header visibility is working
|
||||
# This is needed in case new data was added
|
||||
self.table_view.resizeColumnsToContents()
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def exportCsv(self, state):
|
||||
|
||||
file_path, filter = QtGui.QFileDialog.getSaveFileName(
|
||||
None, translate("FEM", "Save as csv file"), "", "CSV (*.csv)"
|
||||
)
|
||||
if not file_path:
|
||||
FreeCAD.Console.PrintMessage(
|
||||
translate("FEM", "CSV file export aborted: no filename selected")
|
||||
)
|
||||
return
|
||||
|
||||
writer = vtkDelimitedTextWriter()
|
||||
writer.SetFileName(file_path)
|
||||
writer.SetInputData(self.model.getTable())
|
||||
writer.Write()
|
||||
|
||||
@QtCore.Slot()
|
||||
def copyToClipboard(self):
|
||||
|
||||
sel_model = self.table_view.selectionModel()
|
||||
selection = sel_model.selectedIndexes()
|
||||
|
||||
if len(selection) < 1:
|
||||
return
|
||||
|
||||
copy_table = ""
|
||||
previous = selection.pop(0)
|
||||
for current in selection:
|
||||
|
||||
data = self.model.data(previous, QtCore.Qt.DisplayRole)
|
||||
copy_table += str(data)
|
||||
|
||||
if current.row() != previous.row():
|
||||
copy_table += "\n"
|
||||
else:
|
||||
copy_table += "\t"
|
||||
|
||||
previous = current
|
||||
|
||||
copy_table += str(self.model.data(selection[-1], QtCore.Qt.DisplayRole))
|
||||
copy_table += "\n"
|
||||
|
||||
clipboard = QtGui.QApplication.instance().clipboard()
|
||||
clipboard.setText(copy_table)
|
||||
398
src/Mod/Fem/femobjects/base_fempostextractors.py
Normal file
398
src/Mod/Fem/femobjects/base_fempostextractors.py
Normal file
@@ -0,0 +1,398 @@
|
||||
# ***************************************************************************
|
||||
# * 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.vtkCommonCore import vtkIntArray
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
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=QT_TRANSLATE_NOOP("FEM", "The data table that stores the extracted data"),
|
||||
value=vtkTable(),
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyLink",
|
||||
name="Source",
|
||||
group="Base",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "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 6:
|
||||
return ["XX", "YY", "ZZ", "XY", "XZ", "YZ"]
|
||||
case _:
|
||||
return ["Not a vector"]
|
||||
|
||||
def get_representive_fieldname(self, obj):
|
||||
# should return the representive field name, e.g. Position (X)
|
||||
return ""
|
||||
|
||||
|
||||
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=QT_TRANSLATE_NOOP("FEM", "The field to use as X data"),
|
||||
value=[],
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="XComponent",
|
||||
group="X Data",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "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)
|
||||
|
||||
def _x_array_component_to_table(self, obj, array, table):
|
||||
# extracts the component out of the array according to XComponent setting
|
||||
# Note: Uses the array name unchanged
|
||||
|
||||
if array.GetNumberOfComponents() == 1:
|
||||
table.AddColumn(array)
|
||||
else:
|
||||
component_array = vtkDoubleArray()
|
||||
component_array.SetNumberOfComponents(1)
|
||||
component_array.SetNumberOfTuples(array.GetNumberOfTuples())
|
||||
c_idx = obj.getEnumerationsOfProperty("XComponent").index(obj.XComponent)
|
||||
component_array.CopyComponent(0, array, c_idx)
|
||||
component_array.SetName(array.GetName())
|
||||
table.AddColumn(component_array)
|
||||
|
||||
def _x_array_from_dataset(self, obj, dataset, copy=True):
|
||||
# extracts the relevant array from the dataset and returns a copy
|
||||
# indices = None uses all indices, otherwise the values in this list
|
||||
|
||||
match obj.XField:
|
||||
case "Index":
|
||||
# index needs always to be build, ignore copy argument
|
||||
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()
|
||||
if copy:
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
else:
|
||||
array = orig_array
|
||||
|
||||
case _:
|
||||
point_data = dataset.GetPointData()
|
||||
orig_array = point_data.GetAbstractArray(obj.XField)
|
||||
if copy:
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
else:
|
||||
array = orig_array
|
||||
|
||||
return array
|
||||
|
||||
def get_representive_fieldname(self, obj):
|
||||
# representive field is the x field
|
||||
label = obj.XField
|
||||
if not label:
|
||||
return ""
|
||||
|
||||
if len(obj.getEnumerationsOfProperty("XComponent")) > 1:
|
||||
label += f" ({obj.XComponent})"
|
||||
|
||||
return label
|
||||
|
||||
|
||||
class Extractor2D(Extractor1D):
|
||||
|
||||
ExtractionDimension = "2D"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YField",
|
||||
group="Y Data",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The field to use as Y data"),
|
||||
value=[],
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="YComponent",
|
||||
group="Y Data",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "Which part of the Y field vector to use for the Y axis"
|
||||
),
|
||||
value=[],
|
||||
),
|
||||
]
|
||||
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
|
||||
super().onChanged(obj, prop)
|
||||
|
||||
if prop == "YField" and obj.Source and obj.Source.getDataSet():
|
||||
point_data = obj.Source.getDataSet().GetPointData()
|
||||
self._setup_y_component_property(obj, point_data)
|
||||
|
||||
if prop == "Source":
|
||||
if obj.Source:
|
||||
dset = obj.Source.getDataSet()
|
||||
if dset:
|
||||
self._setup_y_properties(obj, dset)
|
||||
else:
|
||||
self._clear_y_properties(obj)
|
||||
else:
|
||||
self._clear_y_properties(obj)
|
||||
|
||||
def _setup_y_component_property(self, obj, point_data):
|
||||
|
||||
if obj.YField == "Position":
|
||||
obj.YComponent = self.component_options(3)
|
||||
else:
|
||||
array = point_data.GetAbstractArray(obj.YField)
|
||||
obj.YComponent = self.component_options(array.GetNumberOfComponents())
|
||||
|
||||
def _clear_y_properties(self, obj):
|
||||
if hasattr(obj, "YComponent"):
|
||||
obj.YComponent = []
|
||||
if hasattr(obj, "YField"):
|
||||
obj.YField = []
|
||||
|
||||
def _setup_y_properties(self, obj, dataset):
|
||||
# Set all X Data properties correctly for the given dataset
|
||||
fields = ["Position"]
|
||||
point_data = dataset.GetPointData()
|
||||
|
||||
for i in range(point_data.GetNumberOfArrays()):
|
||||
fields.append(point_data.GetArrayName(i))
|
||||
|
||||
current_field = obj.YField
|
||||
obj.YField = fields
|
||||
if current_field in fields:
|
||||
obj.YField = current_field
|
||||
|
||||
self._setup_y_component_property(obj, point_data)
|
||||
|
||||
def _y_array_component_to_table(self, obj, array, table):
|
||||
# extracts the component out of the array according to XComponent setting
|
||||
|
||||
if array.GetNumberOfComponents() == 1:
|
||||
table.AddColumn(array)
|
||||
else:
|
||||
component_array = vtkDoubleArray()
|
||||
component_array.SetNumberOfComponents(1)
|
||||
component_array.SetNumberOfTuples(array.GetNumberOfTuples())
|
||||
c_idx = obj.getEnumerationsOfProperty("YComponent").index(obj.YComponent)
|
||||
component_array.CopyComponent(0, array, c_idx)
|
||||
component_array.SetName(array.GetName())
|
||||
table.AddColumn(component_array)
|
||||
|
||||
def _y_array_from_dataset(self, obj, dataset, copy=True):
|
||||
# extracts the relevant array from the dataset and returns a copy
|
||||
# indices = None uses all indices, otherwise the values in this list
|
||||
|
||||
match obj.YField:
|
||||
case "Position":
|
||||
|
||||
orig_array = dataset.GetPoints().GetData()
|
||||
if copy:
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
else:
|
||||
array = orig_array
|
||||
|
||||
case _:
|
||||
point_data = dataset.GetPointData()
|
||||
orig_array = point_data.GetAbstractArray(obj.YField)
|
||||
|
||||
if copy:
|
||||
array = vtkDoubleArray()
|
||||
array.DeepCopy(orig_array)
|
||||
else:
|
||||
array = orig_array
|
||||
|
||||
return array
|
||||
|
||||
def get_representive_fieldname(self, obj):
|
||||
# representive field is the y field
|
||||
label = obj.YField
|
||||
if not label:
|
||||
return ""
|
||||
|
||||
if len(obj.getEnumerationsOfProperty("YComponent")) > 1:
|
||||
label += f" ({obj.YComponent})"
|
||||
|
||||
return label
|
||||
183
src/Mod/Fem/femobjects/base_fempostvisualizations.py
Normal file
183
src/Mod/Fem/femobjects/base_fempostvisualizations.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# ***************************************************************************
|
||||
# * 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 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)
|
||||
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
|
||||
@@ -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):
|
||||
|
||||
213
src/Mod/Fem/femobjects/post_extract1D.py
Normal file
213
src/Mod/Fem/femobjects/post_extract1D.py
Normal file
@@ -0,0 +1,213 @@
|
||||
# ***************************************************************************
|
||||
# * 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
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempythonobject
|
||||
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
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=QT_TRANSLATE_NOOP(
|
||||
"FEM", "Specify if the field shall be extracted for every available frame"
|
||||
),
|
||||
value=False,
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
# on execution we populate the vtk table
|
||||
table = vtkTable()
|
||||
|
||||
if not obj.Source:
|
||||
obj.Table = table
|
||||
return
|
||||
|
||||
dataset = obj.Source.getDataSet()
|
||||
if not dataset:
|
||||
obj.Table = table
|
||||
return
|
||||
|
||||
timesteps = []
|
||||
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())
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
'No frames available in data, ignoring "ExtractFrames" property'
|
||||
)
|
||||
|
||||
if not timesteps:
|
||||
# get the dataset and extract the correct array
|
||||
array = self._x_array_from_dataset(obj, dataset)
|
||||
if array.GetNumberOfComponents() > 1:
|
||||
array.SetName(obj.XField + " (" + obj.XComponent + ")")
|
||||
else:
|
||||
array.SetName(obj.XField)
|
||||
|
||||
self._x_array_component_to_table(obj, array, table)
|
||||
|
||||
else:
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
for timestep in timesteps:
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self._x_array_from_dataset(obj, dataset)
|
||||
|
||||
if array.GetNumberOfComponents() > 1:
|
||||
array.SetName(f"{obj.XField} ({obj.XComponent}) - {timestep}")
|
||||
else:
|
||||
array.SetName(f"{obj.XField} - {timestep}")
|
||||
self._x_array_component_to_table(obj, array, table)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
|
||||
|
||||
class PostIndexOverFrames1D(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::PropertyInteger",
|
||||
name="Index",
|
||||
group="X Data",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "Specify for which index the data should be extracted"
|
||||
),
|
||||
value=0,
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
# on execution we populate the vtk table
|
||||
table = vtkTable()
|
||||
|
||||
if not obj.Source:
|
||||
obj.Table = table
|
||||
return
|
||||
|
||||
dataset = obj.Source.getDataSet()
|
||||
if not dataset:
|
||||
obj.Table = table
|
||||
return
|
||||
|
||||
# check if we have timesteps
|
||||
timesteps = []
|
||||
info = obj.Source.getOutputAlgorithm().GetOutputInformation(0)
|
||||
if info.Has(vtkStreamingDemandDrivenPipeline.TIME_STEPS()):
|
||||
timesteps = info.Get(vtkStreamingDemandDrivenPipeline.TIME_STEPS())
|
||||
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
frame_array = vtkDoubleArray()
|
||||
idx = obj.Index
|
||||
|
||||
if timesteps:
|
||||
setup = False
|
||||
for i, timestep in enumerate(timesteps):
|
||||
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self._x_array_from_dataset(obj, dataset, copy=False)
|
||||
|
||||
# safeguard for invalid access
|
||||
if idx < 0 or array.GetNumberOfTuples() - 1 < idx:
|
||||
raise Exception(
|
||||
f"Invalid index: {idx} is not in range 0 - {array.GetNumberOfTuples()-1}"
|
||||
)
|
||||
|
||||
if not setup:
|
||||
frame_array.SetNumberOfComponents(array.GetNumberOfComponents())
|
||||
frame_array.SetNumberOfTuples(len(timesteps))
|
||||
setup = True
|
||||
|
||||
frame_array.SetTuple(i, idx, array)
|
||||
else:
|
||||
algo.Update()
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self._x_array_from_dataset(obj, dataset, copy=False)
|
||||
|
||||
# safeguard for invalid access
|
||||
if idx < 0 or array.GetNumberOfTuples() - 1 < idx:
|
||||
raise Exception(
|
||||
f"Invalid index: {idx} is not in range 0 - {array.GetNumberOfTuples()-1}"
|
||||
)
|
||||
|
||||
frame_array.SetNumberOfComponents(array.GetNumberOfComponents())
|
||||
frame_array.SetNumberOfTuples(1)
|
||||
frame_array.SetTuple(0, idx, array)
|
||||
|
||||
if frame_array.GetNumberOfComponents() > 1:
|
||||
frame_array.SetName(f"{obj.XField} ({obj.XComponent}) @Idx {obj.Index}")
|
||||
else:
|
||||
frame_array.SetName(f"{obj.XField} @Idx {obj.Index}")
|
||||
|
||||
self._x_array_component_to_table(obj, frame_array, table)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
249
src/Mod/Fem/femobjects/post_extract2D.py
Normal file
249
src/Mod/Fem/femobjects/post_extract2D.py
Normal file
@@ -0,0 +1,249 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD post extractors 2D"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_histogram
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying lines
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempythonobject
|
||||
|
||||
_PropHelper = base_fempythonobject._PropHelper
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
class PostFieldData2D(base_fempostextractors.Extractor2D):
|
||||
"""
|
||||
A post processing extraction of two dimensional field data
|
||||
"""
|
||||
|
||||
ExtractionType = "Field"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="ExtractFrames",
|
||||
group="Multiframe",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "Specify if the field shall be extracted for every available frame"
|
||||
),
|
||||
value=False,
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
# on execution we populate the vtk table
|
||||
table = vtkTable()
|
||||
|
||||
if not obj.Source:
|
||||
obj.Table = table
|
||||
return
|
||||
|
||||
dataset = obj.Source.getDataSet()
|
||||
if not dataset:
|
||||
obj.Table = table
|
||||
return
|
||||
|
||||
timesteps = []
|
||||
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())
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
'No frames available in data, ignoring "ExtractFrames" property'
|
||||
)
|
||||
|
||||
if not timesteps:
|
||||
# get the dataset and extract the correct array
|
||||
xarray = self._x_array_from_dataset(obj, dataset)
|
||||
if xarray.GetNumberOfComponents() > 1:
|
||||
xarray.SetName(obj.XField + " (" + obj.XComponent + ")")
|
||||
else:
|
||||
xarray.SetName(obj.XField)
|
||||
|
||||
self._x_array_component_to_table(obj, xarray, table)
|
||||
|
||||
yarray = self._y_array_from_dataset(obj, dataset)
|
||||
if yarray.GetNumberOfComponents() > 1:
|
||||
yarray.SetName(obj.YField + " (" + obj.YComponent + ")")
|
||||
else:
|
||||
yarray.SetName(obj.YField)
|
||||
|
||||
self._y_array_component_to_table(obj, yarray, table)
|
||||
|
||||
else:
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
for timestep in timesteps:
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
|
||||
xarray = self._x_array_from_dataset(obj, dataset)
|
||||
if xarray.GetNumberOfComponents() > 1:
|
||||
xarray.SetName(f"X - {obj.XField} ({obj.XComponent}) - {timestep}")
|
||||
else:
|
||||
xarray.SetName(f"X - {obj.XField} - {timestep}")
|
||||
self._x_array_component_to_table(obj, xarray, table)
|
||||
|
||||
yarray = self._y_array_from_dataset(obj, dataset)
|
||||
if yarray.GetNumberOfComponents() > 1:
|
||||
yarray.SetName(f"{obj.YField} ({obj.YComponent}) - {timestep}")
|
||||
else:
|
||||
yarray.SetName(f"{obj.YField} - {timestep}")
|
||||
self._y_array_component_to_table(obj, yarray, table)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
|
||||
|
||||
class PostIndexOverFrames2D(base_fempostextractors.Extractor2D):
|
||||
"""
|
||||
A post processing extraction for two dimensional data with X always being the frames
|
||||
"""
|
||||
|
||||
ExtractionType = "Index"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
def _get_properties(self):
|
||||
prop = [
|
||||
_PropHelper(
|
||||
type="App::PropertyInteger",
|
||||
name="Index",
|
||||
group="Data",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "Specify for which point index the data should be extracted"
|
||||
),
|
||||
value=0,
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def _setup_x_component_property(self, obj, point_data):
|
||||
# override to only allow "Frames" as X data
|
||||
obj.XComponent = ["Not a vector"]
|
||||
|
||||
def _setup_x_properties(self, obj, dataset):
|
||||
# override to only allow "Frames" as X data
|
||||
obj.XField = ["Frames"]
|
||||
|
||||
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
|
||||
|
||||
# check if we have timesteps (required!)
|
||||
timesteps = []
|
||||
info = obj.Source.getOutputAlgorithm().GetOutputInformation(0)
|
||||
if info.Has(vtkStreamingDemandDrivenPipeline.TIME_STEPS()):
|
||||
timesteps = info.Get(vtkStreamingDemandDrivenPipeline.TIME_STEPS())
|
||||
|
||||
algo = obj.Source.getOutputAlgorithm()
|
||||
|
||||
frame_x_array = vtkDoubleArray()
|
||||
frame_y_array = vtkDoubleArray()
|
||||
idx = obj.Index
|
||||
|
||||
if timesteps:
|
||||
setup = False
|
||||
frame_x_array.SetNumberOfTuples(len(timesteps))
|
||||
frame_x_array.SetNumberOfComponents(1)
|
||||
for i, timestep in enumerate(timesteps):
|
||||
|
||||
frame_x_array.SetTuple1(i, timestep)
|
||||
|
||||
algo.UpdateTimeStep(timestep)
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self._y_array_from_dataset(obj, dataset, copy=False)
|
||||
|
||||
# safeguard for invalid access
|
||||
if idx < 0 or array.GetNumberOfTuples() - 1 < idx:
|
||||
raise Exception(
|
||||
f"Invalid index: {idx} is not in range 0 - {array.GetNumberOfTuples()-1}"
|
||||
)
|
||||
|
||||
if not setup:
|
||||
frame_y_array.SetNumberOfComponents(array.GetNumberOfComponents())
|
||||
frame_y_array.SetNumberOfTuples(len(timesteps))
|
||||
setup = True
|
||||
|
||||
frame_y_array.SetTuple(i, idx, array)
|
||||
|
||||
else:
|
||||
frame_x_array.SetNumberOfTuples(1)
|
||||
frame_x_array.SetNumberOfComponents(1)
|
||||
frame_x_array.SetTuple1(0, 0)
|
||||
|
||||
algo.Update()
|
||||
dataset = algo.GetOutputDataObject(0)
|
||||
array = self._y_array_from_dataset(obj, dataset, copy=False)
|
||||
|
||||
# safeguard for invalid access
|
||||
if idx < 0 or array.GetNumberOfTuples() - 1 < idx:
|
||||
raise Exception(
|
||||
f"Invalid index: {idx} is not in range 0 - {array.GetNumberOfTuples()-1}"
|
||||
)
|
||||
|
||||
frame_y_array.SetNumberOfComponents(array.GetNumberOfComponents())
|
||||
frame_y_array.SetNumberOfTuples(1)
|
||||
frame_y_array.SetTuple(0, idx, array)
|
||||
|
||||
frame_x_array.SetName("Frames")
|
||||
if frame_y_array.GetNumberOfComponents() > 1:
|
||||
frame_y_array.SetName(f"{obj.YField} ({obj.YComponent}) @Idx {obj.Index}")
|
||||
else:
|
||||
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)
|
||||
|
||||
# set the final table
|
||||
obj.Table = table
|
||||
105
src/Mod/Fem/femobjects/post_histogram.py
Normal file
105
src/Mod/Fem/femobjects/post_histogram.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# ***************************************************************************
|
||||
# * 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 histogram"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_histogram
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying histograms
|
||||
|
||||
# check vtk version to potentially find missmatchs
|
||||
from femguiutils.vtk_module_handling import vtk_module_handling
|
||||
|
||||
vtk_module_handling()
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract1D
|
||||
|
||||
from femguiutils import post_visualization
|
||||
|
||||
# register visualization and extractors
|
||||
post_visualization.register_visualization(
|
||||
"Histogram", ":/icons/FEM_PostHistogram.svg", "ObjectsFem", "makePostHistogram"
|
||||
)
|
||||
|
||||
post_visualization.register_extractor(
|
||||
"Histogram",
|
||||
"HistogramFieldData",
|
||||
":/icons/FEM_PostField.svg",
|
||||
"1D",
|
||||
"Field",
|
||||
"ObjectsFem",
|
||||
"makePostHistogramFieldData",
|
||||
)
|
||||
|
||||
|
||||
post_visualization.register_extractor(
|
||||
"Histogram",
|
||||
"HistogramIndexOverFrames",
|
||||
":/icons/FEM_PostIndex.svg",
|
||||
"1D",
|
||||
"Index",
|
||||
"ObjectsFem",
|
||||
"makePostHistogramIndexOverFrames",
|
||||
)
|
||||
|
||||
# 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 PostHistogramIndexOverFrames(post_extract1D.PostIndexOverFrames1D):
|
||||
"""
|
||||
A 1D index extraction for histogram.
|
||||
"""
|
||||
|
||||
VisualizationType = "Histogram"
|
||||
|
||||
|
||||
class PostHistogram(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as histograms
|
||||
"""
|
||||
|
||||
VisualizationType = "Histogram"
|
||||
105
src/Mod/Fem/femobjects/post_lineplot.py
Normal file
105
src/Mod/Fem/femobjects/post_lineplot.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# ***************************************************************************
|
||||
# * 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
|
||||
|
||||
# check vtk version to potentially find missmatchs
|
||||
from femguiutils.vtk_module_handling import vtk_module_handling
|
||||
|
||||
vtk_module_handling()
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract2D
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
post_visualization.register_extractor(
|
||||
"Lineplot",
|
||||
"LineplotIndexOverFrames",
|
||||
":/icons/FEM_PostIndex.svg",
|
||||
"2D",
|
||||
"Index",
|
||||
"ObjectsFem",
|
||||
"makePostLineplotIndexOverFrames",
|
||||
)
|
||||
|
||||
|
||||
# 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 2D Field extraction for lineplot.
|
||||
"""
|
||||
|
||||
VisualizationType = "Lineplot"
|
||||
|
||||
|
||||
class PostLineplotIndexOverFrames(post_extract2D.PostIndexOverFrames2D):
|
||||
"""
|
||||
A 2D index extraction for lineplot.
|
||||
"""
|
||||
|
||||
VisualizationType = "Lineplot"
|
||||
|
||||
|
||||
class PostLineplot(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as line plots
|
||||
"""
|
||||
|
||||
VisualizationType = "Lineplot"
|
||||
105
src/Mod/Fem/femobjects/post_table.py
Normal file
105
src/Mod/Fem/femobjects/post_table.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# ***************************************************************************
|
||||
# * 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 table"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package post_table
|
||||
# \ingroup FEM
|
||||
# \brief Post processing plot displaying tables
|
||||
|
||||
# check vtk version to potentially find missmatchs
|
||||
from femguiutils.vtk_module_handling import vtk_module_handling
|
||||
|
||||
vtk_module_handling()
|
||||
|
||||
from . import base_fempostextractors
|
||||
from . import base_fempostvisualizations
|
||||
from . import post_extract1D
|
||||
|
||||
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 1D Field extraction for tables.
|
||||
"""
|
||||
|
||||
VisualizationType = "Table"
|
||||
|
||||
|
||||
class PostTableIndexOverFrames(post_extract1D.PostIndexOverFrames1D):
|
||||
"""
|
||||
A 1D index extraction for table.
|
||||
"""
|
||||
|
||||
VisualizationType = "Table"
|
||||
|
||||
|
||||
class PostTable(base_fempostvisualizations.PostVisualization):
|
||||
"""
|
||||
A post processing plot for showing extracted data as tables
|
||||
"""
|
||||
|
||||
VisualizationType = "Table"
|
||||
88
src/Mod/Fem/femtaskpanels/base_fempostpanel.py
Normal file
88
src/Mod/Fem/femtaskpanels/base_fempostpanel.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# ***************************************************************************
|
||||
# * 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 task panel base for post object task panels"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package base_fempostpanel
|
||||
# \ingroup FEM
|
||||
# \brief task panel base for post objects
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
import FreeCAD
|
||||
|
||||
from . import base_femtaskpanel
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class _BasePostTaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
"""
|
||||
The TaskPanel for post objects, mimicing the c++ functionality
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
# get the settings group
|
||||
self.__settings_grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
|
||||
|
||||
# Implement parent functions
|
||||
# ##########################
|
||||
|
||||
def getStandardButtons(self):
|
||||
return (
|
||||
QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel
|
||||
)
|
||||
|
||||
def clicked(self, button):
|
||||
# apply button hit?
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.obj.Document.recompute()
|
||||
|
||||
def open(self):
|
||||
# open a new transaction if non is open
|
||||
if not FreeCAD.getActiveTransaction():
|
||||
FreeCAD.ActiveDocument.openTransaction(
|
||||
translate("FEM", "Edit {}").format(self.obj.Label)
|
||||
)
|
||||
|
||||
# Helper functions
|
||||
# ################
|
||||
|
||||
def _recompute(self):
|
||||
# only recompute if the user wants automatic recompute
|
||||
if self.__settings_grp.GetBool("PostAutoRecompute", True):
|
||||
self.obj.Document.recompute()
|
||||
|
||||
def _enumPropertyToCombobox(self, obj, prop, cbox):
|
||||
cbox.blockSignals(True)
|
||||
cbox.clear()
|
||||
entries = obj.getEnumerationsOfProperty(prop)
|
||||
for entry in entries:
|
||||
cbox.addItem(entry)
|
||||
|
||||
cbox.setCurrentText(getattr(obj, prop))
|
||||
cbox.blockSignals(False)
|
||||
54
src/Mod/Fem/femtaskpanels/task_post_extractor.py
Normal file
54
src/Mod/Fem/femtaskpanels/task_post_extractor.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# ***************************************************************************
|
||||
# * 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 post extractor object task panel"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package task_post_extractor
|
||||
# \ingroup FEM
|
||||
# \brief universal task dialog for extractor objects.
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
from . import base_fempostpanel
|
||||
|
||||
|
||||
class _ExtractorTaskPanel(base_fempostpanel._BasePostTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing properties extractor objects. The actual UI is
|
||||
provided by the viewproviders. This allows using a universal task panel
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
|
||||
# form is used to display individual task panels
|
||||
app = obj.ViewObject.Proxy.get_app_edit_widget(self)
|
||||
app.setWindowTitle("Data extraction")
|
||||
app.setWindowIcon(obj.ViewObject.Icon)
|
||||
view = obj.ViewObject.Proxy.get_view_edit_widget(self)
|
||||
view.setWindowTitle("Visualization settings")
|
||||
view.setWindowIcon(obj.ViewObject.Icon)
|
||||
|
||||
self.form = [app, view]
|
||||
@@ -35,10 +35,10 @@ import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from femguiutils import selection_widgets
|
||||
from . import base_femtaskpanel
|
||||
from . import base_fempostpanel
|
||||
|
||||
|
||||
class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
class _TaskPanel(base_fempostpanel._BasePostTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing properties of glyph filter
|
||||
"""
|
||||
@@ -56,50 +56,6 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
# form made from param and selection widget
|
||||
self.form = [self.widget, vobj.createDisplayTaskWidget()]
|
||||
|
||||
# get the settings group
|
||||
self.__settings_grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
|
||||
|
||||
# Implement parent functions
|
||||
# ##########################
|
||||
|
||||
def getStandardButtons(self):
|
||||
return (
|
||||
QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel
|
||||
)
|
||||
|
||||
def clicked(self, button):
|
||||
# apply button hit?
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.obj.Document.recompute()
|
||||
|
||||
def accept(self):
|
||||
# self.obj.CharacteristicLength = self.elelen
|
||||
# self.obj.References = self.selection_widget.references
|
||||
# self.selection_widget.finish_selection()
|
||||
return super().accept()
|
||||
|
||||
def reject(self):
|
||||
# self.selection_widget.finish_selection()
|
||||
return super().reject()
|
||||
|
||||
# Helper functions
|
||||
# ##################
|
||||
|
||||
def _recompute(self):
|
||||
# only recompute if the user wants automatic recompute
|
||||
if self.__settings_grp.GetBool("PostAutoRecompute", True):
|
||||
self.obj.Document.recompute()
|
||||
|
||||
def _enumPropertyToCombobox(self, obj, prop, cbox):
|
||||
cbox.blockSignals(True)
|
||||
cbox.clear()
|
||||
entries = obj.getEnumerationsOfProperty(prop)
|
||||
for entry in entries:
|
||||
cbox.addItem(entry)
|
||||
|
||||
cbox.setCurrentText(getattr(obj, prop))
|
||||
cbox.blockSignals(False)
|
||||
|
||||
# Setup functions
|
||||
# ###############
|
||||
|
||||
|
||||
194
src/Mod/Fem/femtaskpanels/task_post_histogram.py
Normal file
194
src/Mod/Fem/femtaskpanels/task_post_histogram.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# ***************************************************************************
|
||||
# * 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
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class _TaskPanel(base_fempostpanel._BasePostTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing properties of glyph filter
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj.Object)
|
||||
|
||||
# data widget
|
||||
self.data_widget = QtGui.QWidget()
|
||||
hbox = QtGui.QHBoxLayout()
|
||||
self.data_widget.show_plot = QtGui.QPushButton()
|
||||
self.data_widget.show_plot.setText(translate("FEM", "Show plot"))
|
||||
hbox.addWidget(self.data_widget.show_plot)
|
||||
self.data_widget.show_table = QtGui.QPushButton()
|
||||
self.data_widget.show_table.setText(translate("FEM", "Show data"))
|
||||
hbox.addWidget(self.data_widget.show_table)
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.addItem(hbox)
|
||||
vbox.addSpacing(10)
|
||||
|
||||
extracts = elv.ExtractLinkView(self.obj, False, self)
|
||||
vbox.addWidget(extracts)
|
||||
|
||||
self.data_widget.setLayout(vbox)
|
||||
self.data_widget.setWindowTitle(translate("FEM", "Histogram data"))
|
||||
self.data_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostHistogram.svg"))
|
||||
|
||||
# histogram parameter widget
|
||||
self.view_widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostHistogram.ui"
|
||||
)
|
||||
self.view_widget.setWindowTitle(translate("FEM", "Histogram view settings"))
|
||||
self.view_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostHistogram.svg"))
|
||||
|
||||
self.__init_widgets()
|
||||
|
||||
# form made from param and selection widget
|
||||
self.form = [self.data_widget, self.view_widget]
|
||||
|
||||
# Setup functions
|
||||
# ###############
|
||||
|
||||
def __init_widgets(self):
|
||||
|
||||
# connect data widget
|
||||
self.data_widget.show_plot.clicked.connect(self.showPlot)
|
||||
self.data_widget.show_table.clicked.connect(self.showTable)
|
||||
|
||||
# set current values to view widget
|
||||
viewObj = self.obj.ViewObject
|
||||
|
||||
self.view_widget.Bins.setValue(viewObj.Bins)
|
||||
self._enumPropertyToCombobox(viewObj, "Type", self.view_widget.Type)
|
||||
self.view_widget.Cumulative.setChecked(viewObj.Cumulative)
|
||||
|
||||
self.view_widget.Title.setText(viewObj.Title)
|
||||
self.view_widget.XLabel.setText(viewObj.XLabel)
|
||||
self.view_widget.YLabel.setText(viewObj.YLabel)
|
||||
|
||||
self.view_widget.LegendShow.setChecked(viewObj.Legend)
|
||||
self._enumPropertyToCombobox(viewObj, "LegendLocation", self.view_widget.LegendPos)
|
||||
self.view_widget.BarWidth.setValue(viewObj.BarWidth)
|
||||
self.view_widget.HatchWidth.setValue(viewObj.HatchLineWidth)
|
||||
|
||||
# connect callbacks
|
||||
self.view_widget.Bins.valueChanged.connect(self.binsChanged)
|
||||
self.view_widget.Type.activated.connect(self.typeChanged)
|
||||
self.view_widget.Cumulative.toggled.connect(self.comulativeChanged)
|
||||
|
||||
self.view_widget.Title.editingFinished.connect(self.titleChanged)
|
||||
self.view_widget.XLabel.editingFinished.connect(self.xLabelChanged)
|
||||
self.view_widget.YLabel.editingFinished.connect(self.yLabelChanged)
|
||||
|
||||
self.view_widget.LegendShow.toggled.connect(self.legendShowChanged)
|
||||
self.view_widget.LegendPos.activated.connect(self.legendPosChanged)
|
||||
self.view_widget.BarWidth.valueChanged.connect(self.barWidthChanged)
|
||||
self.view_widget.HatchWidth.valueChanged.connect(self.hatchWidthChanged)
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def showPlot(self):
|
||||
self.obj.ViewObject.Proxy.show_visualization()
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def showTable(self):
|
||||
|
||||
# TODO: make data model update when object is recomputed
|
||||
data_model = vtk_table_view.VtkTableModel()
|
||||
data_model.setTable(self.obj.Table)
|
||||
|
||||
dialog = QtGui.QDialog(self.data_widget)
|
||||
widget = vtk_table_view.VtkTableView(data_model)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(widget)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
dialog.setLayout(layout)
|
||||
dialog.resize(1500, 900)
|
||||
dialog.show()
|
||||
|
||||
QtCore.Slot(int)
|
||||
|
||||
def binsChanged(self, bins):
|
||||
self.obj.ViewObject.Bins = bins
|
||||
|
||||
QtCore.Slot(int)
|
||||
|
||||
def typeChanged(self, idx):
|
||||
self.obj.ViewObject.Type = idx
|
||||
|
||||
QtCore.Slot(bool)
|
||||
|
||||
def comulativeChanged(self, state):
|
||||
self.obj.ViewObject.Cumulative = state
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def titleChanged(self):
|
||||
self.obj.ViewObject.Title = self.view_widget.Title.text()
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def xLabelChanged(self):
|
||||
self.obj.ViewObject.XLabel = self.view_widget.XLabel.text()
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def yLabelChanged(self):
|
||||
self.obj.ViewObject.YLabel = self.view_widget.YLabel.text()
|
||||
|
||||
QtCore.Slot(int)
|
||||
|
||||
def legendPosChanged(self, idx):
|
||||
self.obj.ViewObject.LegendLocation = idx
|
||||
|
||||
QtCore.Slot(bool)
|
||||
|
||||
def legendShowChanged(self, state):
|
||||
self.obj.ViewObject.Legend = state
|
||||
|
||||
QtCore.Slot(float)
|
||||
|
||||
def barWidthChanged(self, value):
|
||||
self.obj.ViewObject.BarWidth = value
|
||||
|
||||
QtCore.Slot(float)
|
||||
|
||||
def hatchWidthChanged(self, value):
|
||||
self.obj.ViewObject.HatchLineWidth = value
|
||||
173
src/Mod/Fem/femtaskpanels/task_post_lineplot.py
Normal file
173
src/Mod/Fem/femtaskpanels/task_post_lineplot.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM lineplot plot task panel"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package task_post_lineplot
|
||||
# \ingroup FEM
|
||||
# \brief task panel for post lineplot plot
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from . import base_fempostpanel
|
||||
from femguiutils import extract_link_view as elv
|
||||
from femguiutils import vtk_table_view
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class _TaskPanel(base_fempostpanel._BasePostTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing properties of glyph filter
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj.Object)
|
||||
|
||||
# data widget
|
||||
self.data_widget = QtGui.QWidget()
|
||||
hbox = QtGui.QHBoxLayout()
|
||||
self.data_widget.show_plot = QtGui.QPushButton()
|
||||
self.data_widget.show_plot.setText(translate("FEM", "Show plot"))
|
||||
hbox.addWidget(self.data_widget.show_plot)
|
||||
self.data_widget.show_table = QtGui.QPushButton()
|
||||
self.data_widget.show_table.setText(translate("FEM", "Show data"))
|
||||
hbox.addWidget(self.data_widget.show_table)
|
||||
vbox = QtGui.QVBoxLayout()
|
||||
vbox.addItem(hbox)
|
||||
vbox.addSpacing(10)
|
||||
|
||||
extracts = elv.ExtractLinkView(self.obj, False, self)
|
||||
vbox.addWidget(extracts)
|
||||
|
||||
self.data_widget.setLayout(vbox)
|
||||
self.data_widget.setWindowTitle(translate("FEM", "Lineplot data"))
|
||||
self.data_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostLineplot.svg"))
|
||||
|
||||
# lineplot parameter widget
|
||||
self.view_widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/TaskPostLineplot.ui"
|
||||
)
|
||||
self.view_widget.setWindowTitle(translate("FEM", "Lineplot view settings"))
|
||||
self.view_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostLineplot.svg"))
|
||||
|
||||
self.__init_widgets()
|
||||
|
||||
# form made from param and selection widget
|
||||
self.form = [self.data_widget, self.view_widget]
|
||||
|
||||
# Setup functions
|
||||
# ###############
|
||||
|
||||
def __init_widgets(self):
|
||||
|
||||
# connect data widget
|
||||
self.data_widget.show_plot.clicked.connect(self.showPlot)
|
||||
self.data_widget.show_table.clicked.connect(self.showTable)
|
||||
|
||||
# set current values to view widget
|
||||
viewObj = self.obj.ViewObject
|
||||
|
||||
self._enumPropertyToCombobox(viewObj, "Scale", self.view_widget.Scale)
|
||||
self.view_widget.Grid.setChecked(viewObj.Grid)
|
||||
|
||||
self.view_widget.Title.setText(viewObj.Title)
|
||||
self.view_widget.XLabel.setText(viewObj.XLabel)
|
||||
self.view_widget.YLabel.setText(viewObj.YLabel)
|
||||
|
||||
self.view_widget.LegendShow.setChecked(viewObj.Legend)
|
||||
self._enumPropertyToCombobox(viewObj, "LegendLocation", self.view_widget.LegendPos)
|
||||
|
||||
# connect callbacks
|
||||
self.view_widget.Scale.activated.connect(self.scaleChanged)
|
||||
self.view_widget.Grid.toggled.connect(self.gridChanged)
|
||||
|
||||
self.view_widget.Title.editingFinished.connect(self.titleChanged)
|
||||
self.view_widget.XLabel.editingFinished.connect(self.xLabelChanged)
|
||||
self.view_widget.YLabel.editingFinished.connect(self.yLabelChanged)
|
||||
|
||||
self.view_widget.LegendShow.toggled.connect(self.legendShowChanged)
|
||||
self.view_widget.LegendPos.activated.connect(self.legendPosChanged)
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def showPlot(self):
|
||||
self.obj.ViewObject.Proxy.show_visualization()
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def showTable(self):
|
||||
|
||||
# TODO: make data model update when object is recomputed
|
||||
data_model = vtk_table_view.VtkTableModel()
|
||||
data_model.setTable(self.obj.Table)
|
||||
|
||||
dialog = QtGui.QDialog(self.data_widget)
|
||||
widget = vtk_table_view.VtkTableView(data_model)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(widget)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
dialog.setLayout(layout)
|
||||
dialog.resize(1500, 900)
|
||||
dialog.show()
|
||||
|
||||
QtCore.Slot(int)
|
||||
|
||||
def scaleChanged(self, idx):
|
||||
self.obj.ViewObject.Scale = idx
|
||||
|
||||
QtCore.Slot(bool)
|
||||
|
||||
def gridChanged(self, state):
|
||||
self.obj.ViewObject.Grid = state
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def titleChanged(self):
|
||||
self.obj.ViewObject.Title = self.view_widget.Title.text()
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def xLabelChanged(self):
|
||||
self.obj.ViewObject.XLabel = self.view_widget.XLabel.text()
|
||||
|
||||
QtCore.Slot()
|
||||
|
||||
def yLabelChanged(self):
|
||||
self.obj.ViewObject.YLabel = self.view_widget.YLabel.text()
|
||||
|
||||
QtCore.Slot(int)
|
||||
|
||||
def legendPosChanged(self, idx):
|
||||
self.obj.ViewObject.LegendLocation = idx
|
||||
|
||||
QtCore.Slot(bool)
|
||||
|
||||
def legendShowChanged(self, state):
|
||||
self.obj.ViewObject.Legend = state
|
||||
82
src/Mod/Fem/femtaskpanels/task_post_table.py
Normal file
82
src/Mod/Fem/femtaskpanels/task_post_table.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# ***************************************************************************
|
||||
# * 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
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
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(translate("FEM", "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(translate("FEM", "Table data"))
|
||||
self.data_widget.setWindowIcon(FreeCADGui.getIcon(":/icons/FEM_PostSpreadsheet.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)
|
||||
|
||||
@QtCore.Slot()
|
||||
def showTable(self):
|
||||
self.obj.ViewObject.Proxy.show_visualization()
|
||||
@@ -79,12 +79,16 @@ class TestObjectCreate(unittest.TestCase):
|
||||
# gmsh mesh children: group, region, boundary layer --> 3
|
||||
# result children: mesh result --> 1
|
||||
# analysis itself is not in analysis group --> 1
|
||||
# vtk post pipeline children: region, scalar, cut, wrap, glyph --> 5
|
||||
# vtk python post objects: glyph --> 1
|
||||
# vtk post pipeline children: region, scalar, cut, wrap, contour --> 5
|
||||
# vtk python post objects: glyph, 6x data extraction --> 7
|
||||
|
||||
subtraction = 15
|
||||
if vtk_objects_used:
|
||||
subtraction += 6
|
||||
subtraction += 12
|
||||
if not ("BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__):
|
||||
# remove the 3 data visualization objects that would be in the Analysis
|
||||
# if they would be available (Lineplot, histogram, table)
|
||||
subtraction += 3
|
||||
|
||||
self.assertEqual(len(doc.Analysis.Group), count_defmake - subtraction)
|
||||
|
||||
@@ -92,7 +96,9 @@ class TestObjectCreate(unittest.TestCase):
|
||||
# have been counted, but will not be executed to create objects
|
||||
failed = 0
|
||||
if vtk_objects_used and not ("BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__):
|
||||
failed += 1
|
||||
# the 7 objects also counted in subtraction, +3 additional objects that are
|
||||
# added directly to the analysis
|
||||
failed += 10
|
||||
|
||||
self.assertEqual(len(doc.Objects), count_defmake - failed)
|
||||
|
||||
@@ -1167,6 +1173,19 @@ def create_all_fem_objects_doc(doc):
|
||||
if "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__:
|
||||
ObjectsFem.makePostFilterGlyph(doc, vres)
|
||||
|
||||
# data extraction objects
|
||||
lp = analysis.addObject(ObjectsFem.makePostLineplot(doc))[0]
|
||||
lp.addObject(ObjectsFem.makePostLineplotFieldData(doc))
|
||||
lp.addObject(ObjectsFem.makePostLineplotIndexOverFrames(doc))
|
||||
|
||||
hp = analysis.addObject(ObjectsFem.makePostHistogram(doc))[0]
|
||||
hp.addObject(ObjectsFem.makePostHistogramFieldData(doc))
|
||||
hp.addObject(ObjectsFem.makePostHistogramIndexOverFrames(doc))
|
||||
|
||||
tb = analysis.addObject(ObjectsFem.makePostTable(doc))[0]
|
||||
tb.addObject(ObjectsFem.makePostTableFieldData(doc))
|
||||
tb.addObject(ObjectsFem.makePostTableIndexOverFrames(doc))
|
||||
|
||||
analysis.addObject(ObjectsFem.makeSolverCalculiXCcxTools(doc))
|
||||
analysis.addObject(ObjectsFem.makeSolverCalculiX(doc))
|
||||
sol = analysis.addObject(ObjectsFem.makeSolverElmer(doc))[0]
|
||||
|
||||
@@ -36,9 +36,27 @@ import FreeCADGui
|
||||
|
||||
import FemGui # needed to display the icons in TreeView
|
||||
|
||||
from femobjects.base_fempythonobject import _PropHelper
|
||||
|
||||
False if FemGui.__name__ else True # flake8, dummy FemGui usage
|
||||
|
||||
|
||||
class _GuiPropHelper(_PropHelper):
|
||||
"""
|
||||
Helper class to manage property data inside proxy objects.
|
||||
Based on the App verison, but viewprovider addProperty does
|
||||
not take keyword args, hence we use positional arguments here
|
||||
"""
|
||||
|
||||
def __init__(self, **kwds):
|
||||
super().__init__(**kwds)
|
||||
|
||||
def add_to_object(self, obj):
|
||||
obj.addProperty(self.info["type"], self.info["name"], self.info["group"], self.info["doc"])
|
||||
obj.setPropertyStatus(self.name, "LockDynamic")
|
||||
setattr(obj, self.name, self.value)
|
||||
|
||||
|
||||
class VPBaseFemObject:
|
||||
"""Proxy View Provider for FEM FeaturePythons base constraint."""
|
||||
|
||||
|
||||
148
src/Mod/Fem/femviewprovider/view_base_fempostextractors.py
Normal file
148
src/Mod/Fem/femviewprovider/view_base_fempostextractors.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# ***************************************************************************
|
||||
# * 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 line plot ViewProvider for the document object"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package view_post_lineplot
|
||||
# \ingroup FEM
|
||||
# \brief view provider for post line plot object
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
from PySide import QtGui
|
||||
|
||||
from femtaskpanels import task_post_extractor
|
||||
|
||||
|
||||
class VPPostExtractor:
|
||||
"""
|
||||
A View Provider for extraction of data
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self._setup_properties(vobj)
|
||||
|
||||
def _setup_properties(self, vobj):
|
||||
pl = vobj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
if not prop.name in pl:
|
||||
prop.add_to_object(vobj)
|
||||
|
||||
def _get_properties(self):
|
||||
return []
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object # used on various places, claim childreens, get icon, etc.
|
||||
self.ViewObject = vobj
|
||||
|
||||
def isShow(self):
|
||||
return True
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
|
||||
# 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":
|
||||
return
|
||||
|
||||
group = vobj.Object.getParentGroup()
|
||||
if not group:
|
||||
return
|
||||
|
||||
if hasattr(group.ViewObject, "Proxy") and hasattr(
|
||||
group.ViewObject.Proxy, "childViewPropertyChanged"
|
||||
):
|
||||
|
||||
group.ViewObject.Proxy.childViewPropertyChanged(vobj, prop)
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
# build up the task panel
|
||||
taskd = task_post_extractor._ExtractorTaskPanel(vobj.Object)
|
||||
|
||||
# show it
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode=0):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
guidoc = FreeCADGui.getDocument(vobj.Object.Document)
|
||||
|
||||
# check if another VP is in edit mode and close it then
|
||||
if guidoc.getInEdit():
|
||||
FreeCADGui.Control.closeDialog()
|
||||
guidoc.resetEdit()
|
||||
|
||||
guidoc.setEdit(vobj.Object.Name)
|
||||
|
||||
return True
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
# To be implemented by subclasses:
|
||||
# ################################
|
||||
|
||||
def get_default_color_property(self):
|
||||
# Returns the property name to set the default color to.
|
||||
# Return None if no such property
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_default_field_properties(self):
|
||||
# Returns the property name to which the default field name should be set
|
||||
# ret: [FieldProperty, ComponentProperty]
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_kw_args(self):
|
||||
# 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!)
|
||||
# The widget will be part of the provided post_dialog, and
|
||||
# should use its functionality to inform of changes.
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_view_edit_widget(self, post_dialog):
|
||||
# Returns a widgets for editing the viewprovider (not object!)
|
||||
# The widget will be part of the provided post_dialog, and
|
||||
# should use its functionality to inform of changes.
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
|
||||
def get_preview(self):
|
||||
# Returns the preview tuple of icon and label: (QPixmap, str)
|
||||
# Note: QPixmap in ratio 2:1
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
124
src/Mod/Fem/femviewprovider/view_base_fempostvisualization.py
Normal file
124
src/Mod/Fem/femviewprovider/view_base_fempostvisualization.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# ***************************************************************************
|
||||
# * 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 visualization base ViewProvider"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package view_base_fempostvisualizations
|
||||
# \ingroup FEM
|
||||
# \brief view provider for post visualization object
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
|
||||
class VPPostVisualization:
|
||||
"""
|
||||
A View Provider for visualization objects
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self._setup_properties(vobj)
|
||||
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
||||
|
||||
def _setup_properties(self, vobj):
|
||||
pl = vobj.PropertiesList
|
||||
for prop in self._get_properties():
|
||||
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 getDisplayModes(self, obj):
|
||||
return ["Dialog"]
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
|
||||
guidoc = FreeCADGui.getDocument(vobj.Object.Document)
|
||||
|
||||
# check if another VP is in edit mode and close it then
|
||||
if guidoc.getInEdit():
|
||||
FreeCADGui.Control.closeDialog()
|
||||
guidoc.resetEdit()
|
||||
|
||||
# open task dialog
|
||||
guidoc.setEdit(vobj.Object.Name)
|
||||
|
||||
# show visualization
|
||||
self.show_visualization()
|
||||
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode=0):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
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")
|
||||
|
||||
def get_next_default_color(self):
|
||||
# Returns the next default color a new object should use
|
||||
# Returns color in FreeCAD proeprty notation (r,g,b,a)
|
||||
# If the relevant extractors do not have color properties, this
|
||||
# can stay unimplemented
|
||||
raise FreeCAD.Base.FreeCADError("Not implemented")
|
||||
603
src/Mod/Fem/femviewprovider/view_post_histogram.py
Normal file
603
src/Mod/Fem/femviewprovider/view_post_histogram.py
Normal file
@@ -0,0 +1,603 @@
|
||||
# ***************************************************************************
|
||||
# * 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 line plot ViewProvider for the document object"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package view_post_lineplot
|
||||
# \ingroup FEM
|
||||
# \brief view provider for post line plot object
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import Plot
|
||||
from PySide import QtGui, QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import io
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
from packaging.version import Version
|
||||
|
||||
from vtkmodules.numpy_interface.dataset_adapter import VTKArray
|
||||
|
||||
from . import view_base_fempostextractors
|
||||
from . import view_base_fempostvisualization
|
||||
from femtaskpanels import task_post_histogram
|
||||
|
||||
from . import view_base_femobject
|
||||
|
||||
_GuiPropHelper = view_base_femobject._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/PostHistogramFieldViewEdit.ui"
|
||||
)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.__init_widget()
|
||||
|
||||
def __init_widget(self):
|
||||
vobj = self._object.ViewObject
|
||||
|
||||
self.widget.Legend.setText(vobj.Legend)
|
||||
self._post_dialog._enumPropertyToCombobox(vobj, "Hatch", self.widget.Hatch)
|
||||
self._post_dialog._enumPropertyToCombobox(vobj, "LineStyle", self.widget.LineStyle)
|
||||
self.widget.LineWidth.setValue(vobj.LineWidth)
|
||||
self.widget.HatchDensity.setValue(vobj.HatchDensity)
|
||||
|
||||
# setup the color buttons (don't use FreeCADs color button, as this does not work in popups!)
|
||||
self._setup_color_button(self.widget.BarColor, vobj.BarColor, self.barColorChanged)
|
||||
self._setup_color_button(self.widget.LineColor, vobj.LineColor, self.lineColorChanged)
|
||||
|
||||
self.widget.Legend.editingFinished.connect(self.legendChanged)
|
||||
self.widget.Hatch.activated.connect(self.hatchPatternChanged)
|
||||
self.widget.LineStyle.activated.connect(self.lineStyleChanged)
|
||||
self.widget.HatchDensity.valueChanged.connect(self.hatchDensityChanged)
|
||||
self.widget.LineWidth.valueChanged.connect(self.lineWidthChanged)
|
||||
|
||||
# sometimes wierd sizes occur with spinboxes
|
||||
self.widget.HatchDensity.setMaximumHeight(self.widget.Hatch.sizeHint().height())
|
||||
self.widget.LineWidth.setMaximumHeight(self.widget.LineStyle.sizeHint().height())
|
||||
|
||||
def _setup_color_button(self, button, fcColor, callback):
|
||||
|
||||
barColor = QtGui.QColor(*[v * 255 for v in fcColor])
|
||||
icon_size = button.iconSize()
|
||||
icon_size.setWidth(icon_size.width() * 2)
|
||||
button.setIconSize(icon_size)
|
||||
pixmap = QtGui.QPixmap(icon_size)
|
||||
pixmap.fill(barColor)
|
||||
button.setIcon(pixmap)
|
||||
|
||||
action = QtGui.QWidgetAction(button)
|
||||
diag = QtGui.QColorDialog(barColor, parent=button)
|
||||
diag.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
|
||||
diag.accepted.connect(action.trigger)
|
||||
diag.rejected.connect(action.trigger)
|
||||
diag.colorSelected.connect(callback)
|
||||
|
||||
action.setDefaultWidget(diag)
|
||||
button.addAction(action)
|
||||
button.setPopupMode(QtGui.QToolButton.InstantPopup)
|
||||
|
||||
@QtCore.Slot(QtGui.QColor)
|
||||
def lineColorChanged(self, color):
|
||||
|
||||
pixmap = QtGui.QPixmap(self.widget.LineColor.iconSize())
|
||||
pixmap.fill(color)
|
||||
self.widget.LineColor.setIcon(pixmap)
|
||||
|
||||
self._object.ViewObject.LineColor = color.getRgb()
|
||||
|
||||
@QtCore.Slot(QtGui.QColor)
|
||||
def barColorChanged(self, color):
|
||||
|
||||
pixmap = QtGui.QPixmap(self.widget.BarColor.iconSize())
|
||||
pixmap.fill(color)
|
||||
self.widget.BarColor.setIcon(pixmap)
|
||||
|
||||
self._object.ViewObject.BarColor = color.getRgb()
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def lineWidthChanged(self, value):
|
||||
self._object.ViewObject.LineWidth = value
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def hatchDensityChanged(self, value):
|
||||
self._object.ViewObject.HatchDensity = value
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def hatchPatternChanged(self, index):
|
||||
self._object.ViewObject.Hatch = index
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def lineStyleChanged(self, index):
|
||||
self._object.ViewObject.LineStyle = index
|
||||
|
||||
@QtCore.Slot()
|
||||
def legendChanged(self):
|
||||
self._object.ViewObject.Legend = self.widget.Legend.text()
|
||||
|
||||
|
||||
class 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
|
||||
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
|
||||
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 VPPostHistogramFieldData(view_base_fempostextractors.VPPostExtractor):
|
||||
"""
|
||||
A View Provider for extraction of 1D field data specialy for histograms
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
vobj.Proxy = self
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Legend",
|
||||
group="HistogramPlot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The name used in the plots legend"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyColor",
|
||||
name="BarColor",
|
||||
group="HistogramBar",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The color the data bin area is drawn with"),
|
||||
value=(0, 85, 255, 255),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="Hatch",
|
||||
group="HistogramBar",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The hatch pattern drawn in the bar"),
|
||||
value=["None", "/", "\\", "|", "-", "+", "x", "o", "O", ".", "*"],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyIntegerConstraint",
|
||||
name="HatchDensity",
|
||||
group="HistogramBar",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The line width of the hatch)"),
|
||||
value=(1, 1, 99, 1),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyColor",
|
||||
name="LineColor",
|
||||
group="HistogramLine",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The color the data bin area is drawn with"),
|
||||
value=(0, 0, 0, 1), # black
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="LineWidth",
|
||||
group="HistogramLine",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "The width of the bar, between 0 and 1 (1 being without gaps)"
|
||||
),
|
||||
value=(1, 0, 99, 0.1),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="LineStyle",
|
||||
group="HistogramLine",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The style the line is drawn in"),
|
||||
value=["None", "-", "--", "-.", ":"],
|
||||
),
|
||||
]
|
||||
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):
|
||||
|
||||
fig = mpl.pyplot.figure(figsize=(0.4, 0.2), dpi=500)
|
||||
ax = mpl.pyplot.Axes(fig, [0.0, 0.0, 2, 1])
|
||||
ax.set_axis_off()
|
||||
fig.add_axes(ax)
|
||||
|
||||
kwargs = self.get_kw_args()
|
||||
patch = mpl.patches.Rectangle(xy=(0, 0), width=2, height=1, **kwargs)
|
||||
ax.add_patch(patch)
|
||||
|
||||
data = io.BytesIO()
|
||||
mpl.pyplot.savefig(data, bbox_inches=0, transparent=True)
|
||||
mpl.pyplot.close()
|
||||
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.loadFromData(data.getvalue())
|
||||
|
||||
return (pixmap, self.ViewObject.Legend)
|
||||
|
||||
def get_kw_args(self):
|
||||
# builds kw args from the properties
|
||||
kwargs = {}
|
||||
|
||||
# colors need a workaround, some error occurs with rgba tuple
|
||||
kwargs["edgecolor"] = self.ViewObject.LineColor
|
||||
kwargs["facecolor"] = self.ViewObject.BarColor
|
||||
kwargs["linestyle"] = self.ViewObject.LineStyle
|
||||
kwargs["linewidth"] = self.ViewObject.LineWidth
|
||||
if self.ViewObject.Hatch != "None":
|
||||
kwargs["hatch"] = self.ViewObject.Hatch * self.ViewObject.HatchDensity
|
||||
|
||||
return kwargs
|
||||
|
||||
def get_default_color_property(self):
|
||||
return "BarColor"
|
||||
|
||||
|
||||
class VPPostHistogramIndexOverFrames(VPPostHistogramFieldData):
|
||||
"""
|
||||
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 VPPostHistogram(view_base_fempostvisualization.VPPostVisualization):
|
||||
"""
|
||||
A View Provider for Histogram plots
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Cumulative",
|
||||
group="Histogram",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "If be the bars shoud show the cumulative sum left to rigth"
|
||||
),
|
||||
value=False,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="Type",
|
||||
group="Histogram",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The type of histogram plotted"),
|
||||
value=["bar", "barstacked", "step", "stepfilled"],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="BarWidth",
|
||||
group="Histogram",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "The width of the bar, between 0 and 1 (1 being without gaps)"
|
||||
),
|
||||
value=(0.9, 0, 1, 0.05),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="HatchLineWidth",
|
||||
group="Histogram",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The line width of all drawn hatch patterns"),
|
||||
value=(1, 0, 99, 0.1),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyInteger",
|
||||
name="Bins",
|
||||
group="Histogram",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The number of bins the data is split into"),
|
||||
value=10,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Title",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The histogram plot title"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="XLabel",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The label shown for the histogram X axis"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="YLabel",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The label shown for the histogram Y axis"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Legend",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "Determines if the legend is plotted"),
|
||||
value=True,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="LegendLocation",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "Determines if the legend is plotted"),
|
||||
value=[
|
||||
"best",
|
||||
"upper right",
|
||||
"upper left",
|
||||
"lower left",
|
||||
"lower right",
|
||||
"right",
|
||||
"center left",
|
||||
"center right",
|
||||
"lower center",
|
||||
"upper center",
|
||||
"center",
|
||||
],
|
||||
),
|
||||
]
|
||||
return prop
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostHistogram.svg"
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
# build up the task panel
|
||||
taskd = task_post_histogram._TaskPanel(vobj)
|
||||
|
||||
# show it
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
return True
|
||||
|
||||
def show_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
main = FreeCADGui.getMainWindow()
|
||||
self._plot = Plot.Plot()
|
||||
self._plot.setWindowTitle(self.Object.Label)
|
||||
self._plot.setParent(main)
|
||||
self._plot.setWindowFlags(QtGui.Qt.Dialog)
|
||||
self._plot.resize(main.size().height() / 2, main.size().height() / 3) # keep it square
|
||||
self.update_visualization()
|
||||
|
||||
self._plot.show()
|
||||
|
||||
def get_kw_args(self, obj):
|
||||
view = obj.ViewObject
|
||||
if not view or not hasattr(view, "Proxy"):
|
||||
return {}
|
||||
if not hasattr(view.Proxy, "get_kw_args"):
|
||||
return {}
|
||||
return view.Proxy.get_kw_args()
|
||||
|
||||
def update_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
return
|
||||
|
||||
self._plot.axes.clear()
|
||||
bins = self.ViewObject.Bins
|
||||
|
||||
# we do not iterate the table, but iterate the children. This makes it possible
|
||||
# to attribute the correct styles
|
||||
full_args = []
|
||||
full_data = []
|
||||
labels = []
|
||||
for child in self.Object.Group:
|
||||
|
||||
table = child.Table
|
||||
kwargs = self.get_kw_args(child)
|
||||
|
||||
# iterate over the table and plot all
|
||||
color_factor = np.linspace(1, 0.5, table.GetNumberOfColumns())
|
||||
legend_multiframe = table.GetNumberOfColumns() > 1
|
||||
for i in range(table.GetNumberOfColumns()):
|
||||
|
||||
# add the kw args, with some slide change over color for multiple frames
|
||||
args = kwargs.copy()
|
||||
for key in kwargs:
|
||||
|
||||
if "color" in key:
|
||||
value = np.array(kwargs[key]) * color_factor[i]
|
||||
args[key] = mpl.colors.to_hex(value)
|
||||
|
||||
full_args.append(args)
|
||||
|
||||
data = VTKArray(table.GetColumn(i))
|
||||
full_data.append(data)
|
||||
|
||||
# legend labels
|
||||
if child.ViewObject.Legend:
|
||||
if not legend_multiframe:
|
||||
labels.append(child.ViewObject.Legend)
|
||||
else:
|
||||
postfix = table.GetColumnName(i).split("-")[-1]
|
||||
labels.append(child.ViewObject.Legend + " - " + postfix)
|
||||
else:
|
||||
legend_prefix = ""
|
||||
if len(self.Object.Group) > 1:
|
||||
legend_prefix = child.Source.Label + ": "
|
||||
labels.append(legend_prefix + table.GetColumnName(i))
|
||||
|
||||
args = {}
|
||||
args["rwidth"] = self.ViewObject.BarWidth
|
||||
args["cumulative"] = self.ViewObject.Cumulative
|
||||
args["histtype"] = self.ViewObject.Type
|
||||
args["label"] = labels
|
||||
if Version(mpl.__version__) >= Version("3.10.0"):
|
||||
args["hatch_linewidth"] = self.ViewObject.HatchLineWidth
|
||||
|
||||
n, b, patches = self._plot.axes.hist(full_data, bins, **args)
|
||||
|
||||
# set the patches view properties.
|
||||
if len(full_args) == 1:
|
||||
for patch in patches:
|
||||
patch.set(**full_args[0])
|
||||
elif len(full_args) > 1:
|
||||
for i, args in enumerate(full_args):
|
||||
for patch in patches[i]:
|
||||
patch.set(**full_args[i])
|
||||
|
||||
# axes decoration
|
||||
if self.ViewObject.Title:
|
||||
self._plot.axes.set_title(self.ViewObject.Title)
|
||||
if self.ViewObject.XLabel:
|
||||
self._plot.axes.set_xlabel(self.ViewObject.XLabel)
|
||||
if self.ViewObject.YLabel:
|
||||
self._plot.axes.set_ylabel(self.ViewObject.YLabel)
|
||||
|
||||
if self.ViewObject.Legend and labels:
|
||||
self._plot.axes.legend(loc=self.ViewObject.LegendLocation)
|
||||
|
||||
self._plot.update()
|
||||
|
||||
def get_next_default_color(self):
|
||||
# we use the next color in order. We do not check (yet) if this
|
||||
# color is already taken
|
||||
i = len(self.Object.Group)
|
||||
cmap = mpl.pyplot.get_cmap("tab10")
|
||||
return cmap(i)
|
||||
583
src/Mod/Fem/femviewprovider/view_post_lineplot.py
Normal file
583
src/Mod/Fem/femviewprovider/view_post_lineplot.py
Normal file
@@ -0,0 +1,583 @@
|
||||
# ***************************************************************************
|
||||
# * 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 line plot ViewProvider for the document object"
|
||||
__author__ = "Stefan Tröger"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package view_post_lineplot
|
||||
# \ingroup FEM
|
||||
# \brief view provider for post line plot object
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import Plot
|
||||
from PySide import QtGui, QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
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_lineplot
|
||||
|
||||
from . import view_base_femobject
|
||||
|
||||
_GuiPropHelper = view_base_femobject._GuiPropHelper
|
||||
|
||||
|
||||
class EditViewWidget(QtGui.QWidget):
|
||||
|
||||
def __init__(self, obj, post_dialog):
|
||||
super().__init__()
|
||||
|
||||
self._object = obj
|
||||
self._post_dialog = post_dialog
|
||||
|
||||
# load the ui and set it up
|
||||
self.widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostLineplotFieldViewEdit.ui"
|
||||
)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.__init_widget()
|
||||
|
||||
def __init_widget(self):
|
||||
vobj = self._object.ViewObject
|
||||
|
||||
self.widget.Legend.setText(vobj.Legend)
|
||||
self._post_dialog._enumPropertyToCombobox(vobj, "LineStyle", self.widget.LineStyle)
|
||||
self._post_dialog._enumPropertyToCombobox(vobj, "MarkerStyle", self.widget.MarkerStyle)
|
||||
self.widget.LineWidth.setValue(vobj.LineWidth)
|
||||
self.widget.MarkerSize.setValue(vobj.MarkerSize)
|
||||
|
||||
self._setup_color_button(self.widget.Color, vobj.Color, self.colorChanged)
|
||||
|
||||
self.widget.Legend.editingFinished.connect(self.legendChanged)
|
||||
self.widget.MarkerStyle.activated.connect(self.markerStyleChanged)
|
||||
self.widget.LineStyle.activated.connect(self.lineStyleChanged)
|
||||
self.widget.MarkerSize.valueChanged.connect(self.markerSizeChanged)
|
||||
self.widget.LineWidth.valueChanged.connect(self.lineWidthChanged)
|
||||
|
||||
# sometimes wierd sizes occur with spinboxes
|
||||
self.widget.MarkerSize.setMaximumHeight(self.widget.MarkerStyle.sizeHint().height())
|
||||
self.widget.LineWidth.setMaximumHeight(self.widget.LineStyle.sizeHint().height())
|
||||
|
||||
def _setup_color_button(self, button, fcColor, callback):
|
||||
|
||||
barColor = QtGui.QColor(*[v * 255 for v in fcColor])
|
||||
icon_size = button.iconSize()
|
||||
icon_size.setWidth(icon_size.width() * 2)
|
||||
button.setIconSize(icon_size)
|
||||
pixmap = QtGui.QPixmap(icon_size)
|
||||
pixmap.fill(barColor)
|
||||
button.setIcon(pixmap)
|
||||
|
||||
action = QtGui.QWidgetAction(button)
|
||||
diag = QtGui.QColorDialog(barColor, parent=button)
|
||||
diag.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
|
||||
diag.accepted.connect(action.trigger)
|
||||
diag.rejected.connect(action.trigger)
|
||||
diag.colorSelected.connect(callback)
|
||||
|
||||
action.setDefaultWidget(diag)
|
||||
button.addAction(action)
|
||||
button.setPopupMode(QtGui.QToolButton.InstantPopup)
|
||||
|
||||
@QtCore.Slot(QtGui.QColor)
|
||||
def colorChanged(self, color):
|
||||
|
||||
pixmap = QtGui.QPixmap(self.widget.Color.iconSize())
|
||||
pixmap.fill(color)
|
||||
self.widget.Color.setIcon(pixmap)
|
||||
|
||||
self._object.ViewObject.Color = color.getRgb()
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def lineWidthChanged(self, value):
|
||||
self._object.ViewObject.LineWidth = value
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def markerSizeChanged(self, value):
|
||||
self._object.ViewObject.MarkerSize = value
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def markerStyleChanged(self, index):
|
||||
self._object.ViewObject.MarkerStyle = index
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def lineStyleChanged(self, index):
|
||||
self._object.ViewObject.LineStyle = index
|
||||
|
||||
@QtCore.Slot()
|
||||
def legendChanged(self):
|
||||
self._object.ViewObject.Legend = self.widget.Legend.text()
|
||||
|
||||
|
||||
class 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
|
||||
self.widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostLineplotFieldAppEdit.ui"
|
||||
)
|
||||
layout = QtGui.QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.__init_widget()
|
||||
|
||||
def __init_widget(self):
|
||||
# set the other properties
|
||||
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "XField", self.widget.XField)
|
||||
self._post_dialog._enumPropertyToCombobox(
|
||||
self._object, "XComponent", self.widget.XComponent
|
||||
)
|
||||
self._post_dialog._enumPropertyToCombobox(self._object, "YField", self.widget.YField)
|
||||
self._post_dialog._enumPropertyToCombobox(
|
||||
self._object, "YComponent", self.widget.YComponent
|
||||
)
|
||||
self.widget.Extract.setChecked(self._object.ExtractFrames)
|
||||
|
||||
self.widget.XField.activated.connect(self.xFieldChanged)
|
||||
self.widget.XComponent.activated.connect(self.xComponentChanged)
|
||||
self.widget.YField.activated.connect(self.yFieldChanged)
|
||||
self.widget.YComponent.activated.connect(self.yComponentChanged)
|
||||
self.widget.Extract.toggled.connect(self.extractionChanged)
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def xFieldChanged(self, index):
|
||||
self._object.XField = index
|
||||
self._post_dialog._enumPropertyToCombobox(
|
||||
self._object, "XComponent", self.widget.XComponent
|
||||
)
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def xComponentChanged(self, index):
|
||||
self._object.XComponent = index
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def yFieldChanged(self, index):
|
||||
self._object.YField = index
|
||||
self._post_dialog._enumPropertyToCombobox(
|
||||
self._object, "YComponent", self.widget.YComponent
|
||||
)
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def yComponentChanged(self, index):
|
||||
self._object.YComponent = index
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def extractionChanged(self, extract):
|
||||
self._object.ExtractFrames = extract
|
||||
self._post_dialog._recompute()
|
||||
|
||||
|
||||
class 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
|
||||
self.widget = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/PostLineplotIndexAppEdit.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, "YField", self.widget.YField)
|
||||
self._post_dialog._enumPropertyToCombobox(
|
||||
self._object, "YComponent", self.widget.YComponent
|
||||
)
|
||||
|
||||
self.widget.Index.valueChanged.connect(self.indexChanged)
|
||||
self.widget.YField.activated.connect(self.yFieldChanged)
|
||||
self.widget.YComponent.activated.connect(self.yComponentChanged)
|
||||
|
||||
# sometimes wierd sizes occur with spinboxes
|
||||
self.widget.Index.setMaximumHeight(self.widget.YField.sizeHint().height())
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def indexChanged(self, value):
|
||||
self._object.Index = value
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def yFieldChanged(self, index):
|
||||
self._object.YField = index
|
||||
self._post_dialog._enumPropertyToCombobox(
|
||||
self._object, "YComponent", self.widget.YComponent
|
||||
)
|
||||
self._post_dialog._recompute()
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def yComponentChanged(self, index):
|
||||
self._object.YComponent = index
|
||||
self._post_dialog._recompute()
|
||||
|
||||
|
||||
class VPPostLineplotFieldData(view_base_fempostextractors.VPPostExtractor):
|
||||
"""
|
||||
A View Provider for extraction of 2D field data specialy for histograms
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
vobj.Proxy = self
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Legend",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The name used in the plots legend"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyColor",
|
||||
name="Color",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The color the line and the markers are drawn with"),
|
||||
value=(0, 85, 255, 255),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="LineStyle",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The style the line is drawn in"),
|
||||
value=["-", "--", "-.", ":", "None"],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="LineWidth",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The width the line is drawn with"),
|
||||
value=(1, 0.1, 99, 0.1),
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="MarkerStyle",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The style the data markers are drawn with"),
|
||||
value=["None", "*", "+", "s", ".", "o", "x"],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyFloatConstraint",
|
||||
name="MarkerSize",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The size the data markers are drawn in"),
|
||||
value=(5, 0.1, 99, 0.1),
|
||||
),
|
||||
]
|
||||
return super()._get_properties() + prop
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
self.ViewObject = vobj
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostField.svg"
|
||||
|
||||
def get_app_edit_widget(self, post_dialog):
|
||||
return EditFieldAppWidget(self.Object, post_dialog)
|
||||
|
||||
def get_view_edit_widget(self, post_dialog):
|
||||
return EditViewWidget(self.Object, post_dialog)
|
||||
|
||||
def get_preview(self):
|
||||
# Returns the preview tuple of icon and label: (QPixmap, str)
|
||||
# Note: QPixmap in ratio 2:1
|
||||
|
||||
fig = mpl.pyplot.figure(figsize=(0.2, 0.1), dpi=1000)
|
||||
ax = mpl.pyplot.Axes(fig, [0.0, 0.0, 1.0, 1.0])
|
||||
ax.set_axis_off()
|
||||
fig.add_axes(ax)
|
||||
kwargs = self.get_kw_args()
|
||||
kwargs["markevery"] = [1]
|
||||
ax.plot([0, 0.5, 1], [0.5, 0.5, 0.5], **kwargs)
|
||||
data = io.BytesIO()
|
||||
mpl.pyplot.savefig(data, bbox_inches=0, transparent=True)
|
||||
mpl.pyplot.close()
|
||||
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.loadFromData(data.getvalue())
|
||||
|
||||
return (pixmap, self.ViewObject.Legend)
|
||||
|
||||
def get_kw_args(self):
|
||||
# builds kw args from the properties
|
||||
kwargs = {}
|
||||
|
||||
# colors need a workaround, some error occurs with rgba tuple
|
||||
kwargs["color"] = self.ViewObject.Color
|
||||
kwargs["markeredgecolor"] = self.ViewObject.Color
|
||||
kwargs["markerfacecolor"] = self.ViewObject.Color
|
||||
kwargs["linestyle"] = self.ViewObject.LineStyle
|
||||
kwargs["linewidth"] = self.ViewObject.LineWidth
|
||||
kwargs["marker"] = self.ViewObject.MarkerStyle
|
||||
kwargs["markersize"] = self.ViewObject.MarkerSize
|
||||
return kwargs
|
||||
|
||||
def get_default_color_property(self):
|
||||
return "Color"
|
||||
|
||||
|
||||
class VPPostLineplotIndexOverFrames(VPPostLineplotFieldData):
|
||||
"""
|
||||
A View Provider for extraction of 2D 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 VPPostLineplot(view_base_fempostvisualization.VPPostVisualization):
|
||||
"""
|
||||
A View Provider for Lineplot plots
|
||||
"""
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
|
||||
def _get_properties(self):
|
||||
|
||||
prop = [
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Grid",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP(
|
||||
"FEM", "If be the bars shoud show the cumulative sum left to rigth"
|
||||
),
|
||||
value=True,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="Scale",
|
||||
group="Lineplot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The scale the axis are drawn in"),
|
||||
value=["linear", "semi-log x", "semi-log y", "log"],
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="Title",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The histogram plot title"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="XLabel",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The label shown for the histogram X axis"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyString",
|
||||
name="YLabel",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "The label shown for the histogram Y axis"),
|
||||
value="",
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Legend",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "Determines if the legend is plotted"),
|
||||
value=True,
|
||||
),
|
||||
_GuiPropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="LegendLocation",
|
||||
group="Plot",
|
||||
doc=QT_TRANSLATE_NOOP("FEM", "Determines if the legend is plotted"),
|
||||
value=[
|
||||
"best",
|
||||
"upper right",
|
||||
"upper left",
|
||||
"lower left",
|
||||
"lower right",
|
||||
"right",
|
||||
"center left",
|
||||
"center right",
|
||||
"lower center",
|
||||
"upper center",
|
||||
"center",
|
||||
],
|
||||
),
|
||||
]
|
||||
return prop
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_PostLineplot.svg"
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
|
||||
# build up the task panel
|
||||
taskd = task_post_lineplot._TaskPanel(vobj)
|
||||
|
||||
# show it
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
return True
|
||||
|
||||
def show_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
main = FreeCADGui.getMainWindow()
|
||||
self._plot = Plot.Plot()
|
||||
self._plot.setWindowTitle(self.Object.Label)
|
||||
self._plot.setParent(main)
|
||||
self._plot.setWindowFlags(QtGui.Qt.Dialog)
|
||||
self._plot.resize(
|
||||
main.size().height() / 2, main.size().height() / 3
|
||||
) # keep the aspect ratio
|
||||
self.update_visualization()
|
||||
|
||||
self._plot.show()
|
||||
|
||||
def get_kw_args(self, obj):
|
||||
view = obj.ViewObject
|
||||
if not view or not hasattr(view, "Proxy"):
|
||||
return {}
|
||||
if not hasattr(view.Proxy, "get_kw_args"):
|
||||
return {}
|
||||
return view.Proxy.get_kw_args()
|
||||
|
||||
def update_visualization(self):
|
||||
|
||||
if not hasattr(self, "_plot") or not self._plot:
|
||||
return
|
||||
|
||||
self._plot.axes.clear()
|
||||
|
||||
# we do not iterate the table, but iterate the children. This makes it possible
|
||||
# to attribute the correct styles
|
||||
plotted = False
|
||||
for child in self.Object.Group:
|
||||
|
||||
table = child.Table
|
||||
kwargs = self.get_kw_args(child)
|
||||
|
||||
# iterate over the table and plot all (note: column 0 is always X!)
|
||||
color_factor = np.linspace(1, 0.5, int(table.GetNumberOfColumns() / 2))
|
||||
legend_multiframe = table.GetNumberOfColumns() > 2
|
||||
|
||||
for i in range(0, table.GetNumberOfColumns(), 2):
|
||||
|
||||
plotted = True
|
||||
|
||||
# add the kw args, with some slide change over color for multiple frames
|
||||
tmp_args = {}
|
||||
for key in kwargs:
|
||||
if "color" in key:
|
||||
value = np.array(kwargs[key]) * color_factor[int(i / 2)]
|
||||
tmp_args[key] = mpl.colors.to_hex(value)
|
||||
else:
|
||||
tmp_args[key] = kwargs[key]
|
||||
|
||||
xdata = VTKArray(table.GetColumn(i))
|
||||
ydata = VTKArray(table.GetColumn(i + 1))
|
||||
|
||||
# ensure points are visible if it is a single datapoint
|
||||
if len(xdata) == 1 and tmp_args["marker"] == "None":
|
||||
tmp_args["marker"] = "o"
|
||||
|
||||
# legend labels
|
||||
if child.ViewObject.Legend:
|
||||
if not legend_multiframe:
|
||||
label = child.ViewObject.Legend
|
||||
else:
|
||||
postfix = table.GetColumnName(i + 1).split("-")[-1]
|
||||
label = child.ViewObject.Legend + " - " + postfix
|
||||
else:
|
||||
legend_prefix = ""
|
||||
if len(self.Object.Group) > 1:
|
||||
legend_prefix = child.Source.Label + ": "
|
||||
label = legend_prefix + table.GetColumnName(i + 1)
|
||||
|
||||
match self.ViewObject.Scale:
|
||||
case "log":
|
||||
self._plot.axes.loglog(xdata, ydata, **tmp_args, label=label)
|
||||
case "semi-log x":
|
||||
self._plot.axes.semilogx(xdata, ydata, **tmp_args, label=label)
|
||||
case "semi-log y":
|
||||
self._plot.axes.semilogy(xdata, ydata, **tmp_args, label=label)
|
||||
case _:
|
||||
self._plot.axes.plot(xdata, ydata, **tmp_args, label=label)
|
||||
|
||||
if self.ViewObject.Title:
|
||||
self._plot.axes.set_title(self.ViewObject.Title)
|
||||
if self.ViewObject.XLabel:
|
||||
self._plot.axes.set_xlabel(self.ViewObject.XLabel)
|
||||
if self.ViewObject.YLabel:
|
||||
self._plot.axes.set_ylabel(self.ViewObject.YLabel)
|
||||
|
||||
if self.ViewObject.Legend and plotted:
|
||||
self._plot.axes.legend(loc=self.ViewObject.LegendLocation)
|
||||
|
||||
self._plot.axes.grid(self.ViewObject.Grid)
|
||||
self._plot.update()
|
||||
|
||||
def get_next_default_color(self):
|
||||
# we use the next color in order. We do not check (yet) if this
|
||||
# color is already taken
|
||||
i = len(self.Object.Group)
|
||||
cmap = mpl.pyplot.get_cmap("tab10")
|
||||
return cmap(i)
|
||||
291
src/Mod/Fem/femviewprovider/view_post_table.py
Normal file
291
src/Mod/Fem/femviewprovider/view_post_table.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# ***************************************************************************
|
||||
# * 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
|
||||
|
||||
from PySide import QtGui, QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
from . import view_base_fempostextractors
|
||||
from . import view_base_fempostvisualization
|
||||
from femtaskpanels import task_post_table
|
||||
from femguiutils import vtk_table_view as vtv
|
||||
|
||||
from . import view_base_femobject
|
||||
|
||||
_GuiPropHelper = view_base_femobject._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=QT_TRANSLATE_NOOP(
|
||||
"FEM", "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 = QT_TRANSLATE_NOOP("FEM", "default")
|
||||
if self.ViewObject.Name:
|
||||
name = self.ViewObject.Name
|
||||
return (QtGui.QPixmap(), name)
|
||||
|
||||
def get_default_color_property(self):
|
||||
return None
|
||||
|
||||
|
||||
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:
|
||||
main = FreeCADGui.getMainWindow()
|
||||
self._tableModel = vtv.VtkTableModel()
|
||||
self._tableview = vtv.VtkTableView(self._tableModel)
|
||||
self._tableview.setWindowTitle(self.Object.Label)
|
||||
self._tableview.setParent(main)
|
||||
self._tableview.setWindowFlags(QtGui.Qt.Dialog)
|
||||
self._tableview.resize(
|
||||
main.size().height() / 2, main.size().height() / 3
|
||||
) # keep the aspect ratio
|
||||
|
||||
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)
|
||||
@@ -28,7 +28,7 @@ import sys
|
||||
try:
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Qt5Agg")
|
||||
matplotlib.use("QtAgg")
|
||||
|
||||
# Force matplotlib to use PySide backend by temporarily unloading PyQt
|
||||
if "PyQt5.QtCore" in sys.modules:
|
||||
@@ -36,10 +36,11 @@ try:
|
||||
import matplotlib.pyplot as plt
|
||||
import PyQt5.QtCore
|
||||
else:
|
||||
print("default matplotlib import")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
|
||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
|
||||
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
|
||||
|
||||
from matplotlib.figure import Figure
|
||||
except ImportError:
|
||||
|
||||
Reference in New Issue
Block a user