FEM: add CalculiX solver object

This commit is contained in:
Bernd Hahnebach 2015-11-15 20:48:23 +01:00 committed by Yorik van Havre
parent 80cf664ec8
commit 6865e35fd9
14 changed files with 299 additions and 52 deletions

View File

@ -73,6 +73,7 @@ SET(FemScripts_SRCS
_CommandFemBeamSection.py
_CommandFemFromShape.py
_CommandFemShellThickness.py
_CommandFemSolverCalculix.py
_CommandMechanicalJobControl.py
_CommandMechanicalMaterial.py
_CommandMechanicalShowResult.py
@ -82,6 +83,7 @@ SET(FemScripts_SRCS
_FemAnalysis.py
_FemBeamSection.py
_FemShellThickness.py
_FemSolverCalculix.py
_MechanicalMaterial.py
_TaskPanelFemBeamSection.py
_TaskPanelFemShellThickness.py
@ -91,6 +93,7 @@ SET(FemScripts_SRCS
_ViewProviderFemAnalysis.py
_ViewProviderFemBeamSection.py
_ViewProviderFemShellThickness.py
_ViewProviderFemSolverCalculix.py
_ViewProviderMechanicalMaterial.py
ccxDatReader.py
ccxFrdReader.py
@ -102,6 +105,7 @@ SET(FemScripts_SRCS
FemBeamSection.py
FemExample.py
FemShellThickness.py
FemSolverCalculix.py
FemTools.py
MechanicalAnalysis.py
MechanicalMaterial.py

View File

@ -43,6 +43,7 @@ PROPERTY_SOURCE(Fem::FemSolverObject, App::DocumentObject)
FemSolverObject::FemSolverObject()
{
/*
ADD_PROPERTY_TYPE(SolverName,("Calculix"), "Data",Prop_None,"Solver program name");
ADD_PROPERTY_TYPE(Category,("FEM"), "Data",Prop_None,"FEM, CFD ...");
ADD_PROPERTY_TYPE(Module,(""), "Data",Prop_None,"Python module name");
@ -54,6 +55,7 @@ FemSolverObject::FemSolverObject()
ADD_PROPERTY_TYPE(InputCaseName,("TestCase"), "Solver",Prop_None,"Solver input file without suffix");
ADD_PROPERTY_TYPE(Parallel,(false), "Solver",Prop_None,"Run solver in parallel like MPI");
ADD_PROPERTY_TYPE(ResultObtained,(false), "Solver",Prop_None,"if true, result has been obtained");
*/
}
FemSolverObject::~FemSolverObject()

View File

@ -43,6 +43,7 @@ public:
FemSolverObject(void);
virtual ~FemSolverObject();
/*
/// Solver name, unique to identify solver in registered_solver dict
App::PropertyString SolverName;
/// CAE category like FEM, all capitalised letters
@ -64,6 +65,7 @@ public:
App::PropertyBool Parallel;
/// result has been obtained, purge result may be needed for rerun
App::PropertyBool ResultObtained;
*/
/// returns the type name of the ViewProvider
virtual const char* getViewProviderName(void) const {

View File

@ -62,6 +62,11 @@ INSTALL(
_TaskPanelMechanicalMaterial.py
TaskPanelMechanicalMaterial.ui
FemSolverCalculix.py
_FemSolverCalculix.py
_ViewProviderFemSolverCalculix.py
_CommandFemSolverCalculix.py
DESTINATION
Mod/Fem
)

View File

@ -0,0 +1,39 @@
# ***************************************************************************
# * *
# * Copyright (c) 2015 - Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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__ = "FemSolverCalculix"
__author__ = "Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
import FreeCAD
import _FemSolverCalculix
def makeFemSolverCalculix(name="Calculix"):
'''makeSolverCalculix(name): makes a Calculix solver object'''
obj = FreeCAD.ActiveDocument.addObject("Fem::FemSolverObjectPython", name)
_FemSolverCalculix._FemSolverCalculix(obj)
if FreeCAD.GuiUp:
import _ViewProviderFemSolverCalculix
_ViewProviderFemSolverCalculix._ViewProviderFemSolverCalculix(obj.ViewObject)
return obj

View File

@ -50,15 +50,18 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
self.analysis = FemGui.getActiveAnalysis()
if self.analysis:
self.update_objects()
self.set_analysis_type()
self.set_eigenmode_parameters()
## @var base_name
# base name of .inp/.frd file (without extension). It is used to construct .inp file path that is passed to CalculiX ccx
self.base_name = ""
## @var results_present
# boolean variable indicating if there are calculation results ready for use
self.results_present = False
self.setup_working_dir()
if self.solver:
self.set_analysis_type()
self.set_eigenmode_parameters()
self.setup_working_dir()
else:
raise Exception('FEM: No solver found!')
if test_mode:
self.ccx_binary_present = True
else:
@ -149,6 +152,9 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
# [{'Object':beam_sections, 'xxxxxxxx':value}, {}, ...]
# [{'Object':shell_thicknesses, 'xxxxxxxx':value}, {}, ...]
## @var solver
# solver of the analysis. Used to store solver and analysis parameters
self.solver = None
## @var mesh
# mesh of the analysis. Used to generate .inp file and to show results
self.mesh = None
@ -178,7 +184,9 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
self.shell_thicknesses = []
for m in self.analysis.Member:
if m.isDerivedFrom("Fem::FemMeshObject"):
if m.isDerivedFrom("Fem::FemSolverObjectPython"):
self.solver = m
elif m.isDerivedFrom("Fem::FemMeshObject"):
self.mesh = m
elif m.isDerivedFrom("App::MaterialObjectPython"):
material_dict = {}
@ -299,7 +307,7 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
_number = number
else:
try:
_number = self.analysis.NumberOfEigenmodes
_number = self.solver.NumberOfEigenmodes
except:
#Not yet in prefs, so it will always default to 10
_number = self.fem_prefs.GetInteger("NumberOfEigenmodes", 10)
@ -310,7 +318,7 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
_limit_low = limit_low
else:
try:
_limit_low = self.analysis.EigenmodeLowLimit
_limit_low = self.solver.EigenmodeLowLimit
except:
#Not yet in prefs, so it will always default to 0.0
_limit_low = self.fem_prefs.GetFloat("EigenmodeLowLimit", 0.0)
@ -319,7 +327,7 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
_limit_high = limit_high
else:
try:
_limit_high = self.analysis.EigenmodeHighLimit
_limit_high = self.solver.EigenmodeHighLimit
except:
#Not yet in prefs, so it will always default to 1000000.0
_limit_high = self.fem_prefs.GetFloat("EigenmodeHighLimit", 1000000.0)
@ -357,21 +365,21 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
self.analysis_type = analysis_type
else:
try:
self.analysis_type = self.analysis.AnalysisType
self.analysis_type = self.solver.AnalysisType
except:
self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
self.analysis_type = self.fem_prefs.GetString("AnalysisType", "static")
## Sets working dir for ccx execution. Called with no working_dir uses WorkingDir from FEM preferences
## Sets working dir for solver execution. Called with no working_dir uses WorkingDir from FEM preferences
# @param self The python object self
# @working_dir directory to be used for writing .inp file and executing CalculiX ccx
# @working_dir directory to be used for writing solver input file or files and executing solver
def setup_working_dir(self, working_dir=None):
import os
if working_dir is not None:
self.working_dir = working_dir
else:
try:
self.working_dir = self.analysis.WorkingDir
self.working_dir = self.solver.WorkingDir
except:
FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem").GetString("WorkingDir")
self.working_dir = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem").GetString("WorkingDir")

View File

@ -56,6 +56,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
Gui::ToolBarItem* fem = new Gui::ToolBarItem(root);
fem->setCommand("FEM");
*fem << "Fem_NewMechanicalAnalysis"
<< "Fem_SolverCalculix"
<< "Fem_CreateFromShape"
<< "Fem_MechanicalMaterial"
<< "Fem_BeamSection"
@ -85,6 +86,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
root->insertItem(item, fem);
fem->setCommand("&FEM");
*fem << "Fem_NewMechanicalAnalysis"
<< "Fem_SolverCalculix"
<< "Fem_CreateFromShape"
<< "Fem_MechanicalMaterial"
<< "Fem_BeamSection"

View File

@ -51,6 +51,7 @@ class FemWorkbench (Workbench):
import _CommandFemShellThickness
import _CommandFemBeamSection
import _CommandMechanicalMaterial
import _CommandFemSolverCalculix
import MechanicalAnalysis

View File

@ -0,0 +1,53 @@
# ***************************************************************************
# * *
# * Copyright (c) 2015 - Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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__ = "_CommandFemSolverCalculix"
__author__ = "Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
import FreeCAD
from FemCommands import FemCommands
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore
class _CommandFemSolverCalculix(FemCommands):
"The Fem_SolverCalculix command definition"
def __init__(self):
super(_CommandFemSolverCalculix, self).__init__()
self.resources = {'Pixmap': 'fem-solver',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Fem_SolverCalculix", "Create FEM Solver Calculix ..."),
'Accel': "S, C",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Fem_SolverCalculix", "Creates FEM Solver Calculix")}
self.is_active = 'with_analysis'
def Activated(self):
FreeCAD.ActiveDocument.openTransaction("Create SolverCalculix")
FreeCADGui.addModule("FemSolverCalculix")
FreeCADGui.doCommand("FemGui.getActiveAnalysis().Member = FemGui.getActiveAnalysis().Member + [FemSolverCalculix.makeFemSolverCalculix()]")
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Fem_SolverCalculix', _CommandFemSolverCalculix())

View File

@ -24,35 +24,12 @@ __title__ = "Fem Analysis"
__author__ = "Juergen Riegel"
__url__ = "http://www.freecadweb.org"
import FreeCAD
from FemTools import FemTools
class _FemAnalysis:
"The FemAnalysis container object"
def __init__(self, obj):
self.Type = "FemAnalysis"
obj.Proxy = self
fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
obj.addProperty("App::PropertyEnumeration", "AnalysisType", "Fem", "Type of the analysis")
obj.AnalysisType = FemTools.known_analysis_types
analysis_type = fem_prefs.GetInt("AnalysisType", 0)
obj.AnalysisType = FemTools.known_analysis_types[analysis_type]
obj.addProperty("App::PropertyPath", "WorkingDir", "Fem", "Working directory for calculations")
obj.WorkingDir = fem_prefs.GetString("WorkingDir", "")
obj.addProperty("App::PropertyIntegerConstraint", "NumberOfEigenmodes", "Fem", "Number of modes for frequency calculations")
noe = fem_prefs.GetInt("NumberOfEigenmodes", 10)
obj.NumberOfEigenmodes = (noe, 1, 100, 1)
obj.addProperty("App::PropertyFloatConstraint", "EigenmodeLowLimit", "Fem", "Low frequency limit for eigenmode calculations")
#Not yet in prefs, so it will always default to 0.0
ell = fem_prefs.GetFloat("EigenmodeLowLimit", 0.0)
obj.EigenmodeLowLimit = (ell, 0.0, 1000000.0, 10000.0)
obj.addProperty("App::PropertyFloatConstraint", "EigenmodeHighLimit", "Fem", "High frequency limit for eigenmode calculations")
ehl = fem_prefs.GetFloat("EigenmodeHighLimit", 1000000.0)
obj.EigenmodeHighLimit = (ehl, 0.0, 1000000.0, 10000.0)
def execute(self, obj):
return

View File

@ -0,0 +1,75 @@
# ***************************************************************************
# * *
# * Copyright (c) 2015 - Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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__ = "_FemSolverCalculix"
__author__ = "Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
import FreeCAD
from FemTools import FemTools
class _FemSolverCalculix():
"""The Fem::FemSolver's Proxy python type, add solver specific properties
"""
def __init__(self, obj):
self.Type = "FemSolverCalculix"
self.Object = obj # keep a ref to the DocObj for nonGui usage
obj.Proxy = self # link between App::DocumentObject to this object
obj.addProperty("App::PropertyString", "SolverType", "Base", "Type of the solver")
obj.SolverType = str(self.Type)
obj.setEditorMode("SolverType", 1)
fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
obj.addProperty("App::PropertyPath", "WorkingDir", "Fem", "Working directory for calculations")
obj.WorkingDir = fem_prefs.GetString("WorkingDir", "")
obj.addProperty("App::PropertyEnumeration", "AnalysisType", "Fem", "Type of the analysis")
obj.AnalysisType = FemTools.known_analysis_types
analysis_type = fem_prefs.GetInt("AnalysisType", 0)
obj.AnalysisType = FemTools.known_analysis_types[analysis_type]
obj.addProperty("App::PropertyIntegerConstraint", "NumberOfEigenmodes", "Fem", "Number of modes for frequency calculations")
noe = fem_prefs.GetInt("NumberOfEigenmodes", 10)
obj.NumberOfEigenmodes = (noe, 1, 100, 1)
obj.addProperty("App::PropertyFloatConstraint", "EigenmodeLowLimit", "Fem", "Low frequency limit for eigenmode calculations")
#Not yet in prefs, so it will always default to 0.0
ell = fem_prefs.GetFloat("EigenmodeLowLimit", 0.0)
obj.EigenmodeLowLimit = (ell, 0.0, 1000000.0, 10000.0)
obj.addProperty("App::PropertyFloatConstraint", "EigenmodeHighLimit", "Fem", "High frequency limit for eigenmode calculations")
ehl = fem_prefs.GetFloat("EigenmodeHighLimit", 1000000.0)
obj.EigenmodeHighLimit = (ehl, 0.0, 1000000.0, 10000.0)
def execute(self, obj):
return
def __getstate__(self):
return self.Type
def __setstate__(self, state):
if state:
self.Type = state

View File

@ -39,7 +39,7 @@ if FreeCAD.GuiUp:
class _TaskPanelJobControl:
def __init__(self, analysis_object):
def __init__(self, solver_object):
self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Fem/TaskPanelMechanicalAnalysis.ui")
self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
ccx_binary = self.fem_prefs.GetString("ccxBinaryPath", "")
@ -56,7 +56,7 @@ class _TaskPanelJobControl:
self.CalculixBinary = 'ccx'
self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem")
self.analysis_object = analysis_object
self.solver_object = solver_object
self.Calculix = QtCore.QProcess()
self.Timer = QtCore.QTimer()
@ -159,28 +159,28 @@ class _TaskPanelJobControl:
def update(self):
'fills the widgets'
self.form.le_working_dir.setText(self.analysis_object.WorkingDir)
if self.analysis_object.AnalysisType == 'static':
self.form.le_working_dir.setText(self.solver_object.WorkingDir)
if self.solver_object.AnalysisType == 'static':
self.form.rb_static_analysis.setChecked(True)
elif self.analysis_object.AnalysisType == 'frequency':
elif self.solver_object.AnalysisType == 'frequency':
self.form.rb_frequency_analysis.setChecked(True)
return
def accept(self):
FreeCADGui.Control.closeDialog()
FreeCADGui.ActiveDocument.resetEdit()
def reject(self):
FreeCADGui.Control.closeDialog()
FreeCADGui.ActiveDocument.resetEdit()
def choose_working_dir(self):
current_wd = self.setup_working_dir()
wd = QtGui.QFileDialog.getExistingDirectory(None, 'Choose CalculiX working directory',
current_wd)
if wd:
self.analysis_object.WorkingDir = wd
self.solver_object.WorkingDir = wd
else:
self.analysis_object.WorkingDir = current_wd
self.form.le_working_dir.setText(self.analysis_object.WorkingDir)
self.solver_object.WorkingDir = current_wd
self.form.le_working_dir.setText(self.solver_object.WorkingDir)
def write_input_file_handler(self):
QApplication.restoreOverrideCursor()
@ -188,7 +188,7 @@ class _TaskPanelJobControl:
QApplication.setOverrideCursor(Qt.WaitCursor)
self.inp_file_name = ""
fea = FemTools()
fea.set_analysis_type(self.analysis_object.AnalysisType)
fea.set_analysis_type(self.solver_object.AnalysisType)
fea.update_objects()
fea.write_inp_file()
if fea.inp_file_name != "":
@ -250,8 +250,8 @@ class _TaskPanelJobControl:
QApplication.restoreOverrideCursor()
def select_analysis_type(self, analysis_type):
if self.analysis_object.AnalysisType != analysis_type:
self.analysis_object.AnalysisType = analysis_type
if self.solver_object.AnalysisType != analysis_type:
self.solver_object.AnalysisType = analysis_type
self.form.pb_edit_inp.setEnabled(False)
self.form.pb_run_ccx.setEnabled(False)
@ -263,7 +263,7 @@ class _TaskPanelJobControl:
# That function overlaps with FemTools setup_working_dir and needs to be removed when we migrate fully to FemTools
def setup_working_dir(self):
wd = self.analysis_object.WorkingDir
wd = self.solver_object.WorkingDir
if not (os.path.isdir(wd)):
try:
os.makedirs(wd)

View File

@ -32,10 +32,6 @@ class _ViewProviderFemAnalysis:
FreeCADGui.activateWorkbench("FemWorkbench")
FemGui.setActiveAnalysis(self.Object)
return True
else:
import _TaskPanelJobControl
taskd = _TaskPanelJobControl(self.Object)
FreeCADGui.Control.showDialog(taskd)
return True
def __getstate__(self):

View File

@ -0,0 +1,83 @@
# ***************************************************************************
# * *
# * Copyright (c) 2015 - Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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__ = "_FemViewProviderSolverCalculix"
__author__ = "Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
import FreeCAD
import FreeCADGui
import FemGui
class _ViewProviderFemSolverCalculix:
"A View Provider for the FemSolverCalculix object"
def __init__(self, vobj):
vobj.Proxy = self
def getIcon(self):
return ":/icons/fem-solver.svg"
def attach(self, vobj):
self.ViewObject = vobj
self.Object = vobj.Object
def updateData(self, obj, prop):
return
def onChanged(self, vobj, prop):
return
def setEdit(self, vobj, mode=0):
import _TaskPanelJobControl
taskd = _TaskPanelJobControl._TaskPanelJobControl(self.Object)
FreeCADGui.Control.showDialog(taskd)
return True
def unsetEdit(self, vobj, mode=0):
FreeCADGui.Control.closeDialog()
return
def doubleClicked(self, vobj):
doc = FreeCADGui.getDocument(vobj.Object.Document)
if not doc.getInEdit():
# may be go the other way around and just activate the analysis the user has doubleClicked on ?!
if FemGui.getActiveAnalysis() is not None:
if FemGui.getActiveAnalysis().Document is FreeCAD.ActiveDocument:
if self.Object in FemGui.getActiveAnalysis().Member:
doc.setEdit(vobj.Object.Name)
else:
FreeCAD.Console.PrintError('Activate the analysis this solver belongs to!\n')
else:
FreeCAD.Console.PrintError('Active Analysis is not in active Document!\n')
else:
FreeCAD.Console.PrintError('No active Analysis found!\n')
else:
FreeCAD.Console.PrintError('Active Task Dialog found! Please close this one first!\n')
return True
def __getstate__(self):
return None
def __setstate__(self, state):
return None