Merge pull request #20609 from marioalexis84/fem-ccx_refactor
Fem: SolverCalculiX refactor - Add option to import results as pipeline - Fixes #20541
This commit is contained in:
@@ -252,14 +252,14 @@ private:
|
||||
Py::Object frdToVTK(const Py::Tuple& args)
|
||||
{
|
||||
char* filename = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args.ptr(), "et", "utf-8", &filename)) {
|
||||
PyObject* binary = Py_True;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "et|O!", "utf-8", &filename, &PyBool_Type, &binary)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
std::string encodedName = std::string(filename);
|
||||
PyMem_Free(filename);
|
||||
|
||||
FemVTKTools::frdToVTK(encodedName.c_str());
|
||||
FemVTKTools::frdToVTK(encodedName.c_str(), Base::asBoolean(binary));
|
||||
|
||||
return Py::None();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,19 @@ using namespace App;
|
||||
PROPERTY_SOURCE(Fem::FemSolverObject, App::DocumentObject)
|
||||
|
||||
|
||||
FemSolverObject::FemSolverObject() = default;
|
||||
FemSolverObject::FemSolverObject()
|
||||
{
|
||||
ADD_PROPERTY_TYPE(Results,
|
||||
(nullptr),
|
||||
"Solver",
|
||||
App::PropertyType(App::Prop_ReadOnly | App::Prop_Output),
|
||||
"Solver results list");
|
||||
ADD_PROPERTY_TYPE(WorkingDirectory,
|
||||
(""),
|
||||
"Solver",
|
||||
App::PropertyType(App::Prop_Transient | App::Prop_Hidden | App::Prop_Output),
|
||||
"Solver working directory");
|
||||
}
|
||||
|
||||
FemSolverObject::~FemSolverObject() = default;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define Fem_FemSolverObject_H
|
||||
|
||||
#include <App/FeaturePython.h>
|
||||
#include <App/PropertyFile.h>
|
||||
#include <Mod/Fem/FemGlobal.h>
|
||||
|
||||
namespace Fem
|
||||
@@ -40,6 +41,8 @@ public:
|
||||
FemSolverObject();
|
||||
~FemSolverObject() override;
|
||||
|
||||
App::PropertyLinkList Results;
|
||||
App::PropertyPath WorkingDirectory;
|
||||
// Attributes are implemented in the FemSolverObjectPython
|
||||
|
||||
/// returns the type name of the ViewProvider
|
||||
|
||||
@@ -1711,7 +1711,7 @@ vtkSmartPointer<vtkMultiBlockDataSet> readFRD(std::ifstream& ifstr)
|
||||
|
||||
} // namespace FRDReader
|
||||
|
||||
void FemVTKTools::frdToVTK(const char* filename)
|
||||
void FemVTKTools::frdToVTK(const char* filename, bool binary)
|
||||
{
|
||||
Base::FileInfo fi(filename);
|
||||
|
||||
@@ -1733,6 +1733,8 @@ void FemVTKTools::frdToVTK(const char* filename)
|
||||
std::string type = info->GetValue(0).c_str();
|
||||
|
||||
auto writer = vtkSmartPointer<vtkXMLMultiBlockDataWriter>::New();
|
||||
writer->SetDataMode(binary ? vtkXMLMultiBlockDataWriter::Binary
|
||||
: vtkXMLMultiBlockDataWriter::Ascii);
|
||||
|
||||
std::string blockFile =
|
||||
dir + "/" + fi.fileNamePure() + type + "." + writer->GetDefaultFileExtension();
|
||||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
// write FemResult (activeObject if res= NULL) to vtkUnstructuredGrid dataset file
|
||||
static void writeResult(const char* filename, const App::DocumentObject* res = nullptr);
|
||||
|
||||
static void frdToVTK(const char* filename);
|
||||
static void frdToVTK(const char* filename, bool binary = true);
|
||||
};
|
||||
} // namespace Fem
|
||||
|
||||
|
||||
@@ -203,6 +203,7 @@ SET(FemObjects_SRCS
|
||||
femobjects/mesh_region.py
|
||||
femobjects/mesh_result.py
|
||||
femobjects/result_mechanical.py
|
||||
femobjects/solver_calculix.py
|
||||
femobjects/solver_ccxtools.py
|
||||
)
|
||||
|
||||
@@ -227,6 +228,7 @@ SET(FemSolver_SRCS
|
||||
|
||||
SET(FemSolverCalculix_SRCS
|
||||
femsolver/calculix/__init__.py
|
||||
femsolver/calculix/calculixtools.py
|
||||
femsolver/calculix/solver.py
|
||||
femsolver/calculix/tasks.py
|
||||
femsolver/calculix/write_constraint_bodyheatsource.py
|
||||
@@ -579,7 +581,7 @@ SET(FemGuiObjects_SRCS
|
||||
SET(FemGuiTaskPanels_SRCS
|
||||
femtaskpanels/__init__.py
|
||||
femtaskpanels/base_femtaskpanel.py
|
||||
femtaskpanels/base_femmeshtaskpanel.py
|
||||
femtaskpanels/base_femlogtaskpanel.py
|
||||
femtaskpanels/task_constraint_bodyheatsource.py
|
||||
femtaskpanels/task_constraint_centrif.py
|
||||
femtaskpanels/task_constraint_currentdensity.py
|
||||
@@ -603,6 +605,7 @@ SET(FemGuiTaskPanels_SRCS
|
||||
femtaskpanels/task_mesh_region.py
|
||||
femtaskpanels/task_mesh_netgen.py
|
||||
femtaskpanels/task_result_mechanical.py
|
||||
femtaskpanels/task_solver_calculix.py
|
||||
femtaskpanels/task_solver_ccxtools.py
|
||||
)
|
||||
|
||||
@@ -652,6 +655,7 @@ SET(FemGuiViewProvider_SRCS
|
||||
femviewprovider/view_mesh_region.py
|
||||
femviewprovider/view_mesh_result.py
|
||||
femviewprovider/view_result_mechanical.py
|
||||
femviewprovider/view_solver_calculix.py
|
||||
femviewprovider/view_solver_ccxtools.py
|
||||
)
|
||||
|
||||
|
||||
@@ -429,6 +429,7 @@ SET(FemGuiPythonUI_SRCS
|
||||
Resources/ui/ResultHints.ui
|
||||
Resources/ui/ResultShow.ui
|
||||
Resources/ui/SolverCalculix.ui
|
||||
Resources/ui/SolverCalculiX.ui
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET(FemPythonUi ALL
|
||||
|
||||
@@ -659,6 +659,60 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="l_pipeline_result">
|
||||
<property name="text">
|
||||
<string>Result object</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="2">
|
||||
<widget class="Gui::PrefCheckBox" name="ckb_pipeline_result">
|
||||
<property name="text">
|
||||
<string>Pipeline only</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Load results as pipeline instead of Result Object.
|
||||
By uncheck this option, CalculiX command behave like SolverCalculiXCcxTools</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>ResultAsPipeline</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Fem/Ccx</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="l_result_format">
|
||||
<property name="text">
|
||||
<string>Result format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="2">
|
||||
<widget class="Gui::PrefCheckBox" name="ckb_result_format">
|
||||
<property name="toolTip">
|
||||
<string>Save result in binary format.
|
||||
Only ta kes effect if 'Pipeline only' is enabled</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use binary format</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>BinaryOutput</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/Fem/Ccx</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -72,6 +72,8 @@ void DlgSettingsFemCcxImp::saveSettings()
|
||||
ui->dsb_ccx_analysis_time->onSave(); // Analysis time
|
||||
ui->dsb_ccx_minimum_time_step->onSave(); // Minimum time step
|
||||
ui->dsb_ccx_maximum_time_step->onSave(); // Maximum time step
|
||||
ui->ckb_pipeline_result->onSave();
|
||||
ui->ckb_result_format->onSave();
|
||||
|
||||
ui->cb_analysis_type->onSave();
|
||||
ui->cb_BeamShellOutput->onSave(); // Beam shell output 3d or 2d
|
||||
@@ -99,6 +101,8 @@ void DlgSettingsFemCcxImp::loadSettings()
|
||||
ui->dsb_ccx_analysis_time->onRestore(); // Analysis time
|
||||
ui->dsb_ccx_minimum_time_step->onRestore(); // Minimum time step
|
||||
ui->dsb_ccx_maximum_time_step->onRestore(); // Maximum time step
|
||||
ui->ckb_pipeline_result->onRestore();
|
||||
ui->ckb_result_format->onRestore();
|
||||
|
||||
ui->cb_analysis_type->onRestore();
|
||||
ui->cb_BeamShellOutput->onRestore(); // Beam shell output 3d or 2d
|
||||
|
||||
144
src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui
Normal file
144
src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui
Normal file
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SolverCalculiX</class>
|
||||
<widget class="QWidget" name="SolverCalculiX">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>475</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Solver CalculiX Control</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ckb_working_directory">
|
||||
<property name="text">
|
||||
<string>Working Directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gpb_working_directory">
|
||||
<property name="title">
|
||||
<string></string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="vbl_working_directory">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hbl_working_directory">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_write_input">
|
||||
<property name="text">
|
||||
<string>Write</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_edit_input">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hbl_working_directory">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="let_working_directory">
|
||||
<property name="toolTip">
|
||||
<string>Path to working directory</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="pb_working_directory">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gpb_solver_params">
|
||||
<property name="title">
|
||||
<string>Solver Parameters</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lbl_analysis_type">
|
||||
<property name="text">
|
||||
<string>Analysis Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cb_analysis_type">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_solver_log">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="te_output">
|
||||
<property name="lineWrapMode">
|
||||
<enum>QTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="l_time">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_solver_version">
|
||||
<property name="text">
|
||||
<string>Solver Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -392,7 +392,7 @@ TaskPostDisplay::TaskPostDisplay(ViewProviderFemPostObject* view, QWidget* paren
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->DisplayMode,
|
||||
ui->Representation);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Field, ui->Field);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->VectorMode, ui->VectorMode);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Component, ui->VectorMode);
|
||||
|
||||
// get Transparency from ViewProvider
|
||||
int trans = getTypedView<ViewProviderFemPostObject>()->Transparency.getValue();
|
||||
@@ -432,18 +432,18 @@ void TaskPostDisplay::onRepresentationActivated(int i)
|
||||
{
|
||||
getTypedView<ViewProviderFemPostObject>()->DisplayMode.setValue(i);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Field, ui->Field);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->VectorMode, ui->VectorMode);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Component, ui->VectorMode);
|
||||
}
|
||||
|
||||
void TaskPostDisplay::onFieldActivated(int i)
|
||||
{
|
||||
getTypedView<ViewProviderFemPostObject>()->Field.setValue(i);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->VectorMode, ui->VectorMode);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Component, ui->VectorMode);
|
||||
}
|
||||
|
||||
void TaskPostDisplay::onVectorModeActivated(int i)
|
||||
{
|
||||
getTypedView<ViewProviderFemPostObject>()->VectorMode.setValue(i);
|
||||
getTypedView<ViewProviderFemPostObject>()->Component.setValue(i);
|
||||
}
|
||||
|
||||
void TaskPostDisplay::onTransparencyValueChanged(int i)
|
||||
@@ -660,7 +660,7 @@ TaskPostDataAlongLine::TaskPostDataAlongLine(ViewProviderFemPostDataAlongLine* v
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->DisplayMode,
|
||||
ui->Representation);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Field, ui->Field);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->VectorMode, ui->VectorMode);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Component, ui->VectorMode);
|
||||
}
|
||||
|
||||
TaskPostDataAlongLine::~TaskPostDataAlongLine()
|
||||
@@ -963,7 +963,7 @@ void TaskPostDataAlongLine::onRepresentationActivated(int i)
|
||||
{
|
||||
getTypedView<ViewProviderFemPostObject>()->DisplayMode.setValue(i);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Field, ui->Field);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->VectorMode, ui->VectorMode);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Component, ui->VectorMode);
|
||||
}
|
||||
|
||||
void TaskPostDataAlongLine::onFieldActivated(int i)
|
||||
@@ -971,15 +971,15 @@ void TaskPostDataAlongLine::onFieldActivated(int i)
|
||||
getTypedView<ViewProviderFemPostObject>()->Field.setValue(i);
|
||||
std::string FieldName = ui->Field->currentText().toStdString();
|
||||
getObject<Fem::FemPostDataAlongLineFilter>()->PlotData.setValue(FieldName);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->VectorMode, ui->VectorMode);
|
||||
updateEnumerationList(getTypedView<ViewProviderFemPostObject>()->Component, ui->VectorMode);
|
||||
|
||||
auto vecMode = static_cast<ViewProviderFemPostObject*>(getView())->VectorMode.getEnum();
|
||||
auto vecMode = static_cast<ViewProviderFemPostObject*>(getView())->Component.getEnum();
|
||||
getObject<Fem::FemPostDataAlongLineFilter>()->PlotDataComponent.setValue(vecMode);
|
||||
}
|
||||
|
||||
void TaskPostDataAlongLine::onVectorModeActivated(int i)
|
||||
{
|
||||
getTypedView<ViewProviderFemPostObject>()->VectorMode.setValue(i);
|
||||
getTypedView<ViewProviderFemPostObject>()->Component.setValue(i);
|
||||
int comp = ui->VectorMode->currentIndex();
|
||||
getObject<Fem::FemPostDataAlongLineFilter>()->PlotDataComponent.setValue(comp);
|
||||
}
|
||||
@@ -1628,7 +1628,7 @@ void TaskPostContours::onFieldsChanged(int idx)
|
||||
// we must also update the VectorMode
|
||||
if (!getObject<Fem::FemPostContoursFilter>()->NoColor.getValue()) {
|
||||
auto newMode = getTypedObject<Fem::FemPostContoursFilter>()->VectorMode.getValue();
|
||||
getTypedView<ViewProviderFemPostObject>()->VectorMode.setValue(newMode);
|
||||
getTypedView<ViewProviderFemPostObject>()->Component.setValue(newMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1644,7 +1644,7 @@ void TaskPostContours::onVectorModeChanged(int idx)
|
||||
updateFields();
|
||||
// now we can set the VectorMode
|
||||
if (!getObject<Fem::FemPostContoursFilter>()->NoColor.getValue()) {
|
||||
getTypedView<ViewProviderFemPostObject>()->VectorMode.setValue(idx);
|
||||
getTypedView<ViewProviderFemPostObject>()->Component.setValue(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1668,9 +1668,9 @@ void TaskPostContours::onNoColorChanged(bool state)
|
||||
// the ViewProvider field starts with an additional entry "None",
|
||||
// therefore the desired new setting is idx + 1
|
||||
getTypedView<ViewProviderFemPostObject>()->Field.setValue(currentField + 1);
|
||||
// set the VectorMode too
|
||||
// set the Component too
|
||||
auto currentMode = getTypedObject<Fem::FemPostContoursFilter>()->VectorMode.getValue();
|
||||
getTypedView<ViewProviderFemPostObject>()->VectorMode.setValue(currentMode);
|
||||
getTypedView<ViewProviderFemPostObject>()->Component.setValue(currentMode);
|
||||
}
|
||||
recompute();
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Vector</string>
|
||||
<string>Component</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -125,26 +125,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Magnitute</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>X</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Y</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Z</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -160,11 +160,11 @@ ViewProviderFemPostObject::ViewProviderFemPostObject()
|
||||
"Coloring",
|
||||
App::Prop_None,
|
||||
"Select the field used for calculating the color");
|
||||
ADD_PROPERTY_TYPE(VectorMode,
|
||||
ADD_PROPERTY_TYPE(Component,
|
||||
((long)0),
|
||||
"Coloring",
|
||||
App::Prop_None,
|
||||
"Select what to show for a vector field");
|
||||
"Select component to display");
|
||||
ADD_PROPERTY_TYPE(Transparency,
|
||||
(0),
|
||||
"Object Style",
|
||||
@@ -453,8 +453,8 @@ void ViewProviderFemPostObject::updateProperties()
|
||||
Field.purgeTouched();
|
||||
|
||||
// Vector mode
|
||||
if (VectorMode.hasEnums() && VectorMode.getValue() >= 0) {
|
||||
val = VectorMode.getValueAsString();
|
||||
if (Component.hasEnums() && Component.getValue() >= 0) {
|
||||
val = Component.getValueAsString();
|
||||
}
|
||||
|
||||
colorArrays.clear();
|
||||
@@ -469,27 +469,41 @@ void ViewProviderFemPostObject::updateProperties()
|
||||
}
|
||||
|
||||
if (data->GetNumberOfComponents() == 1) {
|
||||
// scalar
|
||||
colorArrays.emplace_back("Not a vector");
|
||||
}
|
||||
else {
|
||||
colorArrays.emplace_back("Magnitude");
|
||||
if (data->GetNumberOfComponents() >= 2) {
|
||||
if (data->GetNumberOfComponents() == 2) {
|
||||
// 2D vector
|
||||
colorArrays.emplace_back("X");
|
||||
colorArrays.emplace_back("Y");
|
||||
}
|
||||
if (data->GetNumberOfComponents() >= 3) {
|
||||
else if (data->GetNumberOfComponents() == 3) {
|
||||
// 3D vector
|
||||
colorArrays.emplace_back("X");
|
||||
colorArrays.emplace_back("Y");
|
||||
colorArrays.emplace_back("Z");
|
||||
}
|
||||
else if (data->GetNumberOfComponents() == 6) {
|
||||
// symmetric tensor
|
||||
colorArrays.emplace_back("XX");
|
||||
colorArrays.emplace_back("YY");
|
||||
colorArrays.emplace_back("ZZ");
|
||||
colorArrays.emplace_back("XY");
|
||||
colorArrays.emplace_back("YZ");
|
||||
colorArrays.emplace_back("ZX");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VectorMode.setValue(empty);
|
||||
Component.setValue(empty);
|
||||
m_vectorEnum.setEnums(colorArrays);
|
||||
VectorMode.setValue(m_vectorEnum);
|
||||
Component.setValue(m_vectorEnum);
|
||||
|
||||
it = std::ranges::find(colorArrays, val);
|
||||
if (!val.empty() && it != colorArrays.end()) {
|
||||
VectorMode.setValue(val.c_str());
|
||||
Component.setValue(val.c_str());
|
||||
}
|
||||
|
||||
m_blockPropertyChanges = false;
|
||||
@@ -693,10 +707,10 @@ void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange)
|
||||
return;
|
||||
}
|
||||
|
||||
int component = VectorMode.getValue() - 1; // 0 is either "Not a vector" or magnitude,
|
||||
// for -1 is correct for magnitude.
|
||||
// x y and z are one number too high
|
||||
if (strcmp(VectorMode.getValueAsString(), "Not a vector") == 0) {
|
||||
int component = Component.getValue() - 1; // 0 is either "Not a vector" or magnitude,
|
||||
// for -1 is correct for magnitude.
|
||||
// x y and z are one number too high
|
||||
if (strcmp(Component.getValueAsString(), "Not a vector") == 0) {
|
||||
component = 0;
|
||||
}
|
||||
|
||||
@@ -920,7 +934,7 @@ void ViewProviderFemPostObject::onChanged(const App::Property* prop)
|
||||
updateProperties();
|
||||
WriteColorData(ResetColorBarRange);
|
||||
}
|
||||
else if (prop == &VectorMode && setupPipeline()) {
|
||||
else if (prop == &Component && setupPipeline()) {
|
||||
WriteColorData(ResetColorBarRange);
|
||||
}
|
||||
else if (prop == &Transparency) {
|
||||
@@ -1108,3 +1122,17 @@ void ViewProviderFemPostObject::onSelectionChanged(const Gui::SelectionChanges&
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViewProviderFemPostObject::handleChangedPropertyName(Base::XMLReader& reader,
|
||||
const char* typeName,
|
||||
const char* propName)
|
||||
{
|
||||
if (strcmp(propName, "Field") == 0 && strcmp(typeName, "App::PropertyEnumeration") == 0) {
|
||||
App::PropertyEnumeration field;
|
||||
field.Restore(reader);
|
||||
Component.setValue(field.getValue());
|
||||
}
|
||||
else {
|
||||
Gui::ViewProviderDocumentObject::handleChangedPropertyName(reader, typeName, propName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
~ViewProviderFemPostObject() override;
|
||||
|
||||
App::PropertyEnumeration Field;
|
||||
App::PropertyEnumeration VectorMode;
|
||||
App::PropertyEnumeration Component;
|
||||
App::PropertyPercent Transparency;
|
||||
App::PropertyBool PlainColorEdgeOnSurface;
|
||||
App::PropertyColor EdgeColor;
|
||||
@@ -130,6 +130,10 @@ public:
|
||||
// //@}
|
||||
|
||||
protected:
|
||||
void handleChangedPropertyName(Base::XMLReader& reader,
|
||||
const char* typeName,
|
||||
const char* propName) override;
|
||||
|
||||
virtual void setupTaskDialog(TaskDlgPost* dlg);
|
||||
bool setupPipeline();
|
||||
void updateVtk();
|
||||
|
||||
@@ -166,7 +166,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
|
||||
Gui::ToolBarItem* solve = new Gui::ToolBarItem(root);
|
||||
solve->setCommand("Solve");
|
||||
if (!Fem::Tools::checkIfBinaryExists("Ccx", "ccx", "ccx").empty()) {
|
||||
*solve << "FEM_SolverCalculiXCcxTools";
|
||||
*solve << "FEM_SolverCalculiX";
|
||||
}
|
||||
if (!Fem::Tools::checkIfBinaryExists("Elmer", "elmer", "ElmerSolver").empty()) {
|
||||
*solve << "FEM_SolverElmer";
|
||||
@@ -326,7 +326,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
|
||||
Gui::MenuItem* solve = new Gui::MenuItem;
|
||||
root->insertItem(item, solve);
|
||||
solve->setCommand("&Solve");
|
||||
*solve << "FEM_SolverCalculiXCcxTools"
|
||||
*solve << "FEM_SolverCalculiX"
|
||||
<< "FEM_SolverElmer"
|
||||
<< "FEM_SolverMystran"
|
||||
<< "FEM_SolverZ88"
|
||||
|
||||
@@ -795,12 +795,17 @@ def makeSolverCalculiXCcxTools(doc, name="SolverCcxTools"):
|
||||
return obj
|
||||
|
||||
|
||||
def makeSolverCalculix(doc, name="SolverCalculix"):
|
||||
"""makeSolverCalculix(document, [name]):
|
||||
def makeSolverCalculiX(doc, name="SolverCalculiX"):
|
||||
"""makeSolverCalculiX(document, [name]):
|
||||
makes a Calculix solver object"""
|
||||
import femsolver.calculix.solver
|
||||
obj = doc.addObject("Fem::FemSolverObjectPython", name)
|
||||
from femobjects import solver_calculix
|
||||
|
||||
obj = femsolver.calculix.solver.create(doc, name)
|
||||
solver_calculix.SolverCalculiX(obj)
|
||||
if FreeCAD.GuiUp:
|
||||
from femviewprovider import view_solver_calculix
|
||||
|
||||
view_solver_calculix.VPSolverCalculiX(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ __url__ = "https://www.freecad.org"
|
||||
# \ingroup FEM
|
||||
# \brief FreeCAD FEM command definitions
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide import QtGui
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
from FreeCAD import Qt
|
||||
@@ -630,7 +633,7 @@ class _MaterialMechanicalNonlinear(CommandManager):
|
||||
# CalculiX solver or new frame work CalculiX solver
|
||||
if solver_object and (
|
||||
is_of_type(solver_object, "Fem::SolverCcxTools")
|
||||
or is_of_type(solver_object, "Fem::SolverCalculix")
|
||||
or is_of_type(solver_object, "Fem::SolverCalculiX")
|
||||
):
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Set MaterialNonlinearity to nonlinear for {solver_object.Label}\n"
|
||||
@@ -938,7 +941,7 @@ class _SolverCalculixContextManager:
|
||||
|
||||
def __enter__(self):
|
||||
ccx_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Ccx")
|
||||
FreeCAD.ActiveDocument.openTransaction("Create SolverCalculix")
|
||||
FreeCAD.ActiveDocument.openTransaction("Create SolverCalculiX")
|
||||
FreeCADGui.addModule("ObjectsFem")
|
||||
FreeCADGui.addModule("FemGui")
|
||||
FreeCADGui.doCommand(
|
||||
@@ -1051,24 +1054,28 @@ class _SolverCcxTools(CommandManager):
|
||||
FreeCADGui.doCommand(f"{cm.cli_name}.MaterialNonlinearity = 'nonlinear'")
|
||||
|
||||
|
||||
class _SolverCalculix(CommandManager):
|
||||
"The FEM_SolverCalculix command definition"
|
||||
class _SolverCalculiX(CommandManager):
|
||||
"The FEM_SolverCalculiX command definition"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.pixmap = "FEM_SolverStandard"
|
||||
self.menutext = Qt.QT_TRANSLATE_NOOP(
|
||||
"FEM_SolverCalculiX", "Solver CalculiX (new framework)"
|
||||
)
|
||||
self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_SolverCalculiX", "Solver CalculiX")
|
||||
self.accel = "S, C"
|
||||
self.tooltip = Qt.QT_TRANSLATE_NOOP(
|
||||
"FEM_SolverCalculiX",
|
||||
"Creates a FEM solver CalculiX new framework (less result error handling)",
|
||||
"Creates a FEM solver CalculiX",
|
||||
)
|
||||
self.is_active = "with_analysis"
|
||||
|
||||
def Activated(self):
|
||||
with _SolverCalculixContextManager("makeSolverCalculix", "solver") as cm:
|
||||
ccx_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Ccx")
|
||||
if ccx_prefs.GetBool("ResultAsPipeline", False):
|
||||
make_solver = "makeSolverCalculiX"
|
||||
else:
|
||||
make_solver = "makeSolverCalculiXCcxTools"
|
||||
|
||||
with _SolverCalculixContextManager(make_solver, "solver") as cm:
|
||||
has_nonlinear_material_obj = False
|
||||
for m in self.active_analysis.Group:
|
||||
if is_of_type(m, "Fem::MaterialMechanicalNonlinear"):
|
||||
@@ -1158,13 +1165,44 @@ class _SolverRun(CommandManager):
|
||||
"FEM_SolverRun", "Runs the calculations for the selected solver"
|
||||
)
|
||||
self.is_active = "with_solver"
|
||||
self.tool = None
|
||||
|
||||
def Activated(self):
|
||||
from femsolver.run import run_fem_solver
|
||||
if self.selobj.Proxy.Type == "Fem::SolverCalculiX":
|
||||
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
||||
try:
|
||||
from femsolver.calculix.calculixtools import CalculiXTools
|
||||
|
||||
run_fem_solver(self.selobj)
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
self.tool = CalculiXTools(self.selobj)
|
||||
self._conn(self.tool)
|
||||
self.tool.prepare()
|
||||
self.tool.compute()
|
||||
except Exception as e:
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
raise
|
||||
|
||||
else:
|
||||
from femsolver.run import run_fem_solver
|
||||
|
||||
run_fem_solver(self.selobj)
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def _conn(self, tool):
|
||||
QtCore.QObject.connect(
|
||||
tool.process,
|
||||
QtCore.SIGNAL("finished(int, QProcess::ExitStatus)"),
|
||||
self._process_finished,
|
||||
)
|
||||
|
||||
def _process_finished(self, code, status):
|
||||
if status == QtCore.QProcess.ExitStatus.NormalExit and code == 0:
|
||||
self.tool.update_properties()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
else:
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
FreeCAD.Console.PrintError("Process finished with errors. Result not updated\n")
|
||||
|
||||
|
||||
class _SolverZ88(CommandManager):
|
||||
@@ -1227,7 +1265,7 @@ FreeCADGui.addCommand("FEM_MeshRegion", _MeshRegion())
|
||||
FreeCADGui.addCommand("FEM_ResultShow", _ResultShow())
|
||||
FreeCADGui.addCommand("FEM_ResultsPurge", _ResultsPurge())
|
||||
FreeCADGui.addCommand("FEM_SolverCalculiXCcxTools", _SolverCcxTools())
|
||||
FreeCADGui.addCommand("FEM_SolverCalculiX", _SolverCalculix())
|
||||
FreeCADGui.addCommand("FEM_SolverCalculiX", _SolverCalculiX())
|
||||
FreeCADGui.addCommand("FEM_SolverControl", _SolverControl())
|
||||
FreeCADGui.addCommand("FEM_SolverElmer", _SolverElmer())
|
||||
FreeCADGui.addCommand("FEM_SolverMystran", _SolverMystran())
|
||||
|
||||
@@ -74,7 +74,7 @@ class MeshSetsGetter:
|
||||
# TODO somehow this is not smart, pur mesh objects might be used often
|
||||
if self.member.geos_beamsection and (
|
||||
type_of_obj(self.solver_obj) == "Fem::SolverCcxTools"
|
||||
or type_of_obj(self.solver_obj) == "Fem::SolverCalculix"
|
||||
or type_of_obj(self.solver_obj) == "Fem::SolverCalculiX"
|
||||
):
|
||||
FreeCAD.Console.PrintError(
|
||||
"The mesh does not know the geometry it is made from. "
|
||||
|
||||
46
src/Mod/Fem/femobjects/solver_calculix.py
Normal file
46
src/Mod/Fem/femobjects/solver_calculix.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2017 Bernd Hahnebach <bernd@bimstatik.org> *
|
||||
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD 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 *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM solver CalculiX document object"
|
||||
__author__ = "Bernd Hahnebach, Mario Passaglia"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package solver_calculix
|
||||
# \ingroup FEM
|
||||
# \brief solver CalculiX object
|
||||
|
||||
from . import base_fempythonobject
|
||||
from femsolver.calculix.solver import _BaseSolverCalculix
|
||||
|
||||
|
||||
class SolverCalculiX(base_fempythonobject.BaseFemPythonObject, _BaseSolverCalculix):
|
||||
|
||||
Type = "Fem::SolverCalculiX"
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj)
|
||||
self.add_attributes(obj)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self.on_restore_of_document(obj)
|
||||
191
src/Mod/Fem/femsolver/calculix/calculixtools.py
Normal file
191
src/Mod/Fem/femsolver/calculix/calculixtools.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD 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 *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "Tools for the work with CalculiX solver"
|
||||
__author__ = "Mario Passaglia"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
|
||||
from PySide.QtCore import QProcess, QThread
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import FreeCAD
|
||||
import Fem
|
||||
|
||||
from . import writer
|
||||
from .. import settings
|
||||
|
||||
# from feminout import importCcxDatResults
|
||||
from femmesh import meshsetsgetter
|
||||
from femtools import membertools
|
||||
|
||||
|
||||
class CalculiXTools:
|
||||
|
||||
frd_var_conversion = {
|
||||
"CONTACT": "Contact Displacement",
|
||||
"PE": "Plastic Strain",
|
||||
"CELS": "Contact Energy",
|
||||
"ECD": "Current Density",
|
||||
"EMFB": "Magnetic Field",
|
||||
"EMFE": "Electric Field",
|
||||
"ENER": "Internal Energy Density",
|
||||
"FLUX": "Heat Flux",
|
||||
"DISP": "Displacement",
|
||||
"T": "Temperature",
|
||||
"TOSTRAIN": "Strain",
|
||||
"STRESS": "Stress",
|
||||
"STR(%)": "Error",
|
||||
}
|
||||
|
||||
name = "CalculiX"
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
self.process = QProcess()
|
||||
self.model_file = ""
|
||||
self.analysis = obj.getParentGroup()
|
||||
self.fem_param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
|
||||
self._create_working_directory(obj)
|
||||
|
||||
def _create_working_directory(self, obj):
|
||||
"""
|
||||
Create working directory according to preferences
|
||||
"""
|
||||
if not os.path.isdir(obj.WorkingDirectory):
|
||||
gen_param = self.fem_param.GetGroup("General")
|
||||
if gen_param.GetBool("UseTempDirectory"):
|
||||
self.obj.WorkingDirectory = tempfile.mkdtemp(prefix="fem_")
|
||||
elif gen_param.GetBool("UseBesideDirectory"):
|
||||
root, ext = os.path.splitext(obj.Document.FileName)
|
||||
if root:
|
||||
self.obj.WorkingDirectory = os.path.join(root, obj.Label)
|
||||
os.makedirs(self.obj.WorkingDirectory, exist_ok=True)
|
||||
else:
|
||||
# file not saved, use temporary
|
||||
self.obj.WorkingDirectory = tempfile.mkdtemp(prefix="fem_")
|
||||
elif gen_param.GetBool("UseCustomDirectory"):
|
||||
self.obj.WorkingDirectory = gen_param.GetString("CustomDirectoryPath")
|
||||
os.makedirs(self.obj.WorkingDirectory, exist_ok=True)
|
||||
|
||||
def prepare(self):
|
||||
from femtools.checksanalysis import check_member_for_solver_calculix
|
||||
|
||||
self._clear_results()
|
||||
|
||||
message = check_member_for_solver_calculix(
|
||||
self.analysis,
|
||||
self.obj,
|
||||
membertools.get_mesh_to_solve(self.analysis)[0],
|
||||
membertools.AnalysisMember(self.analysis),
|
||||
)
|
||||
|
||||
mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0]
|
||||
meshdatagetter = meshsetsgetter.MeshSetsGetter(
|
||||
self.analysis,
|
||||
self.obj,
|
||||
mesh_obj,
|
||||
membertools.AnalysisMember(self.analysis),
|
||||
)
|
||||
meshdatagetter.get_mesh_sets()
|
||||
|
||||
# write solver input
|
||||
w = writer.FemInputWriterCcx(
|
||||
self.analysis,
|
||||
self.obj,
|
||||
mesh_obj,
|
||||
meshdatagetter.member,
|
||||
self.obj.WorkingDirectory,
|
||||
meshdatagetter.mat_geo_sets,
|
||||
)
|
||||
self.model_file = w.write_solver_input()
|
||||
# report to user if task succeeded
|
||||
self.input_deck = os.path.splitext(os.path.basename(self.model_file))[0]
|
||||
|
||||
def compute(self):
|
||||
self._clear_results()
|
||||
ccx_bin = settings.get_binary("Calculix")
|
||||
env = self.process.processEnvironment()
|
||||
num_cpu = self.fem_param.GetGroup("Ccx").GetInt(
|
||||
"AnalysisNumCPUs", QThread.idealThreadCount()
|
||||
)
|
||||
env.insert("OMP_NUM_THREADS", str(num_cpu))
|
||||
self.process.setProcessEnvironment(env)
|
||||
self.process.setWorkingDirectory(self.obj.WorkingDirectory)
|
||||
|
||||
command_list = ["-i", os.path.join(self.obj.WorkingDirectory, self.input_deck)]
|
||||
self.process.start(ccx_bin, command_list)
|
||||
|
||||
return self.process
|
||||
|
||||
def update_properties(self):
|
||||
# TODO at the moment, only one .vtm file is assumed
|
||||
if not self.obj.Results:
|
||||
pipeline = self.obj.Document.addObject("Fem::FemPostPipeline", self.obj.Name + "Result")
|
||||
self.analysis.addObject(pipeline)
|
||||
self.obj.Results = [pipeline]
|
||||
self._load_ccxfrd_results()
|
||||
# default display mode
|
||||
pipeline.ViewObject.DisplayMode = "Surface"
|
||||
pipeline.ViewObject.SelectionStyle = "BoundBox"
|
||||
if self.obj.AnalysisType in ["static", "frequency", "buckling"]:
|
||||
pipeline.ViewObject.Field = "Displacement"
|
||||
elif self.obj.AnalysisType in ["thermomech"]:
|
||||
pipeline.ViewObject.Field = "Temperature"
|
||||
else:
|
||||
self._load_ccxfrd_results()
|
||||
|
||||
def _clear_results(self):
|
||||
# result is a 'Result.vtm' file and a 'Result' directory
|
||||
# with the .vtu files
|
||||
dir_content = os.listdir(self.obj.WorkingDirectory)
|
||||
for f in dir_content:
|
||||
path = os.path.join(self.obj.WorkingDirectory, f)
|
||||
base, ext = os.path.splitext(path)
|
||||
if ext == ".vtm":
|
||||
# remove .vtm file
|
||||
os.remove(path)
|
||||
# remove dir with .vtu files
|
||||
shutil.rmtree(base)
|
||||
|
||||
def _load_ccxfrd_results(self):
|
||||
frd_result_prefix = os.path.join(self.obj.WorkingDirectory, self.input_deck)
|
||||
binary_mode = self.fem_param.GetGroup("Ccx").GetBool("BinaryOutput", False)
|
||||
Fem.frdToVTK(frd_result_prefix + ".frd", binary_mode)
|
||||
files = os.listdir(self.obj.WorkingDirectory)
|
||||
for f in files:
|
||||
if f.endswith(".vtm"):
|
||||
res = os.path.join(self.obj.WorkingDirectory, f)
|
||||
self.obj.Results[0].read(res)
|
||||
self.obj.Results[0].renameArrays(self.frd_var_conversion)
|
||||
break
|
||||
|
||||
def version(self):
|
||||
p = QProcess()
|
||||
ccx_bin = settings.get_binary("Calculix")
|
||||
p.start(ccx_bin, ["-v"])
|
||||
p.waitForFinished()
|
||||
info = p.readAll().data().decode()
|
||||
return info
|
||||
@@ -21,13 +21,13 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM mesh base task panel for mesh object object"
|
||||
__title__ = "FreeCAD FEM base task panel with logging"
|
||||
__author__ = "Mario Passaglia"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package base_femmeshtaskpanel
|
||||
## @package base_femlogtaskpanel
|
||||
# \ingroup FEM
|
||||
# \brief base task panel for mesh object
|
||||
# \brief base task panel for logging
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
@@ -44,27 +44,34 @@ from . import base_femtaskpanel
|
||||
class _Thread(QtCore.QThread):
|
||||
"""
|
||||
Class for thread and subprocess manipulation
|
||||
'tool' argument must be an object with 'compute' and 'prepare' methods
|
||||
'tool' argument must be an object with 'compute', 'prepare', 'update_properties' methods
|
||||
and a 'process' attribute of type QProcess object
|
||||
"""
|
||||
|
||||
def __init__(self, tool):
|
||||
super().__init__()
|
||||
self.tool = tool
|
||||
self.prepare_ok = False
|
||||
|
||||
def run(self):
|
||||
self.tool.prepare()
|
||||
try:
|
||||
self.tool.prepare()
|
||||
self.prepare_ok = True
|
||||
except Exception as e:
|
||||
self.prepare_ok = False
|
||||
FreeCAD.Console.PrintError("{}\n".format(e))
|
||||
|
||||
|
||||
class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
class _BaseLogTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
"""
|
||||
Abstract base class for FemMesh object TaskPanel
|
||||
Abstract base class for TaskPanel with logging
|
||||
"""
|
||||
|
||||
def __init__(self, obj, tool):
|
||||
super().__init__(obj)
|
||||
self.tool = tool
|
||||
self.timer = QtCore.QTimer()
|
||||
self.elapsed = QtCore.QElapsedTimer()
|
||||
self._thread = _Thread(self.tool)
|
||||
self.text_log = None
|
||||
self.text_time = None
|
||||
@@ -97,17 +104,27 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
|
||||
def thread_started(self):
|
||||
self.text_log.clear()
|
||||
self.write_log("Prepare meshing...\n", QtGui.QColor(getOutputWinColor("Text")))
|
||||
self.write_log("Prepare process...\n", QtGui.QColor(getOutputWinColor("Text")))
|
||||
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
||||
|
||||
def thread_finished(self):
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
if self._thread.prepare_ok:
|
||||
self.write_log("Preparation finished\n", QtGui.QColor(getOutputWinColor("Text")))
|
||||
self.preparation_finished()
|
||||
else:
|
||||
self.timer.stop()
|
||||
self.write_log("Preparation failed.\n", QtGui.QColor(getOutputWinColor("Error")))
|
||||
return None
|
||||
|
||||
def preparation_finished(self):
|
||||
self.tool.compute()
|
||||
|
||||
def process_finished(self, code, status):
|
||||
if status == QtCore.QProcess.ExitStatus.NormalExit:
|
||||
if code != 0:
|
||||
self.write_log(
|
||||
"Process finished with errors. Mesh not updated\n",
|
||||
"Process finished with errors. Result not updated\n",
|
||||
QtGui.QColor(getOutputWinColor("Error")),
|
||||
)
|
||||
return
|
||||
@@ -117,7 +134,8 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
self.write_log("Process crashed\n", QtGui.QColor(getOutputWinColor("Error")))
|
||||
|
||||
def process_started(self):
|
||||
self.write_log("Start meshing...\n", QtGui.QColor(getOutputWinColor("Text")))
|
||||
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
||||
self.write_log("Start process...\n", QtGui.QColor(getOutputWinColor("Text")))
|
||||
|
||||
def write_output(self):
|
||||
self.write_log(
|
||||
@@ -143,11 +161,11 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
self.text_log.ensureCursorVisible()
|
||||
|
||||
@abstractmethod
|
||||
def set_mesh_params(self):
|
||||
def set_object_params(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_mesh_params(self):
|
||||
def get_object_params(self):
|
||||
pass
|
||||
|
||||
def getStandardButtons(self):
|
||||
@@ -166,7 +184,7 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
|
||||
self.timer.stop()
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
self.set_mesh_params()
|
||||
self.set_object_params()
|
||||
return super().accept()
|
||||
|
||||
def reject(self):
|
||||
@@ -190,8 +208,10 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
FreeCAD.Console.PrintWarning("Process already running\n")
|
||||
return None
|
||||
|
||||
self.set_mesh_params()
|
||||
self.run_mesher()
|
||||
self.apply()
|
||||
|
||||
def apply(self):
|
||||
self.run_process()
|
||||
|
||||
def update_timer_text(self):
|
||||
self.text_time.setText(f"Time: {self.elapsed.elapsed()/1000:4.1f} s")
|
||||
@@ -200,8 +220,8 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
self.timer.stop()
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
|
||||
def run_mesher(self):
|
||||
self.elapsed = QtCore.QElapsedTimer()
|
||||
def run_process(self):
|
||||
self.set_object_params()
|
||||
self.elapsed.start()
|
||||
self.update_timer_text()
|
||||
self.timer.start(100)
|
||||
@@ -210,4 +230,4 @@ class _BaseMeshTaskPanel(base_femtaskpanel._BaseTaskPanel, ABC):
|
||||
|
||||
def get_version(self):
|
||||
full_message = self.tool.version()
|
||||
QtGui.QMessageBox.information(None, "{} - Information".format(self.tool.name), full_message)
|
||||
QtGui.QMessageBox.information(None, "{} - Info".format(self.tool.name), full_message)
|
||||
@@ -36,10 +36,10 @@ import FreeCADGui
|
||||
|
||||
from femmesh import gmshtools
|
||||
|
||||
from . import base_femmeshtaskpanel
|
||||
from . import base_femlogtaskpanel
|
||||
|
||||
|
||||
class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
|
||||
class _TaskPanel(base_femlogtaskpanel._BaseLogTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing References property of
|
||||
MeshGmsh objects and creation of new FEM mesh
|
||||
@@ -78,16 +78,16 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
|
||||
self.form.pb_get_gmsh_version, QtCore.SIGNAL("clicked()"), self.get_version
|
||||
)
|
||||
|
||||
self.get_mesh_params()
|
||||
self.get_object_params()
|
||||
self.set_widgets()
|
||||
|
||||
def get_mesh_params(self):
|
||||
def get_object_params(self):
|
||||
self.clmax = self.obj.CharacteristicLengthMax
|
||||
self.clmin = self.obj.CharacteristicLengthMin
|
||||
self.dimension = self.obj.ElementDimension
|
||||
self.order = self.obj.ElementOrder
|
||||
|
||||
def set_mesh_params(self):
|
||||
def set_object_params(self):
|
||||
self.obj.CharacteristicLengthMax = self.clmax
|
||||
self.obj.CharacteristicLengthMin = self.clmin
|
||||
self.obj.ElementDimension = self.dimension
|
||||
|
||||
@@ -36,10 +36,10 @@ import FreeCADGui
|
||||
|
||||
from femmesh import netgentools
|
||||
|
||||
from . import base_femmeshtaskpanel
|
||||
from . import base_femlogtaskpanel
|
||||
|
||||
|
||||
class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
|
||||
class _TaskPanel(base_femlogtaskpanel._BaseLogTaskPanel):
|
||||
"""
|
||||
The TaskPanel for editing References property of
|
||||
MeshNetgen objects and creation of new FEM mesh
|
||||
@@ -97,10 +97,10 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
|
||||
self.form.pb_get_netgen_version, QtCore.SIGNAL("clicked()"), self.get_version
|
||||
)
|
||||
|
||||
self.get_mesh_params()
|
||||
self.get_object_params()
|
||||
self.set_widgets()
|
||||
|
||||
def get_mesh_params(self):
|
||||
def get_object_params(self):
|
||||
self.min_size = self.obj.MinSize
|
||||
self.max_size = self.obj.MaxSize
|
||||
self.fineness = self.obj.Fineness
|
||||
@@ -110,7 +110,7 @@ class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
|
||||
self.second_order = self.obj.SecondOrder
|
||||
self.user_p = self.get_user_fineness_params(self.obj)
|
||||
|
||||
def set_mesh_params(self):
|
||||
def set_object_params(self):
|
||||
self.obj.MinSize = self.min_size
|
||||
self.obj.MaxSize = self.max_size
|
||||
self.obj.Fineness = self.fineness
|
||||
|
||||
168
src/Mod/Fem/femtaskpanels/task_solver_calculix.py
Normal file
168
src/Mod/Fem/femtaskpanels/task_solver_calculix.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD 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 *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD task panel for CalculiX solver"
|
||||
__author__ = "Mario Passaglia"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package task_solver_calculix
|
||||
# \ingroup FEM
|
||||
# \brief task panel for CalculiX solver
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide import QtGui
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import FemGui
|
||||
|
||||
from femsolver.calculix import calculixtools
|
||||
|
||||
from . import base_femlogtaskpanel
|
||||
|
||||
|
||||
class _TaskPanel(base_femlogtaskpanel._BaseLogTaskPanel):
|
||||
"""
|
||||
The TaskPanel for run CalculiX solver
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
super().__init__(obj, calculixtools.CalculiXTools(obj))
|
||||
|
||||
self.form = FreeCADGui.PySideUic.loadUi(
|
||||
FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/SolverCalculiX.ui"
|
||||
)
|
||||
|
||||
self.text_log = self.form.te_output
|
||||
self.text_time = self.form.l_time
|
||||
self.prepared = False
|
||||
self.run_complete = False
|
||||
|
||||
self.setup_connections()
|
||||
|
||||
def setup_connections(self):
|
||||
super().setup_connections()
|
||||
|
||||
QtCore.QObject.connect(
|
||||
self.form.ckb_working_directory,
|
||||
QtCore.SIGNAL("toggled(bool)"),
|
||||
self.working_directory_toggled,
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.form.cb_analysis_type,
|
||||
QtCore.SIGNAL("currentIndexChanged(int)"),
|
||||
self.analysis_type_changed,
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.form.pb_write_input, QtCore.SIGNAL("clicked()"), self.write_input_clicked
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.form.pb_edit_input, QtCore.SIGNAL("clicked()"), self.edit_input_clicked
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.form.pb_working_directory,
|
||||
QtCore.SIGNAL("clicked()"),
|
||||
self.working_directory_clicked,
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.form.pb_solver_version, QtCore.SIGNAL("clicked()"), self.get_version
|
||||
)
|
||||
QtCore.QObject.connect(
|
||||
self.form.let_working_directory,
|
||||
QtCore.SIGNAL("editingFinished()"),
|
||||
self.working_directory_edited,
|
||||
)
|
||||
|
||||
self.get_object_params()
|
||||
self.set_widgets()
|
||||
|
||||
def preparation_finished(self):
|
||||
# override base class method to not auto compute
|
||||
self.prepared = True
|
||||
if not self.run_complete:
|
||||
self.timer.stop()
|
||||
self.form.pb_edit_input.setEnabled(True)
|
||||
else:
|
||||
super().preparation_finished()
|
||||
|
||||
def apply(self):
|
||||
self.text_log.clear()
|
||||
self.elapsed.restart()
|
||||
if self.prepared:
|
||||
self.timer.start(100)
|
||||
self.tool.compute()
|
||||
else:
|
||||
# run complete process if 'Apply' is pressed without
|
||||
# previously write the input files
|
||||
self.run_complete = True
|
||||
super().apply()
|
||||
|
||||
def get_object_params(self):
|
||||
self.analysis_type = self.obj.AnalysisType
|
||||
|
||||
def set_object_params(self):
|
||||
self.obj.AnalysisType = self.analysis_type
|
||||
|
||||
def set_widgets(self):
|
||||
"fills the widgets"
|
||||
self.analysis_type_enum = self.obj.getEnumerationsOfProperty("AnalysisType")
|
||||
index = self.analysis_type_enum.index(self.analysis_type)
|
||||
self.form.cb_analysis_type.addItems(self.analysis_type_enum)
|
||||
self.form.cb_analysis_type.setCurrentIndex(index)
|
||||
|
||||
self.form.let_working_directory.setText(self.obj.WorkingDirectory)
|
||||
self.form.ckb_working_directory.setChecked(False)
|
||||
self.form.gpb_working_directory.setVisible(False)
|
||||
|
||||
def analysis_type_changed(self, index):
|
||||
self.analysis_type = self.analysis_type_enum[index]
|
||||
self.obj.AnalysisType = self.analysis_type
|
||||
|
||||
def working_directory_clicked(self):
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(dir=self.obj.WorkingDirectory)
|
||||
if directory:
|
||||
self.form.let_working_directory.setText(directory)
|
||||
self.form.let_working_directory.editingFinished.emit()
|
||||
|
||||
def working_directory_edited(self):
|
||||
self.obj.WorkingDirectory = self.form.let_working_directory.text()
|
||||
|
||||
def write_input_clicked(self):
|
||||
self.prepared = False
|
||||
self.run_complete = False
|
||||
self.run_process()
|
||||
|
||||
def edit_input_clicked(self):
|
||||
ccx_param = self.tool.fem_param.GetGroup("Ccx")
|
||||
internal = ccx_param.GetBool("UseInternalEditor", True)
|
||||
ext_editor_path = ccx_param.GetString("ExternalEditorPath", "")
|
||||
if internal or not ext_editor_path:
|
||||
FemGui.open(self.tool.model_file)
|
||||
else:
|
||||
ext_editor_process = QtCore.QProcess()
|
||||
ext_editor_process.start(ext_editor_path, [self.tool.model_file])
|
||||
ext_editor_process.waitForFinished()
|
||||
|
||||
def working_directory_toggled(self, bool_value):
|
||||
self.form.gpb_working_directory.setVisible(bool_value)
|
||||
@@ -254,7 +254,7 @@ class TestObjectType(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
"Fem::SolverCcxTools", type_of_obj(ObjectsFem.makeSolverCalculiXCcxTools(doc))
|
||||
)
|
||||
self.assertEqual("Fem::SolverCalculix", type_of_obj(ObjectsFem.makeSolverCalculix(doc)))
|
||||
self.assertEqual("Fem::SolverCalculiX", type_of_obj(ObjectsFem.makeSolverCalculiX(doc)))
|
||||
self.assertEqual("Fem::SolverElmer", type_of_obj(solverelmer))
|
||||
self.assertEqual("Fem::SolverMystran", type_of_obj(ObjectsFem.makeSolverMystran(doc)))
|
||||
self.assertEqual("Fem::SolverZ88", type_of_obj(ObjectsFem.makeSolverZ88(doc)))
|
||||
@@ -434,7 +434,7 @@ class TestObjectType(unittest.TestCase):
|
||||
self.assertTrue(
|
||||
is_of_type(ObjectsFem.makeSolverCalculiXCcxTools(doc), "Fem::SolverCcxTools")
|
||||
)
|
||||
self.assertTrue(is_of_type(ObjectsFem.makeSolverCalculix(doc), "Fem::SolverCalculix"))
|
||||
self.assertTrue(is_of_type(ObjectsFem.makeSolverCalculiX(doc), "Fem::SolverCalculiX"))
|
||||
self.assertTrue(is_of_type(solverelmer, "Fem::SolverElmer"))
|
||||
self.assertTrue(is_of_type(ObjectsFem.makeSolverMystran(doc), "Fem::SolverMystran"))
|
||||
self.assertTrue(is_of_type(ObjectsFem.makeSolverZ88(doc), "Fem::SolverZ88"))
|
||||
@@ -805,12 +805,12 @@ class TestObjectType(unittest.TestCase):
|
||||
self.assertTrue(is_derived_from(solver_ccxtools, "Fem::FemSolverObjectPython"))
|
||||
self.assertTrue(is_derived_from(solver_ccxtools, "Fem::SolverCcxTools"))
|
||||
|
||||
# SolverCalculix
|
||||
solver_calculix = ObjectsFem.makeSolverCalculix(doc)
|
||||
# SolverCalculiX
|
||||
solver_calculix = ObjectsFem.makeSolverCalculiX(doc)
|
||||
self.assertTrue(is_derived_from(solver_calculix, "App::DocumentObject"))
|
||||
self.assertTrue(is_derived_from(solver_calculix, "Fem::FemSolverObject"))
|
||||
self.assertTrue(is_derived_from(solver_calculix, "Fem::FemSolverObjectPython"))
|
||||
self.assertTrue(is_derived_from(solver_calculix, "Fem::SolverCalculix"))
|
||||
self.assertTrue(is_derived_from(solver_calculix, "Fem::SolverCalculiX"))
|
||||
|
||||
# SolverElmer
|
||||
solver_elmer = ObjectsFem.makeSolverElmer(doc)
|
||||
@@ -1036,7 +1036,7 @@ class TestObjectType(unittest.TestCase):
|
||||
ObjectsFem.makeSolverCalculiXCcxTools(doc).isDerivedFrom("Fem::FemSolverObjectPython")
|
||||
)
|
||||
self.assertTrue(
|
||||
ObjectsFem.makeSolverCalculix(doc).isDerivedFrom("Fem::FemSolverObjectPython")
|
||||
ObjectsFem.makeSolverCalculiX(doc).isDerivedFrom("Fem::FemSolverObjectPython")
|
||||
)
|
||||
self.assertTrue(solverelmer.isDerivedFrom("Fem::FemSolverObjectPython"))
|
||||
self.assertTrue(
|
||||
@@ -1156,7 +1156,7 @@ def create_all_fem_objects_doc(doc):
|
||||
ObjectsFem.makePostVtkFilterContours(doc, vres)
|
||||
|
||||
analysis.addObject(ObjectsFem.makeSolverCalculiXCcxTools(doc))
|
||||
analysis.addObject(ObjectsFem.makeSolverCalculix(doc))
|
||||
analysis.addObject(ObjectsFem.makeSolverCalculiX(doc))
|
||||
sol = analysis.addObject(ObjectsFem.makeSolverElmer(doc))[0]
|
||||
analysis.addObject(ObjectsFem.makeSolverMystran(doc))
|
||||
analysis.addObject(ObjectsFem.makeSolverZ88(doc))
|
||||
|
||||
@@ -259,10 +259,6 @@ class TestObjectOpen(unittest.TestCase):
|
||||
|
||||
self.assertEqual(SolverCcxTools, doc.SolverCcxTools.Proxy.__class__)
|
||||
|
||||
from femsolver.calculix.solver import Proxy
|
||||
|
||||
self.assertEqual(Proxy, doc.SolverCalculix.Proxy.__class__)
|
||||
|
||||
from femsolver.elmer.solver import Proxy
|
||||
|
||||
self.assertEqual(Proxy, doc.SolverElmer.Proxy.__class__)
|
||||
|
||||
@@ -243,10 +243,6 @@ class TestObjectOpen(unittest.TestCase):
|
||||
|
||||
self.assertEqual(VPSolverCcxTools, doc.SolverCcxTools.ViewObject.Proxy.__class__)
|
||||
|
||||
from femsolver.calculix.solver import ViewProxy
|
||||
|
||||
self.assertEqual(ViewProxy, doc.SolverCalculix.ViewObject.Proxy.__class__)
|
||||
|
||||
from femsolver.elmer.solver import ViewProxy
|
||||
|
||||
self.assertEqual(ViewProxy, doc.SolverElmer.ViewObject.Proxy.__class__)
|
||||
|
||||
50
src/Mod/Fem/femviewprovider/view_solver_calculix.py
Normal file
50
src/Mod/Fem/femviewprovider/view_solver_calculix.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD 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 *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "FreeCAD FEM solver CalculiX view provider"
|
||||
__author__ = "Mario Passaglia"
|
||||
__url__ = "https://www.freecad.org"
|
||||
|
||||
## @package view_calculix
|
||||
# \ingroup FEM
|
||||
# \brief solver CalculiX view provider
|
||||
|
||||
import FreeCADGui
|
||||
|
||||
from femtaskpanels import task_solver_calculix
|
||||
from femviewprovider import view_base_femobject
|
||||
|
||||
|
||||
class VPSolverCalculiX(view_base_femobject.VPBaseFemObject):
|
||||
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/FEM_SolverStandard.svg"
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
task = task_solver_calculix._TaskPanel(vobj.Object)
|
||||
FreeCADGui.Control.showDialog(task)
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user