diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt
index 622c3a3d46..1e30655868 100755
--- a/src/Mod/Fem/CMakeLists.txt
+++ b/src/Mod/Fem/CMakeLists.txt
@@ -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
@@ -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
)
diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt
index d7fdd2c4b5..17c7ca28ce 100755
--- a/src/Mod/Fem/Gui/CMakeLists.txt
+++ b/src/Mod/Fem/Gui/CMakeLists.txt
@@ -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
diff --git a/src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui b/src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui
new file mode 100644
index 0000000000..890991abe1
--- /dev/null
+++ b/src/Mod/Fem/Gui/Resources/ui/SolverCalculiX.ui
@@ -0,0 +1,144 @@
+
+
+ SolverCalculiX
+
+
+
+ 0
+ 0
+ 400
+ 475
+
+
+
+ Solver CalculiX Control
+
+
+ -
+
+
+ Working Directory
+
+
+
+ -
+
+
+
+
+
+
-
+
+
-
+
+
+ Write
+
+
+
+ -
+
+
+ false
+
+
+ Edit
+
+
+
+
+
+ -
+
+
-
+
+
+ Path to working directory
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+
+
+
+ -
+
+
+ Solver Parameters
+
+
+
-
+
+
-
+
+
+ Analysis Type:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+ QTextEdit::NoWrap
+
+
+ true
+
+
+
+ -
+
+
+
+ 12
+
+
+
+ Time:
+
+
+
+ -
+
+
+ Solver Version
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py
index 17a8b436f9..72c407b3bc 100644
--- a/src/Mod/Fem/ObjectsFem.py
+++ b/src/Mod/Fem/ObjectsFem.py
@@ -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
diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py
index 89dad73b83..751adbb7bf 100644
--- a/src/Mod/Fem/femcommands/commands.py
+++ b/src/Mod/Fem/femcommands/commands.py
@@ -630,7 +630,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 +938,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,8 +1051,8 @@ 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__()
@@ -1068,7 +1068,13 @@ class _SolverCalculix(CommandManager):
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"):
@@ -1227,7 +1233,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())
diff --git a/src/Mod/Fem/femmesh/meshsetsgetter.py b/src/Mod/Fem/femmesh/meshsetsgetter.py
index 27ccf458b5..0f9b62fa9e 100644
--- a/src/Mod/Fem/femmesh/meshsetsgetter.py
+++ b/src/Mod/Fem/femmesh/meshsetsgetter.py
@@ -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. "
diff --git a/src/Mod/Fem/femobjects/solver_calculix.py b/src/Mod/Fem/femobjects/solver_calculix.py
new file mode 100644
index 0000000000..d7b74f4b7f
--- /dev/null
+++ b/src/Mod/Fem/femobjects/solver_calculix.py
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2017 Bernd Hahnebach *
+# * Copyright (c) 2025 Mario Passaglia *
+# * *
+# * 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 *
+# * . *
+# * *
+# ***************************************************************************
+
+__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)
diff --git a/src/Mod/Fem/femsolver/calculix/calculixtools.py b/src/Mod/Fem/femsolver/calculix/calculixtools.py
new file mode 100644
index 0000000000..88c91397c6
--- /dev/null
+++ b/src/Mod/Fem/femsolver/calculix/calculixtools.py
@@ -0,0 +1,190 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2025 Mario Passaglia *
+# * *
+# * 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 *
+# * . *
+# * *
+# ***************************************************************************
+
+__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)
+ Fem.frdToVTK(frd_result_prefix + ".frd")
+ 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
diff --git a/src/Mod/Fem/femtaskpanels/task_solver_calculix.py b/src/Mod/Fem/femtaskpanels/task_solver_calculix.py
new file mode 100644
index 0000000000..583286706e
--- /dev/null
+++ b/src/Mod/Fem/femtaskpanels/task_solver_calculix.py
@@ -0,0 +1,168 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2025 Mario Passaglia *
+# * *
+# * 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 *
+# * . *
+# * *
+# ***************************************************************************
+
+__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)
diff --git a/src/Mod/Fem/femviewprovider/view_solver_calculix.py b/src/Mod/Fem/femviewprovider/view_solver_calculix.py
new file mode 100644
index 0000000000..499967dbc8
--- /dev/null
+++ b/src/Mod/Fem/femviewprovider/view_solver_calculix.py
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2025 Mario Passaglia *
+# * *
+# * 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 *
+# * . *
+# * *
+# ***************************************************************************
+
+__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