FEM: reformat codebase

This commit is contained in:
lyphrowny
2024-06-19 14:29:15 +03:00
parent 8675aa87b4
commit 359c1ae2bb
302 changed files with 6702 additions and 9421 deletions

View File

@@ -45,8 +45,7 @@ ANALYSIS_TYPES = ["static", "frequency", "thermomech", "check", "buckling"]
def create(doc, name="SolverCalculiX"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class _BaseSolverCalculix:
@@ -58,21 +57,16 @@ class _BaseSolverCalculix:
obj.AnalysisType = temp_analysis_type
else:
FreeCAD.Console.PrintWarning(
"Analysis type {} not found. Standard is used.\n"
.format(temp_analysis_type)
f"Analysis type {temp_analysis_type} not found. Standard is used.\n"
)
obj.AnalysisType = ANALYSIS_TYPES[0]
self.add_attributes(obj)
def add_attributes(self, obj):
if not hasattr(obj, "AnalysisType"):
obj.addProperty(
"App::PropertyEnumeration",
"AnalysisType",
"Fem",
"Type of the analysis"
"App::PropertyEnumeration", "AnalysisType", "Fem", "Type of the analysis"
)
obj.AnalysisType = ANALYSIS_TYPES
obj.AnalysisType = ANALYSIS_TYPES[0]
@@ -83,7 +77,7 @@ class _BaseSolverCalculix:
"App::PropertyEnumeration",
"GeometricalNonlinearity",
"Fem",
"Set geometrical nonlinearity"
"Set geometrical nonlinearity",
)
obj.GeometricalNonlinearity = choices_geom_nonlinear
obj.GeometricalNonlinearity = choices_geom_nonlinear[0]
@@ -94,7 +88,7 @@ class _BaseSolverCalculix:
"App::PropertyEnumeration",
"MaterialNonlinearity",
"Fem",
"Set material nonlinearity"
"Set material nonlinearity",
)
obj.MaterialNonlinearity = choices_material_nonlinear
obj.MaterialNonlinearity = choices_material_nonlinear[0]
@@ -104,7 +98,7 @@ class _BaseSolverCalculix:
"App::PropertyIntegerConstraint",
"EigenmodesCount",
"Fem",
"Number of modes for frequency calculations"
"Number of modes for frequency calculations",
)
obj.EigenmodesCount = (10, 1, 100, 1)
@@ -113,7 +107,7 @@ class _BaseSolverCalculix:
"App::PropertyFloatConstraint",
"EigenmodeLowLimit",
"Fem",
"Low frequency limit for eigenmode calculations"
"Low frequency limit for eigenmode calculations",
)
obj.EigenmodeLowLimit = (0.0, 0.0, 1000000.0, 10000.0)
@@ -122,20 +116,19 @@ class _BaseSolverCalculix:
"App::PropertyFloatConstraint",
"EigenmodeHighLimit",
"Fem",
"High frequency limit for eigenmode calculations"
"High frequency limit for eigenmode calculations",
)
obj.EigenmodeHighLimit = (1000000.0, 0.0, 1000000.0, 10000.0)
if not hasattr(obj, "IterationsMaximum"):
help_string_IterationsMaximum = (
"Maximum Number of iterations "
"in each time step before stopping jobs"
"Maximum Number of iterations in each time step before stopping jobs"
)
obj.addProperty(
"App::PropertyIntegerConstraint",
"IterationsMaximum",
"Fem",
help_string_IterationsMaximum
help_string_IterationsMaximum,
)
obj.IterationsMaximum = 2000
@@ -148,43 +141,29 @@ class _BaseSolverCalculix:
"App::PropertyIntegerConstraint",
"BucklingFactors",
"Fem",
"Calculates the lowest buckling modes to the corresponding buckling factors"
"Calculates the lowest buckling modes to the corresponding buckling factors",
)
obj.BucklingFactors = 1
if not hasattr(obj, "TimeInitialStep"):
obj.addProperty(
"App::PropertyFloatConstraint",
"TimeInitialStep",
"Fem",
"Initial time steps"
"App::PropertyFloatConstraint", "TimeInitialStep", "Fem", "Initial time steps"
)
obj.TimeInitialStep = 0.01
if not hasattr(obj, "TimeEnd"):
obj.addProperty(
"App::PropertyFloatConstraint",
"TimeEnd",
"Fem",
"End time analysis"
)
obj.addProperty("App::PropertyFloatConstraint", "TimeEnd", "Fem", "End time analysis")
obj.TimeEnd = 1.0
if not hasattr(obj, "TimeMinimumStep"):
obj.addProperty(
"App::PropertyFloatConstraint",
"TimeMinimumStep",
"Fem",
"Minimum time step"
"App::PropertyFloatConstraint", "TimeMinimumStep", "Fem", "Minimum time step"
)
obj.TimeMinimumStep = 0.00001
if not hasattr(obj, "TimeMaximumStep"):
obj.addProperty(
"App::PropertyFloatConstraint",
"TimeMaximumStep",
"Fem",
"Maximum time step"
"App::PropertyFloatConstraint", "TimeMaximumStep", "Fem", "Maximum time step"
)
obj.TimeMaximumStep = 1.0
@@ -193,7 +172,7 @@ class _BaseSolverCalculix:
"App::PropertyBool",
"ThermoMechSteadyState",
"Fem",
"Choose between steady state thermo mech or transient thermo mech analysis"
"Choose between steady state thermo mech or transient thermo mech analysis",
)
obj.ThermoMechSteadyState = True
@@ -202,16 +181,13 @@ class _BaseSolverCalculix:
"App::PropertyBool",
"IterationsControlParameterTimeUse",
"Fem",
"Use the user defined time incrementation control parameter"
"Use the user defined time incrementation control parameter",
)
obj.IterationsControlParameterTimeUse = False
if not hasattr(obj, "SplitInputWriter"):
obj.addProperty(
"App::PropertyBool",
"SplitInputWriter",
"Fem",
"Split writing of ccx input file"
"App::PropertyBool", "SplitInputWriter", "Fem", "Split writing of ccx input file"
)
obj.SplitInputWriter = False
@@ -234,28 +210,26 @@ class _BaseSolverCalculix:
"App::PropertyString",
"IterationsControlParameterIter",
"Fem",
"User defined time incrementation iterations control parameter"
"User defined time incrementation iterations control parameter",
)
obj.IterationsControlParameterIter = control_parameter_iterations
if not hasattr(obj, "IterationsControlParameterCutb"):
control_parameter_cutback = (
"{D_f},{D_C},{D_B},{D_A},{D_S},{D_H},{D_D},{W_G}".format(
D_f=0.25,
D_C=0.5,
D_B=0.75,
D_A=0.85,
D_S="",
D_H="",
D_D=1.5,
W_G="",
)
control_parameter_cutback = "{D_f},{D_C},{D_B},{D_A},{D_S},{D_H},{D_D},{W_G}".format(
D_f=0.25,
D_C=0.5,
D_B=0.75,
D_A=0.85,
D_S="",
D_H="",
D_D=1.5,
W_G="",
)
obj.addProperty(
"App::PropertyString",
"IterationsControlParameterCutb",
"Fem",
"User defined time incrementation cutbacks control parameter"
"User defined time incrementation cutbacks control parameter",
)
obj.IterationsControlParameterCutb = control_parameter_cutback
@@ -268,7 +242,7 @@ class _BaseSolverCalculix:
"App::PropertyBool",
"IterationsUserDefinedIncrementations",
"Fem",
stringIterationsUserDefinedIncrementations
stringIterationsUserDefinedIncrementations,
)
obj.IterationsUserDefinedIncrementations = False
@@ -281,7 +255,7 @@ class _BaseSolverCalculix:
"App::PropertyBool",
"IterationsUserDefinedTimeStepLength",
"Fem",
help_string_IterationsUserDefinedTimeStepLength
help_string_IterationsUserDefinedTimeStepLength,
)
obj.IterationsUserDefinedTimeStepLength = False
@@ -292,13 +266,10 @@ class _BaseSolverCalculix:
"pardiso",
"spooles",
"iterativescaling",
"iterativecholesky"
"iterativecholesky",
]
obj.addProperty(
"App::PropertyEnumeration",
"MatrixSolverType",
"Fem",
"Type of solver to use"
"App::PropertyEnumeration", "MatrixSolverType", "Fem", "Type of solver to use"
)
obj.MatrixSolverType = known_ccx_solver_types
obj.MatrixSolverType = known_ccx_solver_types[0]
@@ -308,7 +279,7 @@ class _BaseSolverCalculix:
"App::PropertyBool",
"BeamShellResultOutput3D",
"Fem",
"Output 3D results for 1D and 2D analysis "
"Output 3D results for 1D and 2D analysis ",
)
obj.BeamShellResultOutput3D = True
@@ -317,7 +288,7 @@ class _BaseSolverCalculix:
"App::PropertyBool",
"BeamReducedIntegration",
"Fem",
"Set to True to use beam elements with reduced integration"
"Set to True to use beam elements with reduced integration",
)
obj.BeamReducedIntegration = True
@@ -326,48 +297,33 @@ class _BaseSolverCalculix:
"App::PropertyIntegerConstraint",
"OutputFrequency",
"Fem",
"Set the output frequency in increments"
"Set the output frequency in increments",
)
obj.OutputFrequency = 1
if not hasattr(obj, "ModelSpace"):
model_space_types = [
"3D",
"plane stress",
"plane strain",
"axisymmetric"
]
obj.addProperty(
"App::PropertyEnumeration",
"ModelSpace",
"Fem",
"Type of model space"
)
model_space_types = ["3D", "plane stress", "plane strain", "axisymmetric"]
obj.addProperty("App::PropertyEnumeration", "ModelSpace", "Fem", "Type of model space")
obj.ModelSpace = model_space_types
if not hasattr(obj, "ThermoMechType"):
thermomech_types = [
"coupled",
"uncoupled",
"pure heat transfer"
]
thermomech_types = ["coupled", "uncoupled", "pure heat transfer"]
obj.addProperty(
"App::PropertyEnumeration",
"ThermoMechType",
"Fem",
"Type of thermomechanical analysis"
"Type of thermomechanical analysis",
)
obj.ThermoMechType = thermomech_types
class Proxy(solverbase.Proxy, _BaseSolverCalculix):
"""The Fem::FemSolver's Proxy python type, add solver specific properties
"""
"""The Fem::FemSolver's Proxy python type, add solver specific properties"""
Type = "Fem::SolverCalculix"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.Proxy = self
self.add_attributes(obj)
@@ -376,19 +332,21 @@ class Proxy(solverbase.Proxy, _BaseSolverCalculix):
def createMachine(self, obj, directory, testmode=False):
return run.Machine(
solver=obj, directory=directory,
solver=obj,
directory=directory,
check=tasks.Check(),
prepare=tasks.Prepare(),
solve=tasks.Solve(),
results=tasks.Results(),
testmode=testmode)
testmode=testmode,
)
def editSupported(self):
return True
def edit(self, directory):
pattern = os.path.join(directory, "*.inp")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)

View File

@@ -56,15 +56,16 @@ class Check(run.Check):
# workaround use Calculix ccxtools pre checks
from femtools.checksanalysis import check_member_for_solver_calculix
message = check_member_for_solver_calculix(
self.analysis,
self.solver,
membertools.get_mesh_to_solve(self.analysis)[0],
membertools.AnalysisMember(self.analysis)
membertools.AnalysisMember(self.analysis),
)
if message:
text = "CalculiX can not be started...\n"
self.report.error("{}{}".format(text, message))
self.report.error(f"{text}{message}")
self.fail()
return
@@ -94,7 +95,7 @@ class Prepare(run.Prepare):
mesh_obj,
meshdatagetter.member,
self.directory,
meshdatagetter.mat_geo_sets
meshdatagetter.mat_geo_sets,
)
path = w.write_solver_input()
# report to user if task succeeded
@@ -124,7 +125,7 @@ class Solve(run.Solve):
[binary, "-i", _inputFileName],
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)
self.signalAbort.add(self._process.terminate)
# output = self._observeSolver(self._process)
@@ -138,8 +139,7 @@ class Solve(run.Solve):
class Results(run.Results):
def run(self):
prefs = FreeCAD.ParamGet(
"User parameter:BaseApp/Preferences/Mod/Fem/General")
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
if not prefs.GetBool("KeepResultsOnReRun", False):
self.purge_results()
self.load_results()
@@ -163,32 +163,22 @@ class Results(run.Results):
self.load_ccxdat_results()
def load_ccxfrd_results(self):
frd_result_file = os.path.join(
self.directory, _inputFileName + ".frd")
frd_result_file = os.path.join(self.directory, _inputFileName + ".frd")
if os.path.isfile(frd_result_file):
result_name_prefix = "CalculiX_" + self.solver.AnalysisType + "_"
importCcxFrdResults.importFrd(
frd_result_file, self.analysis, result_name_prefix)
importCcxFrdResults.importFrd(frd_result_file, self.analysis, result_name_prefix)
else:
# TODO: use solver framework status message system
FreeCAD.Console.PrintError(
"FEM: No results found at {}!\n"
.format(frd_result_file)
)
FreeCAD.Console.PrintError(f"FEM: No results found at {frd_result_file}!\n")
self.fail()
def load_ccxdat_results(self):
dat_result_file = os.path.join(
self.directory, _inputFileName + ".dat")
dat_result_file = os.path.join(self.directory, _inputFileName + ".dat")
if os.path.isfile(dat_result_file):
mode_frequencies = importCcxDatResults.import_dat(
dat_result_file, self.analysis)
mode_frequencies = importCcxDatResults.import_dat(dat_result_file, self.analysis)
else:
# TODO: use solver framework status message system
FreeCAD.Console.PrintError(
"FEM: No results found at {}!\n"
.format(dat_result_file)
)
FreeCAD.Console.PrintError(f"FEM: No results found at {dat_result_file}!\n")
self.fail()
if mode_frequencies:
for m in membertools.get_member(self.analysis, "Fem::FemResultObject"):
@@ -197,4 +187,5 @@ class Results(run.Results):
if m.Eigenmode == mf["eigenmode"]:
m.EigenmodeFrequency = mf["frequency"]
## @}

View File

@@ -1,6 +1,6 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#/***************************************************************************
# /***************************************************************************
# * Copyright (c) 2024 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * This file is part of FreeCAD. *
@@ -58,12 +58,12 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, bodyheatsource_obj, ccxwriter):
f.write("*ELSET,ELSET={}\n".format(bodyheatsource_obj.Name))
f.write(f"*ELSET,ELSET={bodyheatsource_obj.Name}\n")
if isinstance(femobj["FEMElements"], str):
f.write("{}\n".format(femobj["FEMElements"]))
else:
for e in femobj["FEMElements"]:
f.write("{},\n".format(e))
f.write(f"{e},\n")
def write_constraint(f, femobj, bodyheatsource_obj, ccxwriter):
@@ -75,7 +75,9 @@ def write_constraint(f, femobj, bodyheatsource_obj, ccxwriter):
ref_sub_obj = ref[1][0]
density = None
for mat in ccxwriter.member.mats_linear:
mat_ref = [*itertools.chain(*[itertools.product([i[0]],i[1]) for i in mat["Object"].References])]
mat_ref = [
*itertools.chain(*[itertools.product([i[0]], i[1]) for i in mat["Object"].References])
]
if (ref_feat, ref_sub_obj) in mat_ref:
density = FreeCAD.Units.Quantity(mat["Object"].Material["Density"])
break
@@ -95,9 +97,5 @@ def write_constraint(f, femobj, bodyheatsource_obj, ccxwriter):
heat = bodyheatsource_obj.TotalPower / FreeCAD.Units.Quantity(volume, "mm^3")
# write to file
f.write("*DFLUX\n")
f.write(
"{},BF,{:.13G}\n".format(
bodyheatsource_obj.Name, heat.getValueAs("t/(mm*s^3)").Value
)
)
f.write("{},BF,{:.13G}\n".format(bodyheatsource_obj.Name, heat.getValueAs("t/(mm*s^3)").Value))
f.write("\n")

View File

@@ -60,12 +60,12 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, centrif_obj, ccxwriter):
f.write("*ELSET,ELSET={}\n".format(centrif_obj.Name))
f.write(f"*ELSET,ELSET={centrif_obj.Name}\n")
if isinstance(femobj["FEMElements"], str):
f.write("{}\n".format(femobj["FEMElements"]))
else:
for e in femobj["FEMElements"]:
f.write("{},\n".format(e))
f.write(f"{e},\n")
def write_constraint(f, femobj, centrif_obj, ccxwriter):
@@ -85,22 +85,21 @@ def write_constraint(f, femobj, centrif_obj, ccxwriter):
else: # no line found, set default
# TODO: No test at all in the writer
# they should all be before in prechecks
location = FreeCAD.Vector(0., 0., 0.)
direction = FreeCAD.Vector(0., 0., 1.)
location = FreeCAD.Vector(0.0, 0.0, 0.0)
direction = FreeCAD.Vector(0.0, 0.0, 1.0)
# write to file
f.write("*DLOAD\n")
f.write(
"{},CENTRIF,{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G}\n"
.format(
"{},CENTRIF,{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G}\n".format(
centrif_obj.Name,
(2. * math.pi * float(centrif_obj.RotationFrequency.getValueAs("1/s"))) ** 2,
(2.0 * math.pi * float(centrif_obj.RotationFrequency.getValueAs("1/s"))) ** 2,
location.x,
location.y,
location.z,
direction.x,
direction.y,
direction.z
direction.z,
)
)
f.write("\n")

View File

@@ -27,7 +27,7 @@ __url__ = "https://www.freecad.org"
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -56,13 +56,13 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, contact_obj, ccxwriter):
# slave DEP
f.write("*SURFACE, NAME=DEP{}\n".format(contact_obj.Name))
f.write(f"*SURFACE, NAME=DEP{contact_obj.Name}\n")
for i in femobj["ContactSlaveFaces"]:
f.write("{},S{}\n".format(i[0], i[1]))
f.write(f"{i[0]},S{i[1]}\n")
# master IND
f.write("*SURFACE, NAME=IND{}\n".format(contact_obj.Name))
f.write(f"*SURFACE, NAME=IND{contact_obj.Name}\n")
for i in femobj["ContactMasterFaces"]:
f.write("{},S{}\n".format(i[0], i[1]))
f.write(f"{i[0]},S{i[1]}\n")
def write_constraint(f, femobj, contact_obj, ccxwriter):
@@ -70,22 +70,22 @@ def write_constraint(f, femobj, contact_obj, ccxwriter):
# floats read from ccx should use {:.13G}, see comment in writer module
adjust = ""
if contact_obj.Adjust.Value > 0:
adjust = ", ADJUST={:.13G}".format(
contact_obj.Adjust.getValueAs("mm").Value)
adjust = ", ADJUST={:.13G}".format(contact_obj.Adjust.getValueAs("mm").Value)
f.write(
"*CONTACT PAIR, INTERACTION=INT{}, TYPE=SURFACE TO SURFACE{}\n"
.format(contact_obj.Name, adjust)
"*CONTACT PAIR, INTERACTION=INT{}, TYPE=SURFACE TO SURFACE{}\n".format(
contact_obj.Name, adjust
)
)
ind_surf = "IND" + contact_obj.Name
dep_surf = "DEP" + contact_obj.Name
f.write("{}, {}\n".format(dep_surf, ind_surf))
f.write("*SURFACE INTERACTION, NAME=INT{}\n".format(contact_obj.Name))
f.write(f"{dep_surf}, {ind_surf}\n")
f.write(f"*SURFACE INTERACTION, NAME=INT{contact_obj.Name}\n")
f.write("*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR\n")
slope = contact_obj.Slope.getValueAs("MPa/mm").Value
f.write("{:.13G}\n".format(slope))
f.write(f"{slope:.13G}\n")
if contact_obj.Friction:
f.write("*FRICTION\n")
friction = contact_obj.FrictionCoefficient
stick = contact_obj.StickSlope.getValueAs("MPa/mm").Value
f.write("{:.13G}, {:.13G}\n".format(friction, stick))
f.write(f"{friction:.13G}, {stick:.13G}\n")

View File

@@ -29,7 +29,7 @@ import FreeCAD
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -57,9 +57,9 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, disp_obj, ccxwriter):
f.write("*NSET,NSET={}\n".format(disp_obj.Name))
f.write(f"*NSET,NSET={disp_obj.Name}\n")
for n in femobj["Nodes"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
def write_constraint(f, femobj, disp_obj, ccxwriter):
@@ -68,7 +68,7 @@ def write_constraint(f, femobj, disp_obj, ccxwriter):
f.write("*BOUNDARY\n")
if disp_obj.xFix:
f.write("{},1\n".format(disp_obj.Name))
f.write(f"{disp_obj.Name},1\n")
elif not disp_obj.xFree:
f.write(
"{},1,1,{}\n".format(
@@ -76,7 +76,7 @@ def write_constraint(f, femobj, disp_obj, ccxwriter):
)
)
if disp_obj.yFix:
f.write("{},2\n".format(disp_obj.Name))
f.write(f"{disp_obj.Name},2\n")
elif not disp_obj.yFree:
f.write(
"{},2,2,{}\n".format(
@@ -84,7 +84,7 @@ def write_constraint(f, femobj, disp_obj, ccxwriter):
)
)
if disp_obj.zFix:
f.write("{},3\n".format(disp_obj.Name))
f.write(f"{disp_obj.Name},3\n")
elif not disp_obj.zFree:
f.write(
"{},3,3,{}\n".format(
@@ -94,7 +94,7 @@ def write_constraint(f, femobj, disp_obj, ccxwriter):
if ccxwriter.member.geos_beamsection or ccxwriter.member.geos_shellthickness:
if disp_obj.rotxFix:
f.write("{},4\n".format(disp_obj.Name))
f.write(f"{disp_obj.Name},4\n")
elif not disp_obj.rotxFree:
f.write(
"{},4,4,{}\n".format(
@@ -102,7 +102,7 @@ def write_constraint(f, femobj, disp_obj, ccxwriter):
)
)
if disp_obj.rotyFix:
f.write("{},5\n".format(disp_obj.Name))
f.write(f"{disp_obj.Name},5\n")
elif not disp_obj.rotyFree:
f.write(
"{},5,5,{}\n".format(
@@ -110,7 +110,7 @@ def write_constraint(f, femobj, disp_obj, ccxwriter):
)
)
if disp_obj.rotzFix:
f.write("{},6\n".format(disp_obj.Name))
f.write(f"{disp_obj.Name},6\n")
elif not disp_obj.rotzFree:
f.write(
"{},6,6,{}\n".format(

View File

@@ -27,7 +27,7 @@ __url__ = "https://www.freecad.org"
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -55,37 +55,29 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, fix_obj, ccxwriter):
if (
ccxwriter.femmesh.Volumes
and (
len(ccxwriter.member.geos_shellthickness) > 0
or len(ccxwriter.member.geos_beamsection) > 0
)
if ccxwriter.femmesh.Volumes and (
len(ccxwriter.member.geos_shellthickness) > 0 or len(ccxwriter.member.geos_beamsection) > 0
):
if len(femobj["NodesSolid"]) > 0:
f.write("*NSET,NSET={}Solid\n".format(fix_obj.Name))
f.write(f"*NSET,NSET={fix_obj.Name}Solid\n")
for n in femobj["NodesSolid"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
if len(femobj["NodesFaceEdge"]) > 0:
f.write("*NSET,NSET={}FaceEdge\n".format(fix_obj.Name))
f.write(f"*NSET,NSET={fix_obj.Name}FaceEdge\n")
for n in femobj["NodesFaceEdge"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
else:
f.write("*NSET,NSET=" + fix_obj.Name + "\n")
for n in femobj["Nodes"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
def write_constraint(f, femobj, fix_obj, ccxwriter):
# floats read from ccx should use {:.13G}, see comment in writer module
if (
ccxwriter.femmesh.Volumes
and (
len(ccxwriter.member.geos_shellthickness) > 0
or len(ccxwriter.member.geos_beamsection) > 0
)
if ccxwriter.femmesh.Volumes and (
len(ccxwriter.member.geos_shellthickness) > 0 or len(ccxwriter.member.geos_beamsection) > 0
):
if len(femobj["NodesSolid"]) > 0:
f.write("*BOUNDARY\n")

View File

@@ -53,8 +53,7 @@ def handle_fluidsection_liquid_inlet_outlet(inpfile, ccxwriter):
ccxwriter.FluidInletoutlet_ele = []
ccxwriter.fluid_inout_nodes_file = join(
ccxwriter.dir_name,
"{}_inout_nodes.txt".format(ccxwriter.mesh_name)
ccxwriter.dir_name, f"{ccxwriter.mesh_name}_inout_nodes.txt"
)
def get_fluidsection_inoutlet_obj_if_setdata(matgeoset):
@@ -64,29 +63,20 @@ def handle_fluidsection_liquid_inlet_outlet(inpfile, ccxwriter):
and "fluidsection_obj" in matgeoset # fluid mesh
):
fluidsec_obj = matgeoset["fluidsection_obj"]
if (
fluidsec_obj.SectionType == "Liquid"
and (
fluidsec_obj.LiquidSectionType == "PIPE INLET"
or fluidsec_obj.LiquidSectionType == "PIPE OUTLET"
)
if fluidsec_obj.SectionType == "Liquid" and (
fluidsec_obj.LiquidSectionType == "PIPE INLET"
or fluidsec_obj.LiquidSectionType == "PIPE OUTLET"
):
return fluidsec_obj
return None
def is_fluidsection_inoutlet_setnames_possible(mat_geo_sets):
for matgeoset in mat_geo_sets:
if (
matgeoset["ccx_elset"]
and "fluidsection_obj" in matgeoset # fluid mesh
):
if matgeoset["ccx_elset"] and "fluidsection_obj" in matgeoset: # fluid mesh
fluidsec_obj = matgeoset["fluidsection_obj"]
if (
fluidsec_obj.SectionType == "Liquid"
and (
fluidsec_obj.LiquidSectionType == "PIPE INLET"
or fluidsec_obj.LiquidSectionType == "PIPE OUTLET"
)
if fluidsec_obj.SectionType == "Liquid" and (
fluidsec_obj.LiquidSectionType == "PIPE INLET"
or fluidsec_obj.LiquidSectionType == "PIPE OUTLET"
):
return True
return False
@@ -101,15 +91,15 @@ def handle_fluidsection_liquid_inlet_outlet(inpfile, ccxwriter):
counter = 0
for elid in matgeoset["ccx_elset"]:
counter = counter + 1
if (elsetchanged is False) \
and (fluidsec_obj.LiquidSectionType == "PIPE INLET"):
if (elsetchanged is False) and (fluidsec_obj.LiquidSectionType == "PIPE INLET"):
# 3rd index is to track which line nr the element is defined
ccxwriter.FluidInletoutlet_ele.append(
[str(elid), fluidsec_obj.LiquidSectionType, 0]
)
elsetchanged = True
elif (fluidsec_obj.LiquidSectionType == "PIPE OUTLET") \
and (counter == len(matgeoset["ccx_elset"])):
elif (fluidsec_obj.LiquidSectionType == "PIPE OUTLET") and (
counter == len(matgeoset["ccx_elset"])
):
# 3rd index is to track which line nr the element is defined
ccxwriter.FluidInletoutlet_ele.append(
[str(elid), fluidsec_obj.LiquidSectionType, 0]
@@ -123,9 +113,7 @@ def handle_fluidsection_liquid_inlet_outlet(inpfile, ccxwriter):
# for split input file the main file is just closed and reopend even if not needed
inpfile.close()
meshtools.use_correct_fluidinout_ele_def(
ccxwriter.FluidInletoutlet_ele,
ccxwriter.femmesh_file,
ccxwriter.fluid_inout_nodes_file
ccxwriter.FluidInletoutlet_ele, ccxwriter.femmesh_file, ccxwriter.fluid_inout_nodes_file
)
inpfile = codecs.open(ccxwriter.file_name, "a", encoding="utf-8")
@@ -146,13 +134,12 @@ def write_constraints_fluidsection(f, ccxwriter):
f.write("\n***********************************************************\n")
f.write("** FluidSection constraints\n")
if os.path.exists(ccxwriter.fluid_inout_nodes_file):
inout_nodes_file = open(ccxwriter.fluid_inout_nodes_file, "r")
inout_nodes_file = open(ccxwriter.fluid_inout_nodes_file)
lines = inout_nodes_file.readlines()
inout_nodes_file.close()
else:
FreeCAD.Console.PrintError(
"1DFlow inout nodes file not found: {}\n"
.format(ccxwriter.fluid_inout_nodes_file)
f"1DFlow inout nodes file not found: {ccxwriter.fluid_inout_nodes_file}\n"
)
# get nodes
ccxwriter.get_constraints_fluidsection_nodes()
@@ -170,12 +157,11 @@ def write_constraints_fluidsection(f, ccxwriter):
b = line.split(",")
if int(b[0]) == n and b[3] == "PIPE INLET\n":
# degree of freedom 2 is for defining pressure
f.write("{},{},{},{}\n".format(
b[0],
"2",
"2",
fluidsection_obj.InletPressure
))
f.write(
"{},{},{},{}\n".format(
b[0], "2", "2", fluidsection_obj.InletPressure
)
)
if fluidsection_obj.InletFlowRateActive is True:
f.write("*BOUNDARY,MASS FLOW \n")
for n in femobj["Nodes"]:
@@ -184,12 +170,11 @@ def write_constraints_fluidsection(f, ccxwriter):
if int(b[0]) == n and b[3] == "PIPE INLET\n":
# degree of freedom 1 is for defining flow rate
# factor applied to convert unit from kg/s to t/s
f.write("{},{},{},{}\n".format(
b[1],
"1",
"1",
fluidsection_obj.InletFlowRate * 0.001
))
f.write(
"{},{},{},{}\n".format(
b[1], "1", "1", fluidsection_obj.InletFlowRate * 0.001
)
)
elif fluidsection_obj.LiquidSectionType == "PIPE OUTLET":
f.write("**Fluid Section Outlet \n")
if fluidsection_obj.OutletPressureActive is True:
@@ -199,12 +184,11 @@ def write_constraints_fluidsection(f, ccxwriter):
b = line.split(",")
if int(b[0]) == n and b[3] == "PIPE OUTLET\n":
# degree of freedom 2 is for defining pressure
f.write("{},{},{},{}\n".format(
b[0],
"2",
"2",
fluidsection_obj.OutletPressure
))
f.write(
"{},{},{},{}\n".format(
b[0], "2", "2", fluidsection_obj.OutletPressure
)
)
if fluidsection_obj.OutletFlowRateActive is True:
f.write("*BOUNDARY,MASS FLOW \n")
for n in femobj["Nodes"]:
@@ -213,9 +197,8 @@ def write_constraints_fluidsection(f, ccxwriter):
if int(b[0]) == n and b[3] == "PIPE OUTLET\n":
# degree of freedom 1 is for defining flow rate
# factor applied to convert unit from kg/s to t/s
f.write("{},{},{},{}\n".format(
b[1],
"1",
"1",
fluidsection_obj.OutletFlowRate * 0.001
))
f.write(
"{},{},{},{}\n".format(
b[1], "1", "1", fluidsection_obj.OutletFlowRate * 0.001
)
)

View File

@@ -51,18 +51,18 @@ def write_meshdata_constraint(f, femobj, force_obj, ccxwriter):
# be careful with raising the tolerance, a big load would have an impact
# but compared to the real direction the impact would be small again
for ref_shape in femobj["NodeLoadTable"]:
f.write("** {}\n".format(ref_shape[0]))
f.write(f"** {ref_shape[0]}\n")
for n in sorted(ref_shape[1]):
node_load = ref_shape[1][n]
# the loads in ref_shape[1][n] are without unit
if abs(direction_vec.x) > dir_zero_tol:
v1 = "{:.13G}".format((direction_vec.x * node_load).Value)
f.write("{},1,{}\n".format(n, v1))
v1 = f"{(direction_vec.x * node_load).Value:.13G}"
f.write(f"{n},1,{v1}\n")
if abs(direction_vec.y) > dir_zero_tol:
v2 = "{:.13G}".format((direction_vec.y * node_load).Value)
f.write("{},2,{}\n".format(n, v2))
v2 = f"{(direction_vec.y * node_load).Value:.13G}"
f.write(f"{n},2,{v2}\n")
if abs(direction_vec.z) > dir_zero_tol:
v3 = "{:.13G}".format((direction_vec.z * node_load).Value)
f.write("{},3,{}\n".format(n, v3))
v3 = f"{(direction_vec.z * node_load).Value:.13G}"
f.write(f"{n},3,{v3}\n")
f.write("\n")
f.write("\n")

View File

@@ -51,33 +51,26 @@ def write_meshdata_constraint(f, femobj, heatflux_obj, ccxwriter):
heatflux_facetype = "F"
# SvdW: add factor to force heatflux to units system of t/mm/s/K
heatflux_values = "{:.13G},{:.13G}".format(
heatflux_obj.AmbientTemp,
heatflux_obj.FilmCoef * 0.001
heatflux_obj.AmbientTemp, heatflux_obj.FilmCoef * 0.001
)
elif heatflux_obj.ConstraintType == "Radiation":
heatflux_key_word = "RADIATE"
heatflux_facetype = "R"
heatflux_values = "{:.13G},{:.13G}".format(
heatflux_obj.AmbientTemp,
heatflux_obj.Emissivity
heatflux_obj.AmbientTemp, heatflux_obj.Emissivity
)
elif heatflux_obj.ConstraintType == "DFlux":
heatflux_key_word = "DFLUX"
heatflux_facetype = "S"
heatflux_values = "{:.13G}".format(heatflux_obj.DFlux * 0.001)
heatflux_values = f"{heatflux_obj.DFlux * 0.001:.13G}"
f.write("*{}\n".format(heatflux_key_word))
f.write(f"*{heatflux_key_word}\n")
for ref_shape in femobj["HeatFluxFaceTable"]:
elem_string = ref_shape[0]
face_table = ref_shape[1]
f.write("** Heat flux on face {}\n".format(elem_string))
f.write(f"** Heat flux on face {elem_string}\n")
for i in face_table:
# OvG: Only write out the VolumeIDs linked to a particular face
f.write("{},{}{},{}\n".format(
i[0],
heatflux_facetype,
i[1],
heatflux_values
))
f.write(f"{i[0]},{heatflux_facetype}{i[1]},{heatflux_values}\n")

View File

@@ -48,10 +48,11 @@ def write_constraint(f, femobj, inittemp_obj, ccxwriter):
# floats read from ccx should use {:.13G}, see comment in writer module
f.write("{},{}\n".format(
ccxwriter.ccx_nall,
Units.Quantity(inittemp_obj.initialTemperature.getValueAs("K"))
))
f.write(
"{},{}\n".format(
ccxwriter.ccx_nall, Units.Quantity(inittemp_obj.initialTemperature.getValueAs("K"))
)
)
# Should only be one object in the analysis

View File

@@ -30,7 +30,7 @@ from femmesh import meshtools
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -69,16 +69,18 @@ def write_meshdata_constraint(f, femobj, fric_obj, ccxwriter):
# Thus call write_node_sets_constraints_planerotation has to be
# after constraint fixed and constraint displacement
l_nodes = femobj["Nodes"]
f.write("*NSET,NSET={}\n".format(fric_obj.Name))
f.write(f"*NSET,NSET={fric_obj.Name}\n")
# Code to extract nodes and coordinates on the PlaneRotation support face
nodes_coords = []
for node in l_nodes:
nodes_coords.append((
node,
ccxwriter.femnodes_mesh[node].x,
ccxwriter.femnodes_mesh[node].y,
ccxwriter.femnodes_mesh[node].z
))
nodes_coords.append(
(
node,
ccxwriter.femnodes_mesh[node].x,
ccxwriter.femnodes_mesh[node].y,
ccxwriter.femnodes_mesh[node].z,
)
)
node_planerotation = meshtools.get_three_non_colinear_nodes(nodes_coords)
for i in range(len(l_nodes)):
if l_nodes[i] not in node_planerotation:
@@ -93,7 +95,7 @@ def write_meshdata_constraint(f, femobj, fric_obj, ccxwriter):
MPC = node_planerotation[i]
MPC_nodes.append(MPC)
for i in range(len(MPC_nodes)):
f.write("{},\n".format(MPC_nodes[i]))
f.write(f"{MPC_nodes[i]},\n")
def write_constraint(f, femobj, fric_obj, ccxwriter):
@@ -101,4 +103,4 @@ def write_constraint(f, femobj, fric_obj, ccxwriter):
# floats read from ccx should use {:.13G}, see comment in writer module
f.write("*MPC\n")
f.write("PLANE,{}\n".format(fric_obj.Name))
f.write(f"PLANE,{fric_obj.Name}\n")

View File

@@ -27,6 +27,7 @@ __url__ = "https://www.freecad.org"
import FreeCAD
def get_analysis_types():
return ["buckling", "static", "thermomech"]
@@ -60,12 +61,12 @@ def write_meshdata_constraint(f, femobj, prs_obj, ccxwriter):
f.write("** " + ref_shape[0] + "\n")
for face, fno in ref_shape[1]:
if fno > 0: # solid mesh face
f.write("{},P{},{}\n".format(face, fno, press_rev))
f.write(f"{face},P{fno},{press_rev}\n")
# on shell mesh face: fno == 0
# normal of element face == face normal
elif fno == 0:
f.write("{},P,{}\n".format(face, press_rev))
f.write(f"{face},P,{press_rev}\n")
# on shell mesh face: fno == -1
# normal of element face opposite direction face normal
elif fno == -1:
f.write("{},P,{}\n".format(face, -1 * press_rev))
f.write(f"{face},P,{-1 * press_rev}\n")

View File

@@ -27,7 +27,7 @@ __url__ = "https://www.freecadweb.org"
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -58,7 +58,7 @@ def write_meshdata_constraint(f, femobj, rb_obj, ccxwriter):
f.write("*NSET,NSET=" + rb_obj.Name + "\n")
for n in femobj["Nodes"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
def write_constraint(f, femobj, rb_obj, ccxwriter):
@@ -66,19 +66,18 @@ def write_constraint(f, femobj, rb_obj, ccxwriter):
rb_obj_idx = ccxwriter.analysis.Group.index(rb_obj)
node_count = ccxwriter.mesh_object.FemMesh.NodeCount
# factor 2 is to prevent conflict with other rigid body constraint
ref_node_idx = node_count + 2*rb_obj_idx + 1
rot_node_idx = node_count + 2*rb_obj_idx + 2
ref_node_idx = node_count + 2 * rb_obj_idx + 1
rot_node_idx = node_count + 2 * rb_obj_idx + 2
f.write("*NODE\n")
f.write("{},{},{},{}\n".format(ref_node_idx, *rb_obj.ReferenceNode))
f.write("{},{},{},{}\n".format(rot_node_idx, *rb_obj.ReferenceNode))
f.write("*NSET,NSET={}_RefNode\n".format(rb_obj.Name))
f.write("{},\n".format(ref_node_idx))
f.write("*NSET,NSET={}_RotNode\n".format(rb_obj.Name))
f.write("{},\n".format(rot_node_idx))
f.write(f"*NSET,NSET={rb_obj.Name}_RefNode\n")
f.write(f"{ref_node_idx},\n")
f.write(f"*NSET,NSET={rb_obj.Name}_RotNode\n")
f.write(f"{rot_node_idx},\n")
kw_line = "*RIGID BODY, NSET={}, REF NODE={}, ROT NODE={}".format(rb_obj.Name, ref_node_idx, rot_node_idx)
kw_line = f"*RIGID BODY, NSET={rb_obj.Name}, REF NODE={ref_node_idx}, ROT NODE={rot_node_idx}"
f.write(kw_line + "\n")

View File

@@ -30,7 +30,7 @@ import FreeCAD
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -62,16 +62,16 @@ def write_constraint(f, femobj, rb_obj, ccxwriter):
rb_obj_idx = ccxwriter.analysis.Group.index(rb_obj)
node_count = ccxwriter.mesh_object.FemMesh.NodeCount
# factor 2 is to prevent conflict with other rigid body constraint
ref_node_idx = node_count + 2*rb_obj_idx + 1
rot_node_idx = node_count + 2*rb_obj_idx + 2
ref_node_idx = node_count + 2 * rb_obj_idx + 1
rot_node_idx = node_count + 2 * rb_obj_idx + 2
def write_mode(mode, node, dof, constraint, load):
if mode == "Constraint":
f.write("*BOUNDARY\n")
f.write("{},{},{},{:.13G}\n".format(node, dof, dof, constraint))
f.write(f"{node},{dof},{dof},{constraint:.13G}\n")
elif mode == "Load":
f.write("*CLOAD\n")
f.write("{},{},{:.13G}\n".format(node, dof, load))
f.write(f"{node},{dof},{load:.13G}\n")
mode = [rb_obj.TranslationalModeX, rb_obj.TranslationalModeY, rb_obj.TranslationalModeZ]
constraint = rb_obj.Displacement
@@ -80,9 +80,8 @@ def write_constraint(f, femobj, rb_obj, ccxwriter):
for i in range(3):
write_mode(mode[i], ref_node_idx, i + 1, constraint[i], load[i].getValueAs("N").Value)
mode = [rb_obj.RotationalModeX, rb_obj.RotationalModeY, rb_obj.RotationalModeZ]
load = [rb_obj.MomentX,rb_obj.MomentY, rb_obj.MomentZ]
load = [rb_obj.MomentX, rb_obj.MomentY, rb_obj.MomentZ]
# write rotation components according to rotational mode
rot = rb_obj.Rotation
@@ -96,5 +95,4 @@ def write_constraint(f, femobj, rb_obj, ccxwriter):
for i in range(3):
write_mode(mode[i], rot_node_idx, i + 1, constraint[i], load[i].getValueAs("N*mm").Value)
f.write("\n")

View File

@@ -27,7 +27,7 @@ __url__ = "https://www.freecad.org"
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -55,9 +55,9 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, sectionprint_obj, ccxwriter):
f.write("*SURFACE, NAME=SECTIONFACE{}\n".format(sectionprint_obj.Name))
f.write(f"*SURFACE, NAME=SECTIONFACE{sectionprint_obj.Name}\n")
for i in femobj["SectionPrintFaces"]:
f.write("{},S{}\n".format(i[0], i[1]))
f.write(f"{i[0]},S{i[1]}\n")
def write_constraint(f, femobj, sectionprint_obj, ccxwriter):
@@ -73,7 +73,8 @@ def write_constraint(f, femobj, sectionprint_obj, ccxwriter):
key = "DRAG"
f.write(
"*SECTION PRINT, SURFACE=SECTIONFACE{}, NAME=SECTIONPRINT{}\n"
.format(sectionprint_obj.Name, sectionprint_obj.Name)
"*SECTION PRINT, SURFACE=SECTIONFACE{}, NAME=SECTIONPRINT{}\n".format(
sectionprint_obj.Name, sectionprint_obj.Name
)
)
f.write(key + "\n")

View File

@@ -49,13 +49,14 @@ def write_constraint(f, femobj, selwei_obj, ccxwriter):
f.write("*DLOAD\n")
f.write(
# elset, GRAV, magnitude, direction x, dir y ,dir z
"{},GRAV,{:.13G},{:.13G},{:.13G},{:.13G}\n"
.format(
"{},GRAV,{:.13G},{:.13G},{:.13G},{:.13G}\n".format(
ccxwriter.ccx_eall,
selwei_obj.GravityAcceleration.getValueAs("mm/s^2").Value, # actual magnitude of gravity vector
selwei_obj.GravityAcceleration.getValueAs(
"mm/s^2"
).Value, # actual magnitude of gravity vector
selwei_obj.GravityDirection.x, # coordinate x of normalized gravity vector
selwei_obj.GravityDirection.y, # y
selwei_obj.GravityDirection.z # z
selwei_obj.GravityDirection.z, # z
)
)
f.write("\n")

View File

@@ -42,9 +42,9 @@ def get_constraint_title():
def write_meshdata_constraint(f, femobj, temp_obj, ccxwriter):
f.write("*NSET,NSET={}\n".format(temp_obj.Name))
f.write(f"*NSET,NSET={temp_obj.Name}\n")
for n in femobj["Nodes"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
def get_before_write_meshdata_constraint():
@@ -82,7 +82,7 @@ def write_constraint(f, femobj, temp_obj, ccxwriter):
f.write(
"{},11,{}\n".format(
temp_obj.Name,
FreeCAD.Units.Quantity(temp_obj.CFlux.getValueAs("mW")) / NumberOfNodes
FreeCAD.Units.Quantity(temp_obj.CFlux.getValueAs("mW")) / NumberOfNodes,
)
)
f.write("\n")

View File

@@ -30,7 +30,7 @@ from FreeCAD import Units, Vector
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -59,13 +59,13 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, tie_obj, ccxwriter):
# slave DEP
f.write("*SURFACE, NAME=TIE_DEP{}\n".format(tie_obj.Name))
f.write(f"*SURFACE, NAME=TIE_DEP{tie_obj.Name}\n")
for i in femobj["TieSlaveFaces"]:
f.write("{},S{}\n".format(i[0], i[1]))
f.write(f"{i[0]},S{i[1]}\n")
# master IND
f.write("*SURFACE, NAME=TIE_IND{}\n".format(tie_obj.Name))
f.write(f"*SURFACE, NAME=TIE_IND{tie_obj.Name}\n")
for i in femobj["TieMasterFaces"]:
f.write("{},S{}\n".format(i[0], i[1]))
f.write(f"{i[0]},S{i[1]}\n")
def write_constraint(f, femobj, tie_obj, ccxwriter):
@@ -83,18 +83,20 @@ def write_constraint(f, femobj, tie_obj, ccxwriter):
symmetry = ", CYCLIC SYMMETRY"
f.write(
"*TIE, POSITION TOLERANCE={:.13G}{}{}, NAME=TIE{}\n"
.format(tolerance, adjust, symmetry, tie_name)
"*TIE, POSITION TOLERANCE={:.13G}{}{}, NAME=TIE{}\n".format(
tolerance, adjust, symmetry, tie_name
)
)
ind_surf = "TIE_IND{}".format(tie_name)
dep_surf = "TIE_DEP{}".format(tie_name)
f.write("{}, {}\n".format(dep_surf, ind_surf))
ind_surf = f"TIE_IND{tie_name}"
dep_surf = f"TIE_DEP{tie_name}"
f.write(f"{dep_surf}, {ind_surf}\n")
# write CYCLIC SYMMETRY MODEL card
if tie_obj.CyclicSymmetry:
f.write(
"*CYCLIC SYMMETRY MODEL, N={}, NGRAPH={}, TIE=TIE{}, ELSET=Eall\n"
.format(tie_obj.Sectors, tie_obj.ConnectedSectors, tie_name)
"*CYCLIC SYMMETRY MODEL, N={}, NGRAPH={}, TIE=TIE{}, ELSET=Eall\n".format(
tie_obj.Sectors, tie_obj.ConnectedSectors, tie_name
)
)
# get symmetry axis points
@@ -105,7 +107,4 @@ def write_constraint(f, femobj, tie_obj, ccxwriter):
point_a = [set_unit(coord) for coord in vec_a]
point_b = [set_unit(coord) for coord in vec_b]
f.write(
"{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G}\n"
.format(*point_a, *point_b)
)
f.write("{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G}\n".format(*point_a, *point_b))

View File

@@ -32,7 +32,7 @@ from femtools import geomtools
def get_analysis_types():
return "all" # write for all analysis types
return "all" # write for all analysis types
def get_sets_name():
@@ -61,11 +61,11 @@ def get_after_write_constraint():
def write_meshdata_constraint(f, femobj, trans_obj, ccxwriter):
if trans_obj.TransformType == "Rectangular":
f.write("*NSET,NSET=Rect{}\n".format(trans_obj.Name))
f.write(f"*NSET,NSET=Rect{trans_obj.Name}\n")
elif trans_obj.TransformType == "Cylindrical":
f.write("*NSET,NSET=Cylin{}\n".format(trans_obj.Name))
f.write(f"*NSET,NSET=Cylin{trans_obj.Name}\n")
for n in femobj["Nodes"]:
f.write("{},\n".format(n))
f.write(f"{n},\n")
def write_constraint(f, femobj, trans_obj, ccxwriter):
@@ -86,16 +86,20 @@ def write_constraint(f, femobj, trans_obj, ccxwriter):
base = trans_obj.BasePoint
axis = trans_obj.Axis
coords = list(base) + list(base + axis)
f.write("*TRANSFORM, NSET={}{}, TYPE={}\n".format(
trans_name,
trans_obj.Name,
trans_type,
))
f.write("{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G}\n".format(
coords[0],
coords[1],
coords[2],
coords[3],
coords[4],
coords[5],
))
f.write(
"*TRANSFORM, NSET={}{}, TYPE={}\n".format(
trans_name,
trans_obj.Name,
trans_type,
)
)
f.write(
"{:.13G},{:.13G},{:.13G},{:.13G},{:.13G},{:.13G}\n".format(
coords[0],
coords[1],
coords[2],
coords[3],
coords[4],
coords[5],
)
)

View File

@@ -43,9 +43,7 @@ def write_femelement_geometry(f, ccxwriter):
# in CalxuliX called the 1direction
# see meshtools.get_beam_main_axis_m(beam_direction, defined_angle)
section_nor = "{:.13G}, {:.13G}, {:.13G}\n".format(
beam_axis_m[0],
beam_axis_m[1],
beam_axis_m[2]
beam_axis_m[0], beam_axis_m[1], beam_axis_m[2]
)
print(section_nor)
if beamsec_obj.SectionType == "Rectangular":
@@ -61,31 +59,19 @@ def write_femelement_geometry(f, ccxwriter):
len_beam_axis_n = beamsec_obj.RectHeight.getValueAs("mm").Value
len_beam_axis_m = beamsec_obj.RectWidth.getValueAs("mm").Value
section_type = ", SECTION=RECT"
section_geo = "{:.13G},{:.13G}\n".format(len_beam_axis_m, len_beam_axis_n)
section_def = "*BEAM SECTION, {}{}{}\n".format(
elsetdef,
material,
section_type
)
section_geo = f"{len_beam_axis_m:.13G},{len_beam_axis_n:.13G}\n"
section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n"
elif beamsec_obj.SectionType == "Circular":
diameter = beamsec_obj.CircDiameter.getValueAs("mm").Value
section_type = ", SECTION=CIRC"
section_geo = "{:.13G}\n".format(diameter)
section_def = "*BEAM SECTION, {}{}{}\n".format(
elsetdef,
material,
section_type
)
section_geo = f"{diameter:.13G}\n"
section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n"
elif beamsec_obj.SectionType == "Pipe":
radius = 0.5 * beamsec_obj.PipeDiameter.getValueAs("mm").Value
thickness = beamsec_obj.PipeThickness.getValueAs("mm").Value
section_type = ", SECTION=PIPE"
section_geo = "{:.13G},{:.13G}\n".format(radius, thickness)
section_def = "*BEAM SECTION, {}{}{}\n".format(
elsetdef,
material,
section_type
)
section_geo = f"{radius:.13G},{thickness:.13G}\n"
section_def = f"*BEAM SECTION, {elsetdef}{material}{section_type}\n"
f.write(section_def)
f.write(section_geo)
f.write(section_nor)
@@ -96,9 +82,7 @@ def write_femelement_geometry(f, ccxwriter):
if (section_type == "PIPE INLET") or (section_type == "PIPE OUTLET"):
section_type = "PIPE INOUT"
section_def = "*FLUID SECTION, {}TYPE={}, {}\n".format(
elsetdef,
section_type,
material
elsetdef, section_type, material
)
section_geo = liquid_section_def(fluidsec_obj, section_type)
"""
@@ -114,15 +98,15 @@ def write_femelement_geometry(f, ccxwriter):
elif "shellthickness_obj" in matgeoset: # shell mesh
shellth_obj = matgeoset["shellthickness_obj"]
if ccxwriter.solver_obj.ModelSpace == "3D":
section_def = "*SHELL SECTION, {}{}\n".format(elsetdef, material)
section_def = f"*SHELL SECTION, {elsetdef}{material}\n"
else:
section_def = "*SOLID SECTION, {}{}\n".format(elsetdef, material)
section_def = f"*SOLID SECTION, {elsetdef}{material}\n"
thickness = shellth_obj.Thickness.getValueAs("mm").Value
section_geo = "{:.13G}\n".format(thickness)
section_geo = f"{thickness:.13G}\n"
f.write(section_def)
f.write(section_geo)
f.write(section_geo)
else: # solid mesh
section_def = "*SOLID SECTION, {}{}\n".format(elsetdef, material)
section_def = f"*SOLID SECTION, {elsetdef}{material}\n"
f.write(section_def)
@@ -134,68 +118,63 @@ def liquid_section_def(obj, section_type):
manning_radius = obj.ManningRadius.getValueAs("mm").Value
manning_coefficient = obj.ManningCoefficient
section_geo = "{:.13G},{:.13G},{:.13G}\n".format(
manning_area,
manning_radius,
manning_coefficient
manning_area, manning_radius, manning_coefficient
)
return section_geo
elif section_type == "PIPE ENLARGEMENT":
enlarge_area1 = obj.EnlargeArea1.getValueAs("mm^2").Value
enlarge_area2 = obj.EnlargeArea2.getValueAs("mm^2").Value
section_geo = "{:.13G},{:.13G}\n".format(enlarge_area1, enlarge_area2)
section_geo = f"{enlarge_area1:.13G},{enlarge_area2:.13G}\n"
return section_geo
elif section_type == "PIPE CONTRACTION":
contract_area1 = obj.ContractArea1.getValueAs("mm^2").Value
contract_area2 = obj.ContractArea2.getValueAs("mm^2").Value
section_geo = "{:.13G},{:.13G}\n".format(contract_area1, contract_area2)
section_geo = f"{contract_area1:.13G},{contract_area2:.13G}\n"
return section_geo
elif section_type == "PIPE ENTRANCE":
entrance_pipe_area = obj.EntrancePipeArea.getValueAs("mm^2").Value
entrance_area = obj.EntranceArea.getValueAs("mm^2").Value
section_geo = "{:.13G},{:.13G}\n".format(entrance_pipe_area, entrance_area)
section_geo = f"{entrance_pipe_area:.13G},{entrance_area:.13G}\n"
return section_geo
elif section_type == "PIPE DIAPHRAGM":
diaphragm_pipe_area = obj.DiaphragmPipeArea.getValueAs("mm^2").Value
diaphragm_area = obj.DiaphragmArea.getValueAs("mm^2").Value
section_geo = "{:.13G},{:.13G}\n".format(diaphragm_pipe_area, diaphragm_area)
section_geo = f"{diaphragm_pipe_area:.13G},{diaphragm_area:.13G}\n"
return section_geo
elif section_type == "PIPE BEND":
bend_pipe_area = obj.BendPipeArea.getValueAs("mm^2").Value
bend_radius_diameter = obj.BendRadiusDiameter
bend_angle = obj.BendAngle
bend_loss_coefficient = obj.BendLossCoefficient
section_geo = ("{:.13G},{:.13G},{:.13G},{:.13G}\n".format(
bend_pipe_area,
bend_radius_diameter,
bend_angle,
bend_loss_coefficient
))
section_geo = "{:.13G},{:.13G},{:.13G},{:.13G}\n".format(
bend_pipe_area, bend_radius_diameter, bend_angle, bend_loss_coefficient
)
return section_geo
elif section_type == "PIPE GATE VALVE":
gatevalve_pipe_area = obj.GateValvePipeArea.getValueAs("mm^2").Value
gatevalve_closing_coeff = obj.GateValveClosingCoeff
section_geo = "{:.13G},{:.13G}\n".format(gatevalve_pipe_area, gatevalve_closing_coeff)
section_geo = f"{gatevalve_pipe_area:.13G},{gatevalve_closing_coeff:.13G}\n"
return section_geo
elif section_type == "PIPE WHITE-COLEBROOK":
colebrooke_area = obj.ColebrookeArea.getValueAs("mm^2").Value
colebrooke_diameter = 2 * obj.ColebrookeRadius.getValueAs("mm")
colebrooke_grain_diameter = obj.ColebrookeGrainDiameter.getValueAs("mm")
colebrooke_form_factor = obj.ColebrookeFormFactor
section_geo = ("{:.13G},{:.13G},{},{:.13G},{:.13G}\n".format(
section_geo = "{:.13G},{:.13G},{},{:.13G},{:.13G}\n".format(
colebrooke_area,
colebrooke_diameter,
"-1",
colebrooke_grain_diameter,
colebrooke_form_factor
))
colebrooke_form_factor,
)
return section_geo
elif section_type == "LIQUID PUMP":
section_geo = ""
for i in range(len(obj.PumpFlowRate)):
flow_rate = obj.PumpFlowRate[i]
top = obj.PumpHeadLoss[i]
section_geo = "{:.13G},{:.13G},\n".format(section_geo + flow_rate, top)
section_geo = "{}\n".format(section_geo)
section_geo = f"{section_geo + flow_rate:.13G},{top:.13G},\n"
section_geo = f"{section_geo}\n"
return section_geo
else:
return ""

View File

@@ -77,7 +77,7 @@ def write_femelement_material(f, ccxwriter):
SH = FreeCAD.Units.Quantity(mat_obj.Material["SpecificHeat"])
# SvdW: Add factor to force units to results base units of t/mm/s/K
# FIXME: why not get it directly in the units needed ?
SH_in_JkgK = SH.getValueAs("J/kg/K").Value * 1e+06
SH_in_JkgK = SH.getValueAs("J/kg/K").Value * 1e06
if mat_obj.Category == "Solid":
TEC = FreeCAD.Units.Quantity(mat_obj.Material["ThermalExpansionCoefficient"])
TEC_in_mmK = TEC.getValueAs("mm/mm/K").Value
@@ -86,26 +86,26 @@ def write_femelement_material(f, ccxwriter):
DV_in_tmms = DV.getValueAs("t/mm/s").Value
# write material properties
f.write("** FreeCAD material name: {}\n".format(mat_info_name))
f.write("** {}\n".format(mat_label))
f.write("*MATERIAL, NAME={}\n".format(mat_name))
f.write(f"** FreeCAD material name: {mat_info_name}\n")
f.write(f"** {mat_label}\n")
f.write(f"*MATERIAL, NAME={mat_name}\n")
if mat_obj.Category == "Solid":
f.write("*ELASTIC\n")
f.write("{:.13G},{:.13G}\n".format(YM_in_MPa, PR))
f.write(f"{YM_in_MPa:.13G},{PR:.13G}\n")
if is_density_needed() is True:
f.write("*DENSITY\n")
f.write("{:.13G}\n".format(density_in_tonne_per_mm3))
f.write(f"{density_in_tonne_per_mm3:.13G}\n")
if ccxwriter.analysis_type == "thermomech":
if mat_obj.Category == "Solid":
f.write("*CONDUCTIVITY\n")
f.write("{:.13G}\n".format(TC_in_WmK))
f.write(f"{TC_in_WmK:.13G}\n")
f.write("*EXPANSION\n")
f.write("{:.13G}\n".format(TEC_in_mmK))
f.write(f"{TEC_in_mmK:.13G}\n")
f.write("*SPECIFIC HEAT\n")
f.write("{:.13G}\n".format(SH_in_JkgK))
f.write(f"{SH_in_JkgK:.13G}\n")
elif mat_obj.Category == "Fluid":
f.write("*FLUID CONSTANTS\n")
f.write("{:.13G},{:.13G}\n".format(SH_in_JkgK, DV_in_tmms))
f.write(f"{SH_in_JkgK:.13G},{DV_in_tmms:.13G}\n")
# nonlinear material properties
if ccxwriter.solver_obj.MaterialNonlinearity == "nonlinear":
@@ -119,5 +119,5 @@ def write_femelement_material(f, ccxwriter):
else:
f.write("*PLASTIC, HARDENING=KINEMATIC\n")
for yield_point in nl_mat_obj.YieldPoints:
f.write("{}\n".format(yield_point))
f.write(f"{yield_point}\n")
f.write("\n")

View File

@@ -34,20 +34,14 @@ def write_footer(f, ccxwriter):
f.write("\n{}\n".format(59 * "*"))
f.write("** CalculiX Input file\n")
f.write("** written by --> FreeCAD {}.{}.{}\n".format(
ccxwriter.fc_ver[0],
ccxwriter.fc_ver[1],
ccxwriter.fc_ver[2]
))
f.write("** written on --> {}\n".format(
time.ctime()
))
f.write("** file name --> {}\n".format(
os.path.basename(ccxwriter.document.FileName)
))
f.write("** analysis name --> {}\n".format(
ccxwriter.analysis.Name
))
f.write(
"** written by --> FreeCAD {}.{}.{}\n".format(
ccxwriter.fc_ver[0], ccxwriter.fc_ver[1], ccxwriter.fc_ver[2]
)
)
f.write(f"** written on --> {time.ctime()}\n")
f.write(f"** file name --> {os.path.basename(ccxwriter.document.FileName)}\n")
f.write(f"** analysis name --> {ccxwriter.analysis.Name}\n")
f.write("**\n")
f.write("**\n")
f.write(ccxwriter.units_information)

View File

@@ -67,13 +67,13 @@ def write_mesh(ccxwriter):
group_param,
volVariant=vol_variant,
faceVariant=face_variant,
edgeVariant=edge_variant
edgeVariant=edge_variant,
)
inpfile = codecs.open(ccxwriter.file_name, "w", encoding="utf-8")
inpfile.write("{}\n".format(59 * "*"))
inpfile.write("** {}\n".format(write_name))
inpfile.write("*INCLUDE,INPUT={}\n".format(file_name_split))
inpfile.write(f"** {write_name}\n")
inpfile.write(f"*INCLUDE,INPUT={file_name_split}\n")
else:
ccxwriter.femmesh_file = ccxwriter.file_name
@@ -83,7 +83,7 @@ def write_mesh(ccxwriter):
group_param,
volVariant=vol_variant,
faceVariant=face_variant,
edgeVariant=edge_variant
edgeVariant=edge_variant,
)
# reopen file with "append" to add all the rest

View File

@@ -47,11 +47,8 @@ def write_step_equation(f, ccxwriter):
)
if ccxwriter.solver_obj.IterationsMaximum:
if ccxwriter.analysis_type == "thermomech" or ccxwriter.analysis_type == "static":
step += ", INC={}".format(ccxwriter.solver_obj.IterationsMaximum)
elif (
ccxwriter.analysis_type == "frequency"
or ccxwriter.analysis_type == "buckling"
):
step += f", INC={ccxwriter.solver_obj.IterationsMaximum}"
elif ccxwriter.analysis_type == "frequency" or ccxwriter.analysis_type == "buckling":
# parameter is for thermomechanical analysis only, see ccx manual *STEP
pass
# write STEP line
@@ -126,23 +123,27 @@ def write_step_equation(f, ccxwriter):
# ANALYSIS parameter line
analysis_parameter = ""
if ccxwriter.analysis_type == "static" or ccxwriter.analysis_type == "check":
if ccxwriter.solver_obj.IterationsUserDefinedIncrementations is True \
or ccxwriter.solver_obj.IterationsUserDefinedTimeStepLength is True:
if (
ccxwriter.solver_obj.IterationsUserDefinedIncrementations is True
or ccxwriter.solver_obj.IterationsUserDefinedTimeStepLength is True
):
analysis_parameter = "{},{},{},{}".format(
ccxwriter.solver_obj.TimeInitialStep,
ccxwriter.solver_obj.TimeEnd,
ccxwriter.solver_obj.TimeMinimumStep,
ccxwriter.solver_obj.TimeMaximumStep
ccxwriter.solver_obj.TimeMaximumStep,
)
elif ccxwriter.analysis_type == "frequency":
if ccxwriter.solver_obj.EigenmodeLowLimit == 0.0 \
and ccxwriter.solver_obj.EigenmodeHighLimit == 0.0:
analysis_parameter = "{}\n".format(ccxwriter.solver_obj.EigenmodesCount)
if (
ccxwriter.solver_obj.EigenmodeLowLimit == 0.0
and ccxwriter.solver_obj.EigenmodeHighLimit == 0.0
):
analysis_parameter = f"{ccxwriter.solver_obj.EigenmodesCount}\n"
else:
analysis_parameter = "{},{},{}\n".format(
ccxwriter.solver_obj.EigenmodesCount,
ccxwriter.solver_obj.EigenmodeLowLimit,
ccxwriter.solver_obj.EigenmodeHighLimit
ccxwriter.solver_obj.EigenmodeHighLimit,
)
elif ccxwriter.analysis_type == "thermomech":
# OvG: 1.0 increment, total time 1 for steady state will cut back automatically
@@ -150,10 +151,10 @@ def write_step_equation(f, ccxwriter):
ccxwriter.solver_obj.TimeInitialStep,
ccxwriter.solver_obj.TimeEnd,
ccxwriter.solver_obj.TimeMinimumStep,
ccxwriter.solver_obj.TimeMaximumStep
ccxwriter.solver_obj.TimeMaximumStep,
)
elif ccxwriter.analysis_type == "buckling":
analysis_parameter = "{}\n".format(ccxwriter.solver_obj.BucklingFactors)
analysis_parameter = f"{ccxwriter.solver_obj.BucklingFactors}\n"
# write analysis type line, analysis parameter line
f.write(analysis_type + "\n")

View File

@@ -94,18 +94,22 @@ def write_step_output(f, ccxwriter):
or femobj["Object"].TranslationalModeY != "Free"
or femobj["Object"].TranslationalModeZ != "Free"
):
f.write("*NODE PRINT, NSET={}_RefNode, TOTALS=ONLY\n".format(femobj["Object"].Name))
f.write(
"*NODE PRINT, NSET={}_RefNode, TOTALS=ONLY\n".format(femobj["Object"].Name)
)
f.write("RF\n")
if (
femobj["Object"].RotationalModeX != "Free"
or femobj["Object"].RotationalModeY != "Free"
or femobj["Object"].RotationalModeZ != "Free"
):
f.write("*NODE PRINT, NSET={}_RotNode, TOTALS=ONLY\n".format(femobj["Object"].Name))
f.write(
"*NODE PRINT, NSET={}_RotNode, TOTALS=ONLY\n".format(femobj["Object"].Name)
)
f.write("RF\n")
if ccxwriter.member.cons_fixed or ccxwriter.member.cons_displacement:
f.write("\n")
f.write("*OUTPUT, FREQUENCY={}".format(ccxwriter.solver_obj.OutputFrequency))
f.write(f"*OUTPUT, FREQUENCY={ccxwriter.solver_obj.OutputFrequency}")
# there is no need to write all integration point results
# as long as there is no reader for them

View File

@@ -108,22 +108,10 @@ units_information = """*********************************************************
class FemInputWriterCcx(writerbase.FemInputWriter):
def __init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name=None,
mat_geo_sets=None
self, analysis_obj, solver_obj, mesh_obj, member, dir_name=None, mat_geo_sets=None
):
writerbase.FemInputWriter.__init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name,
mat_geo_sets
self, analysis_obj, solver_obj, mesh_obj, member, dir_name, mat_geo_sets
)
self.mesh_name = self.mesh_object.Name
self.file_name = join(self.dir_name, self.mesh_name + ".inp")
@@ -138,10 +126,7 @@ class FemInputWriterCcx(writerbase.FemInputWriter):
time_start = time.process_time()
FreeCAD.Console.PrintMessage("\n") # because of time print in separate line
FreeCAD.Console.PrintMessage("CalculiX solver input writing...\n")
FreeCAD.Console.PrintMessage(
"Input file:{}\n"
.format(self.file_name)
)
FreeCAD.Console.PrintMessage(f"Input file:{self.file_name}\n")
if self.solver_obj.SplitInputWriter is True:
FreeCAD.Console.PrintMessage("Split input file.\n")
@@ -161,7 +146,9 @@ class FemInputWriterCcx(writerbase.FemInputWriter):
# element sets constraints
self.write_constraints_meshsets(inpfile, self.member.cons_centrif, con_centrif)
self.write_constraints_meshsets(inpfile, self.member.cons_bodyheatsource, con_bodyheatsource)
self.write_constraints_meshsets(
inpfile, self.member.cons_bodyheatsource, con_bodyheatsource
)
# node sets
self.write_constraints_meshsets(inpfile, self.member.cons_fixed, con_fixed)
@@ -193,12 +180,16 @@ class FemInputWriterCcx(writerbase.FemInputWriter):
# constraints dependent from steps
self.write_constraints_propdata(inpfile, self.member.cons_fixed, con_fixed)
self.write_constraints_propdata(inpfile, self.member.cons_rigidbody_step, con_rigidbody_step)
self.write_constraints_propdata(
inpfile, self.member.cons_rigidbody_step, con_rigidbody_step
)
self.write_constraints_propdata(inpfile, self.member.cons_displacement, con_displacement)
self.write_constraints_propdata(inpfile, self.member.cons_sectionprint, con_sectionprint)
self.write_constraints_propdata(inpfile, self.member.cons_selfweight, con_selfweight)
self.write_constraints_propdata(inpfile, self.member.cons_centrif, con_centrif)
self.write_constraints_propdata(inpfile, self.member.cons_bodyheatsource, con_bodyheatsource)
self.write_constraints_propdata(
inpfile, self.member.cons_bodyheatsource, con_bodyheatsource
)
self.write_constraints_meshsets(inpfile, self.member.cons_force, con_force)
self.write_constraints_meshsets(inpfile, self.member.cons_pressure, con_pressure)
self.write_constraints_propdata(inpfile, self.member.cons_temperature, con_temperature)
@@ -216,17 +207,14 @@ class FemInputWriterCcx(writerbase.FemInputWriter):
inpfile.close()
writetime = round((time.process_time() - time_start), 3)
FreeCAD.Console.PrintMessage(
"Writing time CalculiX input file: {} seconds.\n".format(writetime)
)
FreeCAD.Console.PrintMessage(f"Writing time CalculiX input file: {writetime} seconds.\n")
# return
if self.femelement_count_test is True:
return self.file_name
else:
FreeCAD.Console.PrintError(
"Problems on writing input file, check report prints.\n\n"
)
FreeCAD.Console.PrintError("Problems on writing input file, check report prints.\n\n")
return ""
## @}

View File

@@ -34,8 +34,7 @@ from ... import equationbase
def create(doc, name="Deformation"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(nonlinear.Proxy, equationbase.DeformationProxy):
@@ -43,51 +42,40 @@ class Proxy(nonlinear.Proxy, equationbase.DeformationProxy):
Type = "Fem::EquationElmerDeformation"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
"CalculatePangle",
"Deformation",
"Compute principal stress angles"
"App::PropertyBool", "CalculatePangle", "Deformation", "Compute principal stress angles"
)
obj.addProperty(
"App::PropertyBool",
"CalculatePrincipal",
"Deformation",
"Compute principal stress components"
"Compute principal stress components",
)
obj.addProperty(
"App::PropertyBool",
"CalculateStrains",
"Deformation",
"Compute the strain tensor"
"App::PropertyBool", "CalculateStrains", "Deformation", "Compute the strain tensor"
)
obj.addProperty(
"App::PropertyBool",
"CalculateStresses",
"Deformation",
"Compute stress tensor and vanMises"
"Compute stress tensor and vanMises",
)
obj.addProperty(
"App::PropertyBool",
"InitializeStateVariables",
"Deformation",
"See Elmer manual for info"
"See Elmer manual for info",
)
obj.addProperty(
"App::PropertyBool",
"MixedFormulation",
"Deformation",
"See Elmer manual for info"
"App::PropertyBool", "MixedFormulation", "Deformation", "See Elmer manual for info"
)
obj.addProperty(
"App::PropertyBool",
"NeoHookeanMaterial",
"Deformation",
(
"Uses the neo-Hookean material model"
)
("Uses the neo-Hookean material model"),
)
obj.addProperty(
"App::PropertyBool",
@@ -96,13 +84,13 @@ class Proxy(nonlinear.Proxy, equationbase.DeformationProxy):
(
"Computes solution according to plane\nstress situation.\n"
"Applies only for 2D geometry."
)
),
)
obj.addProperty(
"App::PropertyString",
"Variable",
"Deformation",
"Only for a 2D model change the '3' to '2'"
"Only for a 2D model change the '3' to '2'",
)
obj.Priority = 10
@@ -114,4 +102,5 @@ class Proxy(nonlinear.Proxy, equationbase.DeformationProxy):
class ViewProxy(nonlinear.ViewProxy, equationbase.DeformationViewProxy):
pass
## @}

View File

@@ -151,7 +151,7 @@ class DeformationWriter:
gravity = self.write.convert(obj.GravityAcceleration.toStr(), "L/T^2")
if self.write.getBodyMaterial(name) is None:
raise general_writer.WriteError(
"The body {} is not referenced in any material.\n\n".format(name)
f"The body {name} is not referenced in any material.\n\n"
)
m = self.write.getBodyMaterial(name).Material
@@ -193,17 +193,13 @@ class DeformationWriter:
# get the material data for all bodies
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies()
)
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
# don't evaluate fluid material
if self.write.isBodyMaterialFluid(name):
break
if "YoungsModulus" not in m:
Console.PrintMessage("m: {}\n".format(m))
Console.PrintMessage(f"m: {m}\n")
# it is no fluid but also no solid
# -> user set no material reference at all
# that now material is known
@@ -213,22 +209,14 @@ class DeformationWriter:
)
self.write.material(name, "Name", m["Name"])
if density_needed is True:
self.write.material(
name, "Density",
self.write.getDensity(m)
)
self.write.material(
name, "Youngs Modulus",
self._getYoungsModulus(m)
)
self.write.material(
name, "Poisson ratio",
float(m["PoissonRatio"])
)
self.write.material(name, "Density", self.write.getDensity(m))
self.write.material(name, "Youngs Modulus", self._getYoungsModulus(m))
self.write.material(name, "Poisson ratio", float(m["PoissonRatio"]))
if tempObj:
self.write.material(
name, "Heat expansion Coefficient",
self.write.convert(m["ThermalExpansionCoefficient"], "O^-1")
name,
"Heat expansion Coefficient",
self.write.convert(m["ThermalExpansionCoefficient"], "O^-1"),
)
def _getYoungsModulus(self, m):
@@ -237,4 +225,5 @@ class DeformationWriter:
youngsModulus *= 1e3
return youngsModulus
## @}

View File

@@ -39,13 +39,12 @@ EIGEN_SYSTEM_SELECT = [
"Smallest Real Part",
"Largest Real Part",
"Smallest Imag Part",
"Largest Imag Part"
"Largest Imag Part",
]
def create(doc, name="Elasticity"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(linear.Proxy, equationbase.ElasticityProxy):
@@ -53,52 +52,37 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
Type = "Fem::EquationElmerElasticity"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
"CalculatePangle",
"Elasticity",
"Compute principal stress angles"
"App::PropertyBool", "CalculatePangle", "Elasticity", "Compute principal stress angles"
)
obj.addProperty(
"App::PropertyBool",
"CalculatePrincipal",
"Elasticity",
"Compute principal stress components"
"Compute principal stress components",
)
obj.addProperty(
"App::PropertyBool",
"CalculateStrains",
"Elasticity",
"Compute the strain tensor"
"App::PropertyBool", "CalculateStrains", "Elasticity", "Compute the strain tensor"
)
obj.addProperty(
"App::PropertyBool",
"CalculateStresses",
"Elasticity",
"Compute stress tensor and vanMises"
"Compute stress tensor and vanMises",
)
obj.addProperty(
"App::PropertyBool",
"ConstantBulkSystem",
"Elasticity",
"See Elmer manual for info"
"App::PropertyBool", "ConstantBulkSystem", "Elasticity", "See Elmer manual for info"
)
obj.addProperty(
"App::PropertyBool",
"DisplaceMesh",
"Elasticity",
(
"If mesh is deformed by displacement field.\n"
"Set to False for 'Eigen Analysis'."
)
("If mesh is deformed by displacement field.\nSet to False for 'Eigen Analysis'."),
)
obj.addProperty(
"App::PropertyBool",
"EigenAnalysis",
"Eigen Values",
"If true, modal analysis"
"App::PropertyBool", "EigenAnalysis", "Eigen Values", "If true, modal analysis"
)
obj.addProperty(
"App::PropertyBool",
@@ -107,13 +91,13 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"Should be true if eigen system is complex\n"
"Must be false for a damped eigen value analysis."
)
),
)
obj.addProperty(
"App::PropertyBool",
"EigenSystemComputeResiduals",
"Eigen Values",
"Computes residuals of eigen value system"
"Computes residuals of eigen value system",
)
obj.addProperty(
"App::PropertyBool",
@@ -122,19 +106,19 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"Set a damped eigen analysis. Can only be\n"
"used if 'Linear Solver Type' is 'Iterative'."
)
),
)
obj.addProperty(
"App::PropertyIntegerConstraint",
"EigenSystemMaxIterations",
"Eigen Values",
"Max iterations for iterative eigensystem solver"
"Max iterations for iterative eigensystem solver",
)
obj.addProperty(
"App::PropertyEnumeration",
"EigenSystemSelect",
"Eigen Values",
"Which eigenvalues are computed"
"Which eigenvalues are computed",
)
obj.addProperty(
"App::PropertyFloat",
@@ -143,25 +127,22 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"Convergence tolerance for iterative eigensystem solve\n"
"Default is 100 times the 'Linear Tolerance'"
)
),
)
obj.addProperty(
"App::PropertyInteger",
"EigenSystemValues",
"Eigen Values",
"Number of lowest eigen modes"
"Number of lowest eigen modes",
)
obj.addProperty(
"App::PropertyBool",
"FixDisplacement",
"Elasticity",
"If displacements or forces are set,\nthereby model lumping is used"
"If displacements or forces are set,\nthereby model lumping is used",
)
obj.addProperty(
"App::PropertyBool",
"GeometricStiffness",
"Elasticity",
"Consider geometric stiffness"
"App::PropertyBool", "GeometricStiffness", "Elasticity", "Consider geometric stiffness"
)
obj.addProperty(
"App::PropertyBool",
@@ -170,25 +151,20 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"Computation of incompressible material in connection\n"
"with viscoelastic Maxwell material and a custom 'Variable'"
)
),
)
obj.addProperty(
"App::PropertyBool",
"MaxwellMaterial",
"Elasticity",
"Compute viscoelastic material model"
)
obj.addProperty(
"App::PropertyBool",
"ModelLumping",
"Elasticity",
"Use model lumping"
"Compute viscoelastic material model",
)
obj.addProperty("App::PropertyBool", "ModelLumping", "Elasticity", "Use model lumping")
obj.addProperty(
"App::PropertyFile",
"ModelLumpingFilename",
"Elasticity",
"File to save results from model lumping to"
"File to save results from model lumping to",
)
obj.addProperty(
"App::PropertyBool",
@@ -197,13 +173,10 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"If true, 'Eigen Analysis' is stability analysis.\n"
"Otherwise modal analysis is performed."
)
),
)
obj.addProperty(
"App::PropertyBool",
"UpdateTransientSystem",
"Elasticity",
"See Elmer manual for info"
"App::PropertyBool", "UpdateTransientSystem", "Elasticity", "See Elmer manual for info"
)
obj.addProperty(
"App::PropertyString",
@@ -212,7 +185,7 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"Only change this if 'Incompressible' is set to true\n"
"according to the Elmer manual."
)
),
)
obj.addProperty(
@@ -222,7 +195,7 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
(
"Computes solution according to plane\nstress situation.\n"
"Applies only for 2D geometry."
)
),
)
obj.EigenSystemValues = 5
@@ -243,8 +216,8 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
obj.EigenSystemSelect = "Smallest Magnitude"
# according to Elmer manual default is 100 times the Linear Tolerance
# since this is a small value we must set an expression, see linear.py for background
for (prop, expr) in obj.ExpressionEngine:
if (prop == "LinearTolerance"):
for prop, expr in obj.ExpressionEngine:
if prop == "LinearTolerance":
obj.setExpression("EigenSystemTolerance", str(100 * obj.evalExpression(expr)))
obj.Variable = "Displacement"
@@ -252,4 +225,5 @@ class Proxy(linear.Proxy, equationbase.ElasticityProxy):
class ViewProxy(linear.ViewProxy, equationbase.ElasticityViewProxy):
pass
## @}

View File

@@ -63,8 +63,7 @@ class ElasticityWriter:
s["Displace mesh"] = equation.DisplaceMesh
s["Eigen Analysis"] = equation.EigenAnalysis
if equation.EigenAnalysis is True:
s["Eigen System Convergence Tolerance"] = \
equation.EigenSystemTolerance
s["Eigen System Convergence Tolerance"] = equation.EigenSystemTolerance
s["Eigen System Complex"] = equation.EigenSystemComplex
if equation.EigenSystemComputeResiduals is True:
s["Eigen System Compute Residuals"] = equation.EigenSystemComputeResiduals
@@ -110,7 +109,7 @@ class ElasticityWriter:
(
"Only change this if 'Incompressible' is set to true\n"
"according to the Elmer manual."
)
),
)
equation.Variable = "Displacement"
if hasattr(equation, "Bubbles"):
@@ -118,10 +117,7 @@ class ElasticityWriter:
equation.removeProperty("Bubbles")
if not hasattr(equation, "ConstantBulkSystem"):
equation.addProperty(
"App::PropertyBool",
"ConstantBulkSystem",
"Elasticity",
"See Elmer manual for info"
"App::PropertyBool", "ConstantBulkSystem", "Elasticity", "See Elmer manual for info"
)
if not hasattr(equation, "DisplaceMesh"):
equation.addProperty(
@@ -131,7 +127,7 @@ class ElasticityWriter:
(
"If mesh is deformed by displacement field.\n"
"Set to False for 'Eigen Analysis'."
)
),
)
# DisplaceMesh is true except if DoFrequencyAnalysis is true
equation.DisplaceMesh = True
@@ -142,10 +138,7 @@ class ElasticityWriter:
# DoFrequencyAnalysis was renamed to EigenAnalysis
# to follow the Elmer manual
equation.addProperty(
"App::PropertyBool",
"EigenAnalysis",
"Eigen Values",
"If true, modal analysis"
"App::PropertyBool", "EigenAnalysis", "Eigen Values", "If true, modal analysis"
)
if hasattr(equation, "DoFrequencyAnalysis"):
equation.EigenAnalysis = equation.DoFrequencyAnalysis
@@ -158,7 +151,7 @@ class ElasticityWriter:
(
"Should be true if eigen system is complex\n"
"Must be false for a damped eigen value analysis."
)
),
)
equation.EigenSystemComplex = True
if not hasattr(equation, "EigenSystemComputeResiduals"):
@@ -166,7 +159,7 @@ class ElasticityWriter:
"App::PropertyBool",
"EigenSystemComputeResiduals",
"Eigen Values",
"Computes residuals of eigen value system"
"Computes residuals of eigen value system",
)
if not hasattr(equation, "EigenSystemDamped"):
equation.addProperty(
@@ -176,14 +169,14 @@ class ElasticityWriter:
(
"Set a damped eigen analysis. Can only be\n"
"used if 'Linear Solver Type' is 'Iterative'."
)
),
)
if not hasattr(equation, "EigenSystemMaxIterations"):
equation.addProperty(
"App::PropertyIntegerConstraint",
"EigenSystemMaxIterations",
"Eigen Values",
"Max iterations for iterative eigensystem solver"
"Max iterations for iterative eigensystem solver",
)
equation.EigenSystemMaxIterations = (300, 1, int(1e8), 1)
if not hasattr(equation, "EigenSystemSelect"):
@@ -191,7 +184,7 @@ class ElasticityWriter:
"App::PropertyEnumeration",
"EigenSystemSelect",
"Eigen Values",
"Which eigenvalues are computed"
"Which eigenvalues are computed",
)
equation.EigenSystemSelect = elasticity.EIGEN_SYSTEM_SELECT
equation.EigenSystemSelect = "Smallest Magnitude"
@@ -203,7 +196,7 @@ class ElasticityWriter:
(
"Convergence tolerance for iterative eigensystem solve\n"
"Default is 100 times the 'Linear Tolerance'"
)
),
)
equation.setExpression("EigenSystemTolerance", str(100 * equation.LinearTolerance))
if not hasattr(equation, "EigenSystemValues"):
@@ -213,7 +206,7 @@ class ElasticityWriter:
"App::PropertyInteger",
"EigenSystemValues",
"Eigen Values",
"Number of lowest eigen modes"
"Number of lowest eigen modes",
)
if hasattr(equation, "EigenmodesCount"):
equation.EigenSystemValues = equation.EigenmodesCount
@@ -223,14 +216,14 @@ class ElasticityWriter:
"App::PropertyBool",
"FixDisplacement",
"Elasticity",
"If displacements or forces are set,\nthereby model lumping is used"
"If displacements or forces are set,\nthereby model lumping is used",
)
if not hasattr(equation, "GeometricStiffness"):
equation.addProperty(
"App::PropertyBool",
"GeometricStiffness",
"Elasticity",
"Consider geometric stiffness"
"Consider geometric stiffness",
)
if not hasattr(equation, "Incompressible"):
equation.addProperty(
@@ -240,28 +233,25 @@ class ElasticityWriter:
(
"Computation of incompressible material in connection\n"
"with viscoelastic Maxwell material and a custom 'Variable'"
)
),
)
if not hasattr(equation, "MaxwellMaterial"):
equation.addProperty(
"App::PropertyBool",
"MaxwellMaterial",
"Elasticity",
"Compute viscoelastic material model"
"Compute viscoelastic material model",
)
if not hasattr(equation, "ModelLumping"):
equation.addProperty(
"App::PropertyBool",
"ModelLumping",
"Elasticity",
"Use model lumping"
"App::PropertyBool", "ModelLumping", "Elasticity", "Use model lumping"
)
if not hasattr(equation, "ModelLumpingFilename"):
equation.addProperty(
"App::PropertyFile",
"ModelLumpingFilename",
"Elasticity",
"File to save results from model lumping to"
"File to save results from model lumping to",
)
if not hasattr(equation, "PlaneStress"):
equation.addProperty(
@@ -271,7 +261,7 @@ class ElasticityWriter:
(
"Computes solution according to plane\nstress situation.\n"
"Applies only for 2D geometry."
)
),
)
if not hasattr(equation, "StabilityAnalysis"):
equation.addProperty(
@@ -281,14 +271,14 @@ class ElasticityWriter:
(
"If true, 'Eigen Analysis' is stability analysis.\n"
"Otherwise modal analysis is performed."
)
),
)
if not hasattr(equation, "UpdateTransientSystem"):
equation.addProperty(
"App::PropertyBool",
"UpdateTransientSystem",
"Elasticity",
"See Elmer manual for info"
"See Elmer manual for info",
)
def handleElasticityConstants(self):
@@ -367,7 +357,7 @@ class ElasticityWriter:
gravity = self.write.convert(obj.GravityAcceleration.toStr(), "L/T^2")
if self.write.getBodyMaterial(name) is None:
raise general_writer.WriteError(
"The body {} is not referenced in any material.\n\n".format(name)
f"The body {name} is not referenced in any material.\n\n"
)
m = self.write.getBodyMaterial(name).Material
@@ -409,17 +399,13 @@ class ElasticityWriter:
# get the material data for all bodies
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies()
)
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
# don't evaluate fluid material
if self.write.isBodyMaterialFluid(name):
break
if "YoungsModulus" not in m:
Console.PrintMessage("m: {}\n".format(m))
Console.PrintMessage(f"m: {m}\n")
# it is no fluid but also no solid
# -> user set no material reference at all
# that now material is known
@@ -429,22 +415,14 @@ class ElasticityWriter:
)
self.write.material(name, "Name", m["Name"])
if density_needed is True:
self.write.material(
name, "Density",
self.write.getDensity(m)
)
self.write.material(
name, "Youngs Modulus",
self._getYoungsModulus(m)
)
self.write.material(
name, "Poisson ratio",
float(m["PoissonRatio"])
)
self.write.material(name, "Density", self.write.getDensity(m))
self.write.material(name, "Youngs Modulus", self._getYoungsModulus(m))
self.write.material(name, "Poisson ratio", float(m["PoissonRatio"]))
if tempObj:
self.write.material(
name, "Heat expansion Coefficient",
self.write.convert(m["ThermalExpansionCoefficient"], "O^-1")
name,
"Heat expansion Coefficient",
self.write.convert(m["ThermalExpansionCoefficient"], "O^-1"),
)
def _getYoungsModulus(self, m):
@@ -453,4 +431,5 @@ class ElasticityWriter:
youngsModulus *= 1e3
return youngsModulus
## @}

View File

@@ -36,8 +36,7 @@ SOLVER_EXEC_METHODS = ["After Timestep", "Always"]
def create(doc, name="Electricforce"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(linear.Proxy, equationbase.ElectricforceProxy):
@@ -45,7 +44,7 @@ class Proxy(linear.Proxy, equationbase.ElectricforceProxy):
Type = "Fem::EquationElmerElectricforce"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyEnumeration",
@@ -54,7 +53,7 @@ class Proxy(linear.Proxy, equationbase.ElectricforceProxy):
(
"That solver is only executed after solution converged\n"
"To execute always, change to 'Always'"
)
),
)
obj.ExecSolver = SOLVER_EXEC_METHODS
@@ -68,4 +67,5 @@ class Proxy(linear.Proxy, equationbase.ElectricforceProxy):
class ViewProxy(linear.ViewProxy, equationbase.ElectricforceViewProxy):
pass
## @}

View File

@@ -60,9 +60,10 @@ class EFwriter:
(
"That solver is only executed after solution converged\n"
"To execute always, change to 'Always'"
)
),
)
equation.ExecSolver = electricforce.SOLVER_EXEC_METHODS
equation.ExecSolver = "After Timestep"
## @}

View File

@@ -35,8 +35,7 @@ from . import linear
def create(doc, name="Electrostatic"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(linear.Proxy, equationbase.ElectrostaticProxy):
@@ -44,38 +43,13 @@ class Proxy(linear.Proxy, equationbase.ElectrostaticProxy):
Type = "Fem::EquationElmerElectrostatic"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
"CalculateCapacitanceMatrix",
"Electrostatic",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElectricEnergy",
"Electrostatic",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElectricField",
"Electrostatic",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElectricFlux",
"Electrostatic",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateSurfaceCharge",
"Electrostatic",
""
)
obj.addProperty("App::PropertyBool", "CalculateCapacitanceMatrix", "Electrostatic", "")
obj.addProperty("App::PropertyBool", "CalculateElectricEnergy", "Electrostatic", "")
obj.addProperty("App::PropertyBool", "CalculateElectricField", "Electrostatic", "")
obj.addProperty("App::PropertyBool", "CalculateElectricFlux", "Electrostatic", "")
obj.addProperty("App::PropertyBool", "CalculateSurfaceCharge", "Electrostatic", "")
"""
obj.addProperty(
"App::PropertyInteger",
@@ -91,13 +65,13 @@ class Proxy(linear.Proxy, equationbase.ElectrostaticProxy):
(
"File where capacitance matrix is being saved\n"
"Only used if 'CalculateCapacitanceMatrix' is true"
)
),
)
obj.addProperty(
"App::PropertyBool",
"ConstantWeights",
"Electrostatic",
"Use constant weighting for results"
"Use constant weighting for results",
)
obj.addProperty(
"App::PropertyFloat",
@@ -106,7 +80,7 @@ class Proxy(linear.Proxy, equationbase.ElectrostaticProxy):
(
"Potential difference in Volt for which capacitance is\n"
"calculated if 'CalculateCapacitanceMatrix' is false"
)
),
)
obj.CapacitanceMatrixFilename = "cmatrix.dat"
@@ -117,4 +91,5 @@ class Proxy(linear.Proxy, equationbase.ElectrostaticProxy):
class ViewProxy(linear.ViewProxy, equationbase.ElectrostaticViewProxy):
pass
## @}

View File

@@ -63,10 +63,7 @@ class ESwriter:
s["Constant Weights"] = equation.ConstantWeights
s["Exec Solver"] = "Always"
s["Optimize Bandwidth"] = True
if (
equation.CalculateCapacitanceMatrix is False
and (equation.PotentialDifference != 0.0)
):
if equation.CalculateCapacitanceMatrix is False and (equation.PotentialDifference != 0.0):
s["Potential Difference"] = equation.PotentialDifference
s["Stabilize"] = equation.Stabilize
return s
@@ -81,7 +78,7 @@ class ESwriter:
(
"File where capacitance matrix is being saved\n"
"Only used if 'CalculateCapacitanceMatrix' is true"
)
),
)
equation.CapacitanceMatrixFilename = "cmatrix.dat"
if not hasattr(equation, "ConstantWeights"):
@@ -89,7 +86,7 @@ class ESwriter:
"App::PropertyBool",
"ConstantWeights",
"Electrostatic",
"Use constant weighting for results"
"Use constant weighting for results",
)
if not hasattr(equation, "PotentialDifference"):
equation.addProperty(
@@ -99,14 +96,13 @@ class ESwriter:
(
"Potential difference in Volt for which capacitance is\n"
"calculated if 'CalculateCapacitanceMatrix' is false"
)
),
)
equation.PotentialDifference = 0.0
def handleElectrostaticConstants(self):
permittivity = self.write.convert(
self.write.constsdef["PermittivityOfVacuum"],
"T^4*I^2/(L^3*M)"
self.write.constsdef["PermittivityOfVacuum"], "T^4*I^2/(L^3*M)"
)
permittivity = round(permittivity, 20) # to get rid of numerical artifacts
self.write.constant("Permittivity Of Vacuum", permittivity)
@@ -114,16 +110,12 @@ class ESwriter:
def handleElectrostaticMaterial(self, bodies):
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies())
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
self.write.material(name, "Name", m["Name"])
if "RelativePermittivity" in m:
self.write.material(
name, "Relative Permittivity",
float(m["RelativePermittivity"])
name, "Relative Permittivity", float(m["RelativePermittivity"])
)
def handleElectrostaticBndConditions(self):
@@ -143,7 +135,7 @@ class ESwriter:
"App::PropertyElectricPotential",
"Potential",
"Parameter",
"Electric Potential"
"Electric Potential",
)
# scale to match SI units
obj.Potential = savePotential * 1e6
@@ -160,4 +152,5 @@ class ESwriter:
self.write.boundary(name, "Capacitance Body", obj.CapacitanceBody)
self.write.handled(obj)
## @}

View File

@@ -40,7 +40,7 @@ if App.GuiUp:
class Proxy(equationbase.BaseProxy):
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyInteger",
"Priority",
@@ -49,7 +49,7 @@ class Proxy(equationbase.BaseProxy):
"Number of your choice\n"
"The equation with highest number\n"
"will be solved first."
)
),
)
@@ -72,14 +72,13 @@ class ViewProxy(equationbase.BaseViewProxy):
return None
class _TaskPanel(object):
class _TaskPanel:
def __init__(self, obj):
self._obj = obj
self._refWidget = selection_widgets.SolidSelector()
self._refWidget.setReferences(obj.References)
propWidget = obj.ViewObject.Proxy.getTaskWidget(
obj.ViewObject)
propWidget = obj.ViewObject.Proxy.getTaskWidget(obj.ViewObject)
if propWidget is None:
self.form = self._refWidget
else:

View File

@@ -38,8 +38,7 @@ FLOW_MODEL = ["Full", "No convection", "Stokes"]
def create(doc, name="Flow"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(nonlinear.Proxy, equationbase.FlowProxy):
@@ -47,7 +46,7 @@ class Proxy(nonlinear.Proxy, equationbase.FlowProxy):
Type = "Fem::EquationElmerFlow"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
@@ -56,14 +55,9 @@ class Proxy(nonlinear.Proxy, equationbase.FlowProxy):
(
"Set to true for incompressible flow for more stable\n"
"discretization when Reynolds number increases"
)
)
obj.addProperty(
"App::PropertyEnumeration",
"FlowModel",
"Flow",
"Flow model to be used"
),
)
obj.addProperty("App::PropertyEnumeration", "FlowModel", "Flow", "Flow model to be used")
obj.addProperty(
"App::PropertyBool",
"GradpDiscretization",
@@ -71,20 +65,14 @@ class Proxy(nonlinear.Proxy, equationbase.FlowProxy):
(
"If true pressure Dirichlet boundary conditions can be used.\n"
"Also mass flux is available as a natural boundary condition."
)
),
)
obj.addProperty(
"App::PropertyString",
"Variable",
"Flow",
"Only for a 2D model change the '3' to '2'"
"App::PropertyString", "Variable", "Flow", "Only for a 2D model change the '3' to '2'"
)
obj.addProperty(
"App::PropertyEnumeration",
"Convection",
"Equation",
"Type of convection to be used"
"App::PropertyEnumeration", "Convection", "Equation", "Type of convection to be used"
)
obj.addProperty(
"App::PropertyBool",
@@ -93,7 +81,7 @@ class Proxy(nonlinear.Proxy, equationbase.FlowProxy):
(
"Magnetic induction equation will be solved\n"
"along with the Navier-Stokes equations"
)
),
)
obj.FlowModel = FLOW_MODEL
obj.FlowModel = "Full"
@@ -106,4 +94,5 @@ class Proxy(nonlinear.Proxy, equationbase.FlowProxy):
class ViewProxy(nonlinear.ViewProxy, equationbase.FlowViewProxy):
pass
## @}

View File

@@ -72,7 +72,7 @@ class Flowwriter:
"App::PropertyEnumeration",
"Convection",
"Equation",
"Type of convection to be used"
"Type of convection to be used",
)
equation.Convection = flow.CONVECTION_TYPE
equation.Convection = "Computed"
@@ -84,14 +84,11 @@ class Flowwriter:
(
"Set to true for incompressible flow for more stable\n"
"discretization when Reynolds number increases"
)
),
)
if not hasattr(equation, "FlowModel"):
equation.addProperty(
"App::PropertyEnumeration",
"FlowModel",
"Flow",
"Flow model to be used"
"App::PropertyEnumeration", "FlowModel", "Flow", "Flow model to be used"
)
equation.FlowModel = flow.FLOW_MODEL
equation.FlowModel = "Full"
@@ -103,7 +100,7 @@ class Flowwriter:
(
"If true pressure Dirichlet boundary conditions can be used.\n"
"Also mass flux is available as a natural boundary condition."
)
),
)
if not hasattr(equation, "MagneticInduction"):
equation.addProperty(
@@ -113,14 +110,14 @@ class Flowwriter:
(
"Magnetic induction equation will be solved\n"
"along with the Navier-Stokes equations"
)
),
)
if not hasattr(equation, "Variable"):
equation.addProperty(
"App::PropertyString",
"Variable",
"Flow",
"Only for a 2D model change the '3' to '2'"
"Only for a 2D model change the '3' to '2'",
)
equation.Variable = "Flow Solution[Velocity:3 Pressure:1]"
@@ -132,10 +129,7 @@ class Flowwriter:
self.write.material(name, "Reference Temperature", refTemp)
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies())
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
self.write.material(name, "Name", m["Name"])
if "Density" in m:
@@ -161,9 +155,7 @@ class Flowwriter:
if "SpecificHeatRatio" in m:
self.write.material(name, "Specific Heat Ratio", float(m["SpecificHeatRatio"]))
if "CompressibilityModel" in m:
self.write.material(
name, "Compressibility Model",
m["CompressibilityModel"])
self.write.material(name, "Compressibility Model", m["CompressibilityModel"])
def _outputInitialPressure(self, obj, name):
# initial pressure only makes sense for fluid material
@@ -276,4 +268,5 @@ class Flowwriter:
if equation.MagneticInduction is True:
self.write.equation(b, "Magnetic Induction", equation.MagneticInduction)
## @}

View File

@@ -39,8 +39,7 @@ VARIABLES = ["Potential", "Temperature"]
def create(doc, name="Flux"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(linear.Proxy, equationbase.FluxProxy):
@@ -48,7 +47,7 @@ class Proxy(linear.Proxy, equationbase.FluxProxy):
Type = "Fem::EquationElmerFlux"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
@@ -57,43 +56,29 @@ class Proxy(linear.Proxy, equationbase.FluxProxy):
(
"Enforces continuity within the same material\n"
"in the 'Discontinuous Galerkin' discretization"
)
),
)
obj.addProperty("App::PropertyBool", "CalculateFlux", "Flux", "Computes flux vector")
obj.addProperty(
"App::PropertyBool",
"CalculateFlux",
"Flux",
"Computes flux vector"
)
obj.addProperty(
"App::PropertyBool",
"CalculateFluxAbs",
"Flux",
"Computes absolute of flux vector"
"App::PropertyBool", "CalculateFluxAbs", "Flux", "Computes absolute of flux vector"
)
obj.addProperty(
"App::PropertyBool",
"CalculateFluxMagnitude",
"Flux",
"Computes magnitude of flux vector field"
"Computes magnitude of flux vector field",
)
obj.addProperty(
"App::PropertyBool",
"CalculateGrad",
"Flux",
"Select calculation of gradient"
"App::PropertyBool", "CalculateGrad", "Flux", "Select calculation of gradient"
)
obj.addProperty(
"App::PropertyBool",
"CalculateGradAbs",
"Flux",
"Computes absolute of gradient field"
"App::PropertyBool", "CalculateGradAbs", "Flux", "Computes absolute of gradient field"
)
obj.addProperty(
"App::PropertyBool",
"CalculateGradMagnitude",
"Flux",
"Computes magnitude of gradient field"
"Computes magnitude of gradient field",
)
obj.addProperty(
"App::PropertyBool",
@@ -102,7 +87,7 @@ class Proxy(linear.Proxy, equationbase.FluxProxy):
(
"Enable if standard Galerkin approximation leads to\n"
"unphysical results when there are discontinuities"
)
),
)
obj.addProperty(
"App::PropertyBool",
@@ -111,19 +96,16 @@ class Proxy(linear.Proxy, equationbase.FluxProxy):
(
"If true, negative values of computed magnitude fields\n"
"are a posteriori set to zero."
)
),
)
obj.addProperty(
"App::PropertyEnumeration",
"FluxCoefficient",
"Flux",
"Proportionality coefficient\nto compute the flux"
"Proportionality coefficient\nto compute the flux",
)
obj.addProperty(
"App::PropertyEnumeration",
"FluxVariable",
"Flux",
"Variable name for flux calculation"
"App::PropertyEnumeration", "FluxVariable", "Flux", "Variable name for flux calculation"
)
obj.CalculateFlux = True
@@ -141,4 +123,5 @@ class Proxy(linear.Proxy, equationbase.FluxProxy):
class ViewProxy(linear.ViewProxy, equationbase.FluxViewProxy):
pass
## @}

View File

@@ -78,38 +78,35 @@ class Fluxwriter:
(
"Enforces continuity within the same material\n"
"in the 'Discontinuous Galerkin' discretization"
)
),
)
if hasattr(equation, "Bubbles"):
# Bubbles was removed because it is unused by Elmer for the flux solver
equation.removeProperty("Bubbles")
if not hasattr(equation, "CalculateFluxAbs"):
equation.addProperty(
"App::PropertyBool",
"CalculateFluxAbs",
"Flux",
"Computes absolute of flux vector"
"App::PropertyBool", "CalculateFluxAbs", "Flux", "Computes absolute of flux vector"
)
if not hasattr(equation, "CalculateFluxMagnitude"):
equation.addProperty(
"App::PropertyBool",
"CalculateFluxMagnitude",
"Flux",
"Computes magnitude of flux vector field"
"Computes magnitude of flux vector field",
)
if not hasattr(equation, "CalculateGradAbs"):
equation.addProperty(
"App::PropertyBool",
"CalculateGradAbs",
"Flux",
"Computes absolute of gradient field"
"Computes absolute of gradient field",
)
if not hasattr(equation, "CalculateGradMagnitude"):
equation.addProperty(
"App::PropertyBool",
"CalculateGradMagnitude",
"Flux",
"Computes magnitude of gradient field"
"Computes magnitude of gradient field",
)
if not hasattr(equation, "DiscontinuousGalerkin"):
equation.addProperty(
@@ -119,7 +116,7 @@ class Fluxwriter:
(
"Enable if standard Galerkin approximation leads to\n"
"unphysical results when there are discontinuities"
)
),
)
if not hasattr(equation, "EnforcePositiveMagnitude"):
equation.addProperty(
@@ -129,7 +126,7 @@ class Fluxwriter:
(
"If true, negative values of computed magnitude fields\n"
"are a posteriori set to zero."
)
),
)
tempFluxCoefficient = ""
if hasattr(equation, "FluxCoefficient"):
@@ -143,7 +140,7 @@ class Fluxwriter:
"App::PropertyEnumeration",
"FluxCoefficient",
"Flux",
"Name of proportionality coefficient\nto compute the flux"
"Name of proportionality coefficient\nto compute the flux",
)
equation.FluxCoefficient = flux.COEFFICIENTS
if tempFluxCoefficient:
@@ -161,9 +158,10 @@ class Fluxwriter:
"App::PropertyEnumeration",
"FluxVariable",
"Flux",
"Variable name for flux calculation"
"Variable name for flux calculation",
)
equation.FluxVariable = flux.VARIABLES
equation.FluxVariable = tempFluxVariable
## @}

View File

@@ -38,8 +38,7 @@ PHASE_CHANGE_MODEL = ["None", "Spatial 1", "Spatial 2", "Temporal"]
def create(doc, name="Heat"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(nonlinear.Proxy, equationbase.HeatProxy):
@@ -47,27 +46,16 @@ class Proxy(nonlinear.Proxy, equationbase.HeatProxy):
Type = "Fem::EquationElmerHeat"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
# according to the Elmer models manual Bubbles is by default True
# and Stabilize is False (Stabilize is added in linear.py)
obj.addProperty("App::PropertyBool", "Bubbles", "Heat", "")
obj.addProperty(
"App::PropertyBool",
"Bubbles",
"Heat",
""
"App::PropertyEnumeration", "Convection", "Equation", "Type of convection to be used"
)
obj.addProperty(
"App::PropertyEnumeration",
"Convection",
"Equation",
"Type of convection to be used"
)
obj.addProperty(
"App::PropertyEnumeration",
"PhaseChangeModel",
"Equation",
"Model for phase change"
"App::PropertyEnumeration", "PhaseChangeModel", "Equation", "Model for phase change"
)
obj.Bubbles = True
@@ -82,4 +70,5 @@ class Proxy(nonlinear.Proxy, equationbase.HeatProxy):
class ViewProxy(nonlinear.ViewProxy, equationbase.HeatViewProxy):
pass
## @}

View File

@@ -63,7 +63,7 @@ class Heatwriter:
def handleHeatConstants(self):
self.write.constant(
"Stefan Boltzmann",
self.write.convert(self.write.constsdef["StefanBoltzmann"], "M/(O^4*T^3)")
self.write.convert(self.write.constsdef["StefanBoltzmann"], "M/(O^4*T^3)"),
)
def handleHeatEquation(self, bodies, equation):
@@ -80,16 +80,13 @@ class Heatwriter:
"App::PropertyEnumeration",
"Convection",
"Equation",
"Type of convection to be used"
"Type of convection to be used",
)
equation.Convection = heat.CONVECTION_TYPE
equation.Convection = "None"
if not hasattr(equation, "PhaseChangeModel"):
equation.addProperty(
"App::PropertyEnumeration",
"PhaseChangeModel",
"Equation",
"Model for phase change"
"App::PropertyEnumeration", "PhaseChangeModel", "Equation", "Model for phase change"
)
equation.PhaseChangeModel = heat.PHASE_CHANGE_MODEL
equation.PhaseChangeModel = "None"
@@ -99,12 +96,10 @@ class Heatwriter:
for obj in self.write.getMember("Fem::ConstraintTemperature"):
i = i + 1
femobjects = membertools.get_several_member(
self.write.analysis,
"Fem::ConstraintTemperature"
self.write.analysis, "Fem::ConstraintTemperature"
)
femobjects[i]["Nodes"] = meshtools.get_femnodes_by_femobj_with_references(
self.write.getSingleMember("Fem::FemMeshObject").FemMesh,
femobjects[i]
self.write.getSingleMember("Fem::FemMeshObject").FemMesh, femobjects[i]
)
NumberOfNodes = len(femobjects[i]["Nodes"])
if obj.References:
@@ -150,7 +145,9 @@ class Heatwriter:
ref_sub_obj = ref[1][0]
density = None
for mat in self.write.getMember("App::MaterialObject"):
mat_ref = [*itertools.chain(*[itertools.product([i[0]],i[1]) for i in mat.References])]
mat_ref = [
*itertools.chain(*[itertools.product([i[0]], i[1]) for i in mat.References])
]
if (ref_feat, ref_sub_obj) in mat_ref:
density = FreeCAD.Units.Quantity(mat.Material["Density"])
break
@@ -162,8 +159,12 @@ class Heatwriter:
density = FreeCAD.Units.Quantity(mat.Material["Density"])
break
volume = ref_feat.getSubObject(ref_sub_obj).Volume
heatSource = (obj.TotalPower / (density*FreeCAD.Units.Quantity(volume, "mm^3"))).getValueAs("W/kg").Value
heatSource = (
(obj.TotalPower / (density * FreeCAD.Units.Quantity(volume, "mm^3")))
.getValueAs("W/kg")
.Value
)
if heatSource == 0.0:
# a zero heat would break Elmer (division by zero)
raise general_writer.WriteError("The body heat source must not be zero!")
@@ -197,32 +198,30 @@ class Heatwriter:
self.write.material(name, "Reference Temperature", refTemp)
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies())
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
if "Density" not in m:
raise general_writer.WriteError(
"Used material does not specify the necessary 'Density'."
)
self.write.material(name, "Name", m["Name"])
self.write.material(
name, "Density",
self.write.getDensity(m))
self.write.material(name, "Density", self.write.getDensity(m))
if "ThermalConductivity" not in m:
raise general_writer.WriteError(
"Used material does not specify the necessary 'Thermal Conductivity'."
)
self.write.material(
name, "Heat Conductivity",
self.write.convert(m["ThermalConductivity"], "M*L/(T^3*O)"))
name,
"Heat Conductivity",
self.write.convert(m["ThermalConductivity"], "M*L/(T^3*O)"),
)
if "SpecificHeat" not in m:
raise general_writer.WriteError(
"Used material does not specify the necessary 'Specific Heat'."
)
self.write.material(
name, "Heat Capacity",
self.write.convert(m["SpecificHeat"], "L^2/(T^2*O)"))
name, "Heat Capacity", self.write.convert(m["SpecificHeat"], "L^2/(T^2*O)")
)
## @}

View File

@@ -39,75 +39,32 @@ from . import equation
LINEAR_SOLVER = ["Direct", "Iterative"]
LINEAR_DIRECT = ["Banded", "MUMPS", "Umfpack"]
LINEAR_ITERATIVE = [
"BiCGStab",
"BiCGStabl",
"CG",
"GCR",
"CGS",
"GMRES",
"Idrs",
"TFQMR"
]
LINEAR_PRECONDITIONING = [
"None",
"Diagonal",
"ILU0",
"ILU1",
"ILU2",
"ILU3",
"ILU4",
"ILUT"
]
LINEAR_ITERATIVE = ["BiCGStab", "BiCGStabl", "CG", "GCR", "CGS", "GMRES", "Idrs", "TFQMR"]
LINEAR_PRECONDITIONING = ["None", "Diagonal", "ILU0", "ILU1", "ILU2", "ILU3", "ILU4", "ILUT"]
class Proxy(equation.Proxy):
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyIntegerConstraint",
"BiCGstablDegree",
"Linear System",
"Polynom degree for iterative method 'BiCGstabl'"
"Polynom degree for iterative method 'BiCGstabl'",
)
obj.addProperty(
"App::PropertyIntegerConstraint",
"IdrsParameter",
"Linear System",
"Parameter for iterative method 'Idrs'"
)
obj.addProperty(
"App::PropertyEnumeration",
"LinearDirectMethod",
"Linear System",
""
)
obj.addProperty(
"App::PropertyIntegerConstraint",
"LinearIterations",
"Linear System",
""
)
obj.addProperty(
"App::PropertyEnumeration",
"LinearIterativeMethod",
"Linear System",
""
)
obj.addProperty(
"App::PropertyEnumeration",
"LinearPreconditioning",
"Linear System",
""
)
obj.addProperty(
"App::PropertyEnumeration",
"LinearSolverType",
"Linear System",
""
"Parameter for iterative method 'Idrs'",
)
obj.addProperty("App::PropertyEnumeration", "LinearDirectMethod", "Linear System", "")
obj.addProperty("App::PropertyIntegerConstraint", "LinearIterations", "Linear System", "")
obj.addProperty("App::PropertyEnumeration", "LinearIterativeMethod", "Linear System", "")
obj.addProperty("App::PropertyEnumeration", "LinearPreconditioning", "Linear System", "")
obj.addProperty("App::PropertyEnumeration", "LinearSolverType", "Linear System", "")
obj.addProperty(
"App::PropertyBool",
"LinearSystemSolverDisabled",
@@ -116,26 +73,16 @@ class Proxy(equation.Proxy):
"Disable the linear system.\n"
"Only use for special cases\n"
"and consult the Elmer docs."
)
),
)
obj.addProperty(
"App::PropertyFloat",
"LinearTolerance",
"Linear System",
"Linear preconditioning method"
)
obj.addProperty(
"App::PropertyBool",
"Stabilize",
"Base",
""
)
obj.addProperty(
"App::PropertyFloat",
"SteadyStateTolerance",
"Steady State",
""
"Linear preconditioning method",
)
obj.addProperty("App::PropertyBool", "Stabilize", "Base", "")
obj.addProperty("App::PropertyFloat", "SteadyStateTolerance", "Steady State", "")
obj.BiCGstablDegree = (2, 2, 10, 1)
obj.IdrsParameter = (2, 1, 10, 1)
@@ -160,4 +107,5 @@ class Proxy(equation.Proxy):
class ViewProxy(equation.ViewProxy):
pass
## @}

View File

@@ -34,8 +34,7 @@ from ... import equationbase
def create(doc, name="Magnetodynamic"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(nonlinear.Proxy, equationbase.MagnetodynamicProxy):
@@ -43,19 +42,19 @@ class Proxy(nonlinear.Proxy, equationbase.MagnetodynamicProxy):
Type = "Fem::EquationElmerMagnetodynamic"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
"IsHarmonic",
"Magnetodynamic",
"If the magnetic source is harmonically driven"
"If the magnetic source is harmonically driven",
)
obj.addProperty(
"App::PropertyFrequency",
"AngularFrequency",
"Magnetodynamic",
"Frequency of the driving current"
"Frequency of the driving current",
)
obj.addProperty(
"App::PropertyBool",
@@ -63,129 +62,68 @@ class Proxy(nonlinear.Proxy, equationbase.MagnetodynamicProxy):
"Magnetodynamic",
"Must be True if basis functions for edge element interpolation\n"
"are selected to be members of optimal edge element family\n"
"or if second-order approximation is used."
"or if second-order approximation is used.",
)
obj.addProperty(
"App::PropertyBool",
"QuadraticApproximation",
"Magnetodynamic",
"Enables second-order approximation of driving current"
"Enables second-order approximation of driving current",
)
obj.addProperty(
"App::PropertyBool",
"StaticConductivity",
"Magnetodynamic",
"See Elmer models manual for info"
"See Elmer models manual for info",
)
obj.addProperty(
"App::PropertyBool",
"FixInputCurrentDensity",
"Magnetodynamic",
"Ensures divergence-freeness of current density"
"Ensures divergence-freeness of current density",
)
obj.addProperty(
"App::PropertyBool",
"AutomatedSourceProjectionBCs",
"Magnetodynamic",
"See Elmer models manual for info"
"See Elmer models manual for info",
)
obj.addProperty(
"App::PropertyBool",
"UseLagrangeGauge",
"Magnetodynamic",
"See Elmer models manual for info"
"See Elmer models manual for info",
)
obj.addProperty(
"App::PropertyFloat",
"LagrangeGaugePenalizationCoefficient",
"Magnetodynamic",
"See Elmer models manual for info"
"See Elmer models manual for info",
)
obj.addProperty(
"App::PropertyBool",
"UseTreeGauge",
"Magnetodynamic",
"See Elmer models manual for info\n"
"Will be ignored if 'UsePiolaTransform' is True"
)
obj.addProperty(
"App::PropertyBool",
"LinearSystemRefactorize",
"Linear System",
""
"See Elmer models manual for info\nWill be ignored if 'UsePiolaTransform' is True",
)
obj.addProperty("App::PropertyBool", "LinearSystemRefactorize", "Linear System", "")
obj.IsHarmonic = False
obj.AngularFrequency = 0
obj.Priority = 10
# the post processor options
obj.addProperty(
"App::PropertyBool",
"CalculateCurrentDensity",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElectricField",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElementalFields",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateHarmonicLoss",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateJouleHeating",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateMagneticFieldStrength",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateMaxwellStress",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalFields",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalForces",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalHeating",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"DiscontinuousBodies",
"Results",
""
)
obj.addProperty("App::PropertyBool", "CalculateCurrentDensity", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateElectricField", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateElementalFields", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateHarmonicLoss", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateJouleHeating", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateMagneticFieldStrength", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateMaxwellStress", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateNodalFields", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateNodalForces", "Results", "")
obj.addProperty("App::PropertyBool", "CalculateNodalHeating", "Results", "")
obj.addProperty("App::PropertyBool", "DiscontinuousBodies", "Results", "")
obj.CalculateCurrentDensity = False
obj.CalculateElectricField = False
# FIXME: at the moment FreeCAD's post processor cannot display elementary field
@@ -204,4 +142,5 @@ class Proxy(nonlinear.Proxy, equationbase.MagnetodynamicProxy):
class ViewProxy(nonlinear.ViewProxy, equationbase.MagnetodynamicViewProxy):
pass
## @}

View File

@@ -34,8 +34,7 @@ from ... import equationbase
def create(doc, name="Magnetodynamic2D"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(nonlinear.Proxy, equationbase.Magnetodynamic2DProxy):
@@ -43,85 +42,37 @@ class Proxy(nonlinear.Proxy, equationbase.Magnetodynamic2DProxy):
Type = "Fem::EquationElmerMagnetodynamic2D"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyBool",
"IsHarmonic",
"Magnetodynamic2D",
"If the magnetic source is harmonically driven"
"If the magnetic source is harmonically driven",
)
obj.addProperty(
"App::PropertyFrequency",
"AngularFrequency",
"Magnetodynamic2D",
"Frequency of the driving current"
"Frequency of the driving current",
)
obj.IsHarmonic = False
obj.AngularFrequency = 0
obj.Priority = 10
# the post processor options
obj.addProperty("App::PropertyBool", "CalculateCurrentDensity", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateElectricField", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateElementalFields", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateHarmonicLoss", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateJouleHeating", "Magnetodynamic2D", "")
obj.addProperty(
"App::PropertyBool",
"CalculateCurrentDensity",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElectricField",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElementalFields",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateHarmonicLoss",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateJouleHeating",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateMagneticFieldStrength",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateMaxwellStress",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalFields",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalForces",
"Magnetodynamic2D",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalHeating",
"Magnetodynamic2D",
""
"App::PropertyBool", "CalculateMagneticFieldStrength", "Magnetodynamic2D", ""
)
obj.addProperty("App::PropertyBool", "CalculateMaxwellStress", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateNodalFields", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateNodalForces", "Magnetodynamic2D", "")
obj.addProperty("App::PropertyBool", "CalculateNodalHeating", "Magnetodynamic2D", "")
obj.CalculateCurrentDensity = False
obj.CalculateElectricField = False
# FIXME: at the moment FreeCAD's post processor cannot display elementary field
@@ -139,4 +90,5 @@ class Proxy(nonlinear.Proxy, equationbase.Magnetodynamic2DProxy):
class ViewProxy(nonlinear.ViewProxy, equationbase.Magnetodynamic2DViewProxy):
pass
## @}

View File

@@ -92,15 +92,13 @@ class MgDyn2Dwriter:
def handleMagnetodynamic2DConstants(self):
permeability = self.write.convert(
self.write.constsdef["PermeabilityOfVacuum"],
"M*L/(T^2*I^2)"
self.write.constsdef["PermeabilityOfVacuum"], "M*L/(T^2*I^2)"
)
# we round in the following to get rid of numerical artifacts
self.write.constant("Permeability Of Vacuum", round(permeability, 20))
permittivity = self.write.convert(
self.write.constsdef["PermittivityOfVacuum"],
"T^4*I^2/(L^3*M)"
self.write.constsdef["PermittivityOfVacuum"], "T^4*I^2/(L^3*M)"
)
self.write.constant("Permittivity Of Vacuum", round(permittivity, 20))
@@ -109,22 +107,19 @@ class MgDyn2Dwriter:
for name in bodies:
if self.write.getBodyMaterial(name) is None:
raise general_writer.WriteError(
"The body {} is not referenced in any material.\n\n".format(name)
f"The body {name} is not referenced in any material.\n\n"
)
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies())
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
if "ElectricalConductivity" not in m:
Console.PrintMessage("m: {}\n".format(m))
Console.PrintMessage(f"m: {m}\n")
raise general_writer.WriteError(
"The electrical conductivity must be specified for all materials.\n\n"
)
if "RelativePermeability" not in m:
Console.PrintMessage("m: {}\n".format(m))
Console.PrintMessage(f"m: {m}\n")
raise general_writer.WriteError(
"The relative permeability must be specified for all materials.\n\n"
)
@@ -132,15 +127,11 @@ class MgDyn2Dwriter:
conductivity = self.write.convert(m["ElectricalConductivity"], "T^3*I^2/(L^3*M)")
conductivity = round(conductivity, 10) # to get rid of numerical artifacts
self.write.material(name, "Electric Conductivity", conductivity)
self.write.material(
name, "Relative Permeability",
float(m["RelativePermeability"])
)
self.write.material(name, "Relative Permeability", float(m["RelativePermeability"]))
# permittivity might be necessary for the post processor
if "RelativePermittivity" in m:
self.write.material(
name, "Relative Permittivity",
float(m["RelativePermittivity"])
name, "Relative Permittivity", float(m["RelativePermittivity"])
)
def _outputMagnetodynamic2DBodyForce(self, obj, name, equation):
@@ -229,12 +220,11 @@ class MgDyn2Dwriter:
def handleMagnetodynamic2DEquation(self, bodies, equation):
for b in bodies:
if equation.IsHarmonic and (equation.AngularFrequency == 0):
raise general_writer.WriteError(
"The angular frequency must not be zero.\n\n"
)
raise general_writer.WriteError("The angular frequency must not be zero.\n\n")
self.write.equation(b, "Name", equation.Name)
if equation.IsHarmonic:
frequency = float(Units.Quantity(equation.AngularFrequency).Value)
self.write.equation(b, "Angular Frequency", round(frequency, 6))
## @}

View File

@@ -73,8 +73,9 @@ class MgDynwriter:
if equation.UseLagrangeGauge is True:
s["Use Lagrange Gauge"] = True
if equation.LagrangeGaugePenalizationCoefficient != 0.0:
s["Lagrange Gauge Penalization Coefficient"] = \
s["Lagrange Gauge Penalization Coefficient"] = (
equation.LagrangeGaugePenalizationCoefficient
)
if equation.UseTreeGauge is True:
s["Use Tree Gauge"] = True
return s
@@ -117,15 +118,13 @@ class MgDynwriter:
def handleMagnetodynamicConstants(self):
permeability = self.write.convert(
self.write.constsdef["PermeabilityOfVacuum"],
"M*L/(T^2*I^2)"
self.write.constsdef["PermeabilityOfVacuum"], "M*L/(T^2*I^2)"
)
# we round in the following to get rid of numerical artifacts
self.write.constant("Permeability Of Vacuum", round(permeability, 20))
permittivity = self.write.convert(
self.write.constsdef["PermittivityOfVacuum"],
"T^4*I^2/(L^3*M)"
self.write.constsdef["PermittivityOfVacuum"], "T^4*I^2/(L^3*M)"
)
self.write.constant("Permittivity Of Vacuum", round(permittivity, 20))
@@ -134,22 +133,19 @@ class MgDynwriter:
for name in bodies:
if self.write.getBodyMaterial(name) is None:
raise general_writer.WriteError(
"The body {} is not referenced in any material.\n\n".format(name)
f"The body {name} is not referenced in any material.\n\n"
)
for obj in self.write.getMember("App::MaterialObject"):
m = obj.Material
refs = (
obj.References[0][1]
if obj.References
else self.write.getAllBodies())
refs = obj.References[0][1] if obj.References else self.write.getAllBodies()
for name in (n for n in refs if n in bodies):
if "ElectricalConductivity" not in m:
Console.PrintMessage("m: {}\n".format(m))
Console.PrintMessage(f"m: {m}\n")
raise general_writer.WriteError(
"The electrical conductivity must be specified for all materials.\n\n"
)
if "RelativePermeability" not in m:
Console.PrintMessage("m: {}\n".format(m))
Console.PrintMessage(f"m: {m}\n")
raise general_writer.WriteError(
"The relative permeability must be specified for all materials.\n\n"
)
@@ -157,15 +153,11 @@ class MgDynwriter:
conductivity = self.write.convert(m["ElectricalConductivity"], "T^3*I^2/(L^3*M)")
conductivity = round(conductivity, 10) # to get rid of numerical artifacts
self.write.material(name, "Electric Conductivity", conductivity)
self.write.material(
name, "Relative Permeability",
float(m["RelativePermeability"])
)
self.write.material(name, "Relative Permeability", float(m["RelativePermeability"]))
# permittivity might be necessary for the post processor
if "RelativePermittivity" in m:
self.write.material(
name, "Relative Permittivity",
float(m["RelativePermittivity"])
name, "Relative Permittivity", float(m["RelativePermittivity"])
)
def _outputMagnetodynamicBodyForce(self, obj, name, equation):
@@ -235,7 +227,7 @@ class MgDynwriter:
for obj in currentDensities:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
firstName = firstName.rstrip("0123456789")
if firstName == "Solid":
for name in obj.References[0][1]:
self._outputMagnetodynamicBodyForce(obj, name, equation)
@@ -278,7 +270,7 @@ class MgDynwriter:
for obj in potentials:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
firstName = firstName.rstrip("0123456789")
if firstName == "Solid":
for name in obj.References[0][1]:
# output only if potentiual is enabled and needed
@@ -346,7 +338,7 @@ class MgDynwriter:
for obj in currentDensities:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
firstName = firstName.rstrip("0123456789")
if firstName == "Face":
for name in obj.References[0][1]:
self._outputMagnetodynamicBndConditions(obj, name, equation)
@@ -363,7 +355,7 @@ class MgDynwriter:
for obj in potentials:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
firstName = firstName.rstrip("0123456789")
if firstName == "Face":
for name in obj.References[0][1]:
# output the FreeCAD label as comment
@@ -373,4 +365,5 @@ class MgDynwriter:
self._outputMagnetodynamicBndConditions(obj, name, equation)
self.write.handled(obj)
## @}

View File

@@ -40,32 +40,24 @@ from . import linear
class Proxy(linear.Proxy):
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyIntegerConstraint",
"NonlinearIterations",
"Nonlinear System",
"Maximum number of iterations"
"Maximum number of iterations",
)
obj.addProperty(
"App::PropertyIntegerConstraint",
"NonlinearNewtonAfterIterations",
"Nonlinear System",
""
"",
)
obj.addProperty(
"App::PropertyFloat",
"NonlinearNewtonAfterTolerance",
"Nonlinear System",
""
)
obj.addProperty(
"App::PropertyFloat",
"NonlinearTolerance",
"Nonlinear System",
""
"App::PropertyFloat", "NonlinearNewtonAfterTolerance", "Nonlinear System", ""
)
obj.addProperty("App::PropertyFloat", "NonlinearTolerance", "Nonlinear System", "")
obj.addProperty(
"App::PropertyFloatConstraint",
"RelaxationFactor",
@@ -73,7 +65,7 @@ class Proxy(linear.Proxy):
(
"Value below 1.0 might be necessary to achieve convergence\n"
"Typical values are in the range [0.3, 1.0]"
)
),
)
obj.NonlinearIterations = (20, 1, int(1e6), 10)
@@ -90,4 +82,5 @@ class Proxy(linear.Proxy):
class ViewProxy(linear.ViewProxy):
pass
## @}

View File

@@ -78,10 +78,10 @@ _TYPE_STRING = "String"
_TYPE_FILE = "File"
_TYPE_VARIABLE = "Variable"
WARN = "\"Warn\""
IGNORE = "\"Ignore\""
ABORT = "\"Abort\""
SILENT = "\"Silent\""
WARN = '"Warn"'
IGNORE = '"Ignore"'
ABORT = '"Abort"'
SILENT = '"Silent"'
def createSection(name):
@@ -104,7 +104,7 @@ def isValid(section):
return section.name in _VALID_SECTIONS
class Builder(object):
class Builder:
_ACTIVE_SOLVERS = "Active Solvers"
@@ -191,7 +191,7 @@ class Builder(object):
return iter(allSections)
class Sif(object):
class Sif:
_CHECK_KEYWORDS = "Check Keywords"
_HEADER = "Header"
@@ -238,7 +238,7 @@ class Sif(object):
stream.write('"%s"' % value)
class Section(object):
class Section:
def __init__(self, name):
self.name = name
@@ -274,7 +274,7 @@ class FileAttr(str):
pass
class _Writer(object):
class _Writer:
def __init__(self, idManager, sections, stream):
self._idMgr = idManager
@@ -282,8 +282,7 @@ class _Writer(object):
self._stream = stream
def write(self):
sortedSections = sorted(
self._sections, key=lambda s: s.priority, reverse=True)
sortedSections = sorted(self._sections, key=lambda s: s.priority, reverse=True)
for s in sortedSections:
self._writeSection(s)
self._stream.write(_NEWLINE)
@@ -332,10 +331,7 @@ class _Writer(object):
return next(it)
def _isCollection(self, data):
return (
not isinstance(data, str)
and isinstance(data, collections.abc.Iterable)
)
return not isinstance(data, str) and isinstance(data, collections.abc.Iterable)
def _checkScalar(self, dataType):
if issubclass(dataType, int):
@@ -359,7 +355,7 @@ class _Writer(object):
self._stream.write(_WHITESPACE)
# check if we have a variable string
if attrType is _TYPE_STRING:
if data.startswith('Variable'):
if data.startswith("Variable"):
attrType = _TYPE_VARIABLE
if attrType is not _TYPE_VARIABLE:
self._stream.write(attrType)
@@ -367,7 +363,7 @@ class _Writer(object):
output = self._preprocess(data, type(data))
# in case of a variable the output must be without the quatoation marks
if attrType is _TYPE_VARIABLE:
output = output.lstrip('\"')
output = output.lstrip('"')
# we cannot use rstrip because there are two subsequent " at the end
output = output[:-1]
self._stream.write(output)
@@ -382,7 +378,7 @@ class _Writer(object):
self._stream.write(_WHITESPACE)
# check if we have a variable string
if attrType is _TYPE_STRING:
if data.startswith('Variable'):
if data.startswith("Variable"):
attrType = _TYPE_VARIABLE
if attrType is not _TYPE_VARIABLE:
self._stream.write(attrType)
@@ -391,7 +387,7 @@ class _Writer(object):
output = self._preprocess(val, type(val))
# in case of a variable the output must be without the quatoation marks
if attrType is _TYPE_VARIABLE:
output = output.lstrip('\"')
output = output.lstrip('"')
# we cannot use rstrip because there are two subsequent " at the end
output = output[:-1]
self._stream.write(output)
@@ -442,7 +438,7 @@ class _Writer(object):
return self._getSifDataType(dataType)
class _IdManager(object):
class _IdManager:
def __init__(self, firstId=1):
self._pool = dict()
@@ -460,4 +456,5 @@ class _IdManager(object):
self.setId(section)
return self._ids[section]
## @}

View File

@@ -50,16 +50,22 @@ from femtools import femutils
if FreeCAD.GuiUp:
import FemGui
COORDINATE_SYSTEM = ["Cartesian", "Cartesian 1D", "Cartesian 2D", "Cartesian 3D",
"Polar 2D", "Polar 3D",
"Cylindric", "Cylindric Symmetric",
"Axi Symmetric"]
COORDINATE_SYSTEM = [
"Cartesian",
"Cartesian 1D",
"Cartesian 2D",
"Cartesian 3D",
"Polar 2D",
"Polar 3D",
"Cylindric",
"Cylindric Symmetric",
"Axi Symmetric",
]
SIMULATION_TYPE = ["Scanning", "Steady State", "Transient"]
def create(doc, name="ElmerSolver"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(solverbase.Proxy):
@@ -80,14 +86,9 @@ class Proxy(solverbase.Proxy):
}
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.addProperty(
"App::PropertyEnumeration",
"CoordinateSystem",
"Coordinate System",
""
)
obj.addProperty("App::PropertyEnumeration", "CoordinateSystem", "Coordinate System", "")
obj.CoordinateSystem = COORDINATE_SYSTEM
obj.CoordinateSystem = "Cartesian"
@@ -95,7 +96,7 @@ class Proxy(solverbase.Proxy):
"App::PropertyIntegerConstraint",
"BDFOrder",
"Timestepping",
"Order of time stepping method 'BDF'"
"Order of time stepping method 'BDF'",
)
# according to the Elmer manual recommended is order 2
# possible ranage is 1 - 5
@@ -105,7 +106,7 @@ class Proxy(solverbase.Proxy):
"App::PropertyIntegerList",
"OutputIntervals",
"Timestepping",
"After how many time steps a result file is output"
"After how many time steps a result file is output",
)
obj.OutputIntervals = [1]
@@ -113,10 +114,7 @@ class Proxy(solverbase.Proxy):
"App::PropertyIntegerList",
"TimestepIntervals",
"Timestepping",
(
"List of times if 'Simulation Type'\n"
"is either 'Scanning' or 'Transient'"
)
("List of times if 'Simulation Type'\nis either 'Scanning' or 'Transient'"),
)
obj.addProperty(
"App::PropertyFloatList",
@@ -125,19 +123,14 @@ class Proxy(solverbase.Proxy):
(
"List of time steps sizes if 'Simulation Type'\n"
"is either 'Scanning' or 'Transient'"
)
),
)
# there is no universal default, it all depends on the analysis, however
# we have to set something and set 10 seconds in steps of 0.1s
obj.TimestepIntervals = [100]
obj.TimestepSizes = [0.1]
obj.addProperty(
"App::PropertyEnumeration",
"SimulationType",
"Type",
""
)
obj.addProperty("App::PropertyEnumeration", "SimulationType", "Type", "")
obj.SimulationType = SIMULATION_TYPE
obj.SimulationType = "Steady State"
@@ -145,7 +138,7 @@ class Proxy(solverbase.Proxy):
"App::PropertyInteger",
"SteadyStateMaxIterations",
"Type",
"Maximal steady state iterations"
"Maximal steady state iterations",
)
obj.SteadyStateMaxIterations = 1
@@ -153,42 +146,26 @@ class Proxy(solverbase.Proxy):
"App::PropertyInteger",
"SteadyStateMinIterations",
"Type",
"Minimal steady state iterations"
"Minimal steady state iterations",
)
obj.SteadyStateMinIterations = 0
obj.addProperty(
"App::PropertyLink",
"ElmerResult",
"Base",
"",
4 | 8
)
obj.addProperty("App::PropertyLink", "ElmerResult", "Base", "", 4 | 8)
obj.addProperty(
"App::PropertyLinkList",
"ElmerTimeResults",
"Base",
"",
4 | 8
)
obj.addProperty("App::PropertyLinkList", "ElmerTimeResults", "Base", "", 4 | 8)
obj.addProperty(
"App::PropertyLink",
"ElmerOutput",
"Base",
"",
4 | 8
)
obj.addProperty("App::PropertyLink", "ElmerOutput", "Base", "", 4 | 8)
def createMachine(self, obj, directory, testmode=False):
return run.Machine(
solver=obj, directory=directory,
solver=obj,
directory=directory,
check=tasks.Check(),
prepare=tasks.Prepare(),
solve=tasks.Solve(),
results=tasks.Results(),
testmode=testmode)
testmode=testmode,
)
def createEquation(self, doc, eqId):
return self._EQUATIONS[eqId].create(doc)
@@ -201,7 +178,7 @@ class Proxy(solverbase.Proxy):
def edit(self, directory):
pattern = os.path.join(directory, "case.sif")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
@@ -212,4 +189,5 @@ class ViewProxy(solverbase.ViewProxy):
def getIcon(self):
return ":/icons/FEM_SolverElmer.svg"
## @}

View File

@@ -47,7 +47,7 @@ class Check(run.Check):
def run(self):
self.pushStatus("Checking analysis...\n")
if (self.check_mesh_exists()):
if self.check_mesh_exists():
self.checkMeshType()
self.check_material_exists()
self.checkEquations()
@@ -55,9 +55,7 @@ class Check(run.Check):
def checkMeshType(self):
mesh = membertools.get_single_member(self.analysis, "Fem::FemMeshObject")
if not femutils.is_of_type(mesh, "Fem::FemMeshGmsh"):
self.report.error(
"Unsupported type of mesh. "
"Mesh must be created with gmsh.")
self.report.error("Unsupported type of mesh. Mesh must be created with gmsh.")
self.fail()
return False
return True
@@ -65,9 +63,7 @@ class Check(run.Check):
def checkEquations(self):
equations = self.solver.Group
if not equations:
self.report.error(
"Solver has no equations. "
"Add at least one equation.")
self.report.error("Solver has no equations. Add at least one equation.")
self.fail()
@@ -77,14 +73,13 @@ class Prepare(run.Prepare):
# TODO print working dir to report console
self.pushStatus("Preparing input files...\n")
num_cores = settings.get_cores("ElmerGrid")
self.pushStatus("Number of CPU cores to be used for the solver run: {}\n"
.format(num_cores))
self.pushStatus(f"Number of CPU cores to be used for the solver run: {num_cores}\n")
if self.testmode:
# test mode: neither gmsh, nor elmergrid nor elmersolver binaries needed
FreeCAD.Console.PrintMessage("Machine testmode: {}\n".format(self.testmode))
FreeCAD.Console.PrintMessage(f"Machine testmode: {self.testmode}\n")
w = writer.Writer(self.solver, self.directory, True)
else:
FreeCAD.Console.PrintLog("Machine testmode: {}\n".format(self.testmode))
FreeCAD.Console.PrintLog(f"Machine testmode: {self.testmode}\n")
w = writer.Writer(self.solver, self.directory)
try:
w.write_solver_input()
@@ -93,7 +88,7 @@ class Prepare(run.Prepare):
except writer.WriteError as e:
self.report.error(str(e))
self.fail()
except IOError:
except OSError:
self.report.error("Can't access working directory.")
self.fail()
@@ -124,11 +119,10 @@ class Solve(run.Solve):
solvpath = os.path.split(binary)[0]
if os.path.isdir(solvpath):
os.environ["ELMER_HOME"] = solvpath
os.environ["LD_LIBRARY_PATH"] = "$LD_LIBRARY_PATH:{}/modules".format(solvpath)
os.environ["LD_LIBRARY_PATH"] = f"$LD_LIBRARY_PATH:{solvpath}/modules"
# different call depending if with multithreading or not
num_cores = settings.get_cores("ElmerSolver")
self.pushStatus("Number of CPU cores to be used for the solver run: {}\n"
.format(num_cores))
self.pushStatus(f"Number of CPU cores to be used for the solver run: {num_cores}\n")
args = []
if num_cores > 1:
if system() != "Windows":
@@ -143,14 +137,11 @@ class Solve(run.Solve):
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
startupinfo=femutils.startProgramInfo("hide")
startupinfo=femutils.startProgramInfo("hide"),
)
else:
self._process = subprocess.Popen(
args,
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
args, cwd=self.directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
self.signalAbort.add(self._process.terminate)
output = self._observeSolver(self._process)
@@ -172,7 +163,8 @@ class Solve(run.Solve):
def _createOutput(self):
self.solver.ElmerOutput = self.analysis.Document.addObject(
"App::TextDocument", self.solver.Name + "Output")
"App::TextDocument", self.solver.Name + "Output"
)
self.solver.ElmerOutput.Label = self.solver.Label + "Output"
# App::TextDocument has no Attribute ReadOnly
# TODO check if the attribute has been removed from App::TextDocument
@@ -194,11 +186,7 @@ class Solve(run.Solve):
FrequencyList = []
for line in OutputList:
LineList = line.split(" ")
if (
len(LineList) > 1
and LineList[0] == "EigenSolve:"
and LineList[1] == "Computed"
):
if len(LineList) > 1 and LineList[0] == "EigenSolve:" and LineList[1] == "Computed":
# we found a result and take now the next LineList[2] lines
modeCount = int(LineList[2])
modeNumber = modeCount
@@ -219,9 +207,7 @@ class Solve(run.Solve):
# now we can perform the calculation
eigenFreq = cmath.sqrt(eigenFreq) / (2 * cmath.pi)
# create an output line
FrequencyList.append(
"Mode {}: {} Hz".format(modeNumber - modeCount + 1, eigenFreq.real)
)
FrequencyList.append(f"Mode {modeNumber - modeCount + 1}: {eigenFreq.real} Hz")
modeCount = modeCount - 1
if modeNumber > 0:
# push the results and append to output
@@ -274,7 +260,8 @@ class Results(run.Results):
def _createResults(self):
self.solver.ElmerResult = self.analysis.Document.addObject(
"Fem::FemPostPipeline", self.solver.Name + "Result")
"Fem::FemPostPipeline", self.solver.Name + "Result"
)
self.solver.ElmerResult.Label = self.solver.ElmerResult.Name
self.solver.ElmerResult.ViewObject.SelectionStyle = "BoundBox"
self.analysis.addObject(self.solver.ElmerResult)
@@ -290,7 +277,7 @@ class Results(run.Results):
self.pushStatus("\nNo result file was created.\n")
self.fail()
return
pvdFile = open(pvdFilePath, "r")
pvdFile = open(pvdFilePath)
# read all lines
pvdContent = pvdFile.readlines()
# skip header and footer line and evaluate all lines
@@ -299,7 +286,7 @@ class Results(run.Results):
# so .split("\"") gives as 2nd the time and as 7th the filename
for i in range(0, len(pvdContent) - 2):
# get time
lineArray = pvdContent[i + 1].split("\"")
lineArray = pvdContent[i + 1].split('"')
time = float(lineArray[1])
filename = os.path.join(self.directory, lineArray[7])
if os.path.isfile(filename):
@@ -317,7 +304,7 @@ class Results(run.Results):
# but not the shape and bar coloring
self.solver.ElmerTimeResults[i].ViewObject.updateColorBars()
else:
self.pushStatus("\nResult file for time {} is missing.\n".format(time))
self.pushStatus(f"\nResult file for time {time} is missing.\n")
self.fail()
return
self.solver.Document.recompute()
@@ -328,9 +315,7 @@ class Results(run.Results):
# FreeCAD would replaces dots in object names with underscores, thus do the same
newName = self.solver.Name + "_" + str(time).replace(".", "_") + "_" + "Result"
if counter > len(self.solver.ElmerTimeResults):
pipeline = self.analysis.Document.addObject(
"Fem::FemPostPipeline", newName
)
pipeline = self.analysis.Document.addObject("Fem::FemPostPipeline", newName)
# App::PropertyLinkList does not support append
# thus we have to use a temporary list to append
tmplist = self.solver.ElmerTimeResults
@@ -343,9 +328,7 @@ class Results(run.Results):
# store current list before removing object since object removal will automatically
# remove entry from self.solver.ElmerTimeResults
tmplist = self.solver.ElmerTimeResults
self.analysis.Document.removeObject(
self.solver.ElmerTimeResults[counter - 1].Name
)
self.analysis.Document.removeObject(self.solver.ElmerTimeResults[counter - 1].Name)
tmplist[counter - 1] = self.analysis.Document.addObject(
"Fem::FemPostPipeline", newName
)
@@ -354,10 +337,7 @@ class Results(run.Results):
def _finishTimeResults(self, time, counter):
# we purposely use the decimal dot in the label
self.solver.ElmerTimeResults[counter].Label = (
"{}_{}_Result"
.format(self.solver.Name, time)
)
self.solver.ElmerTimeResults[counter].Label = f"{self.solver.Name}_{time}_Result"
self.solver.ElmerTimeResults[counter].ViewObject.OnTopWhenSelected = True
self.analysis.addObject(self.solver.ElmerTimeResults[counter])
# to assure the user sees something, set the default to Surface
@@ -389,4 +369,5 @@ class Results(run.Results):
self.fail()
return postPath
## @}

View File

@@ -63,25 +63,30 @@ _STARTINFO_NAME = "ELMERSOLVER_STARTINFO"
_SIF_NAME = "case.sif"
_ELMERGRID_IFORMAT = "8"
_ELMERGRID_OFORMAT = "2"
_COORDS_NON_MAGNETO_2D = ["Polar 2D", "Polar 3D", "Cartesian 3D",
"Cylindric", "Cylindric Symmetric"]
_COORDS_NON_MAGNETO_2D = [
"Polar 2D",
"Polar 3D",
"Cartesian 3D",
"Cylindric",
"Cylindric Symmetric",
]
def _getAllSubObjects(obj):
s = ["Solid" + str(i + 1) for i in range(len(obj.Shape.Solids))]
s.extend(("Face" + str(i + 1) for i in range(len(obj.Shape.Faces))))
s.extend(("Edge" + str(i + 1) for i in range(len(obj.Shape.Edges))))
s.extend(("Vertex" + str(i + 1) for i in range(len(obj.Shape.Vertexes))))
s.extend("Face" + str(i + 1) for i in range(len(obj.Shape.Faces)))
s.extend("Edge" + str(i + 1) for i in range(len(obj.Shape.Edges)))
s.extend("Vertex" + str(i + 1) for i in range(len(obj.Shape.Vertexes)))
return s
class Writer(object):
class Writer:
def __init__(self, solver, directory, testmode=False):
self.analysis = solver.getParentGroup()
self.solver = solver
self.directory = directory
Console.PrintMessage("Write elmer input files to: {}\n".format(self.directory))
Console.PrintMessage(f"Write elmer input files to: {self.directory}\n")
self.testmode = testmode
self._usedVarNames = set()
self._builder = sifio.Builder()
@@ -184,8 +189,9 @@ class Writer(object):
Console.PrintMessage(
"Unit schema: {} not supported by Elmer writer. "
"The FreeCAD standard unit schema mm/kg/s is used. "
"Elmer sif-file writing is done in Standard FreeCAD units.\n"
.format(Units.listSchemas(self.unit_schema))
"Elmer sif-file writing is done in Standard FreeCAD units.\n".format(
Units.listSchemas(self.unit_schema)
)
)
def getFromUi(self, value, unit, outputDim):
@@ -216,8 +222,7 @@ class Writer(object):
self._exportToUnv(groups, mesh, unvPath)
if self.testmode:
Console.PrintMessage(
"Solver Elmer testmode, ElmerGrid will not be used. "
"It might not be installed.\n"
"Solver Elmer testmode, ElmerGrid will not be used. It might not be installed.\n"
)
else:
binary = settings.get_binary("ElmerGrid")
@@ -226,29 +231,23 @@ class Writer(object):
raise WriteError("Could not find ElmerGrid binary.")
# for multithreading we first need a normal mesh creation run
# then a second to split the mesh into the number of used cores
argsBasic = [binary,
_ELMERGRID_IFORMAT,
_ELMERGRID_OFORMAT,
unvPath]
argsBasic = [binary, _ELMERGRID_IFORMAT, _ELMERGRID_OFORMAT, unvPath]
args = argsBasic
args.extend(["-out", self.directory])
if system() == "Windows":
subprocess.call(
args,
stdout=subprocess.DEVNULL,
startupinfo=femutils.startProgramInfo("hide")
args, stdout=subprocess.DEVNULL, startupinfo=femutils.startProgramInfo("hide")
)
else:
subprocess.call(args, stdout=subprocess.DEVNULL)
if num_cores > 1:
args = argsBasic
args.extend(["-partdual", "-metiskway", str(num_cores),
"-out", self.directory])
args.extend(["-partdual", "-metiskway", str(num_cores), "-out", self.directory])
if system() == "Windows":
subprocess.call(
args,
stdout=subprocess.DEVNULL,
startupinfo=femutils.startProgramInfo("hide")
startupinfo=femutils.startProgramInfo("hide"),
)
else:
subprocess.call(args, stdout=subprocess.DEVNULL)
@@ -281,10 +280,10 @@ class Writer(object):
tools.write_geo()
if self.testmode:
Console.PrintMessage(
"Solver Elmer testmode, Gmsh will not be used. "
"It might not be installed.\n"
"Solver Elmer testmode, Gmsh will not be used. It might not be installed.\n"
)
import shutil
shutil.copyfile(geoPath, os.path.join(self.directory, "group_mesh.geo"))
else:
tools.get_gmsh_command()
@@ -307,14 +306,13 @@ class Writer(object):
permittivity = float(objs[0].VacuumPermittivity.getValueAs("F/m"))
# since the base unit of FC is in mm, we must scale it to get plain SI
permittivity = permittivity * 1e-9
Console.PrintLog("Overwriting vacuum permittivity with: {}\n".format(permittivity))
Console.PrintLog(f"Overwriting vacuum permittivity with: {permittivity}\n")
self.constsdef["PermittivityOfVacuum"] = "{} {}".format(permittivity, "F/m")
self.handled(objs[0])
elif len(objs) > 1:
Console.PrintError(
"More than one permittivity constant overwriting objects ({} objs). "
"The permittivity constant overwriting is ignored.\n"
.format(len(objs))
"The permittivity constant overwriting is ignored.\n".format(len(objs))
)
def _handleSimulation(self):
@@ -336,18 +334,9 @@ class Writer(object):
self._simulation("Coordinate Scaling", 0.001)
self._simulation("Simulation Type", self.solver.SimulationType)
if self.solver.SimulationType == "Steady State":
self._simulation(
"Steady State Max Iterations",
self.solver.SteadyStateMaxIterations
)
self._simulation(
"Steady State Min Iterations",
self.solver.SteadyStateMinIterations
)
if (
self.solver.SimulationType == "Scanning"
or self.solver.SimulationType == "Transient"
):
self._simulation("Steady State Max Iterations", self.solver.SteadyStateMaxIterations)
self._simulation("Steady State Min Iterations", self.solver.SteadyStateMinIterations)
if self.solver.SimulationType == "Scanning" or self.solver.SimulationType == "Transient":
self._simulation("BDF Order", self.solver.BDFOrder)
self._simulation("Output Intervals", self.solver.OutputIntervals)
self._simulation("Timestep Intervals", self.solver.TimestepIntervals)
@@ -359,10 +348,7 @@ class Writer(object):
# updates older simulations
if not hasattr(self.solver, "CoordinateSystem"):
solver.addProperty(
"App::PropertyEnumeration",
"CoordinateSystem",
"Coordinate System",
""
"App::PropertyEnumeration", "CoordinateSystem", "Coordinate System", ""
)
solver.CoordinateSystem = solverClass.COORDINATE_SYSTEM
solver.CoordinateSystem = "Cartesian"
@@ -371,32 +357,21 @@ class Writer(object):
"App::PropertyIntegerConstraint",
"BDFOrder",
"Timestepping",
"Order of time stepping method 'BDF'"
"Order of time stepping method 'BDF'",
)
solver.BDFOrder = (2, 1, 5, 1)
if not hasattr(self.solver, "ElmerTimeResults"):
solver.addProperty(
"App::PropertyLinkList",
"ElmerTimeResults",
"Base",
"",
4 | 8
)
solver.addProperty("App::PropertyLinkList", "ElmerTimeResults", "Base", "", 4 | 8)
if not hasattr(self.solver, "OutputIntervals"):
solver.addProperty(
"App::PropertyIntegerList",
"OutputIntervals",
"Timestepping",
"After how many time steps a result file is output"
"After how many time steps a result file is output",
)
solver.OutputIntervals = [1]
if not hasattr(self.solver, "SimulationType"):
solver.addProperty(
"App::PropertyEnumeration",
"SimulationType",
"Type",
""
)
solver.addProperty("App::PropertyEnumeration", "SimulationType", "Type", "")
solver.SimulationType = solverClass.SIMULATION_TYPE
solver.SimulationType = "Steady State"
if not hasattr(self.solver, "TimestepIntervals"):
@@ -407,7 +382,7 @@ class Writer(object):
(
"List of maximum optimization rounds if 'Simulation Type'\n"
"is either 'Scanning' or 'Transient'"
)
),
)
solver.TimestepIntervals = [100]
if not hasattr(self.solver, "TimestepSizes"):
@@ -418,7 +393,7 @@ class Writer(object):
(
"List of time steps of optimization if 'Simulation Type'\n"
"is either 'Scanning' or 'Transient'"
)
),
)
solver.TimestepSizes = [0.1]
@@ -622,8 +597,9 @@ class Writer(object):
raise WriteError(
"The coordinate setting '{}'\n is not "
"supported by the equation 'Magnetodynamic2D'.\n\n"
"Possible is:\n'Cartesian 2D' or 'Axi Symmetric'"
.format(self.solver.CoordinateSystem)
"Possible is:\n'Cartesian 2D' or 'Axi Symmetric'".format(
self.solver.CoordinateSystem
)
)
solverSection = MgDyn2D.getMagnetodynamic2DSolver(equation)
@@ -661,14 +637,14 @@ class Writer(object):
"Disable the linear system.\n"
"Only use for special cases\n"
"and consult the Elmer docs."
)
),
)
if not hasattr(equation, "IdrsParameter"):
equation.addProperty(
"App::PropertyIntegerConstraint",
"IdrsParameter",
"Linear System",
"Parameter for iterative method 'Idrs'"
"Parameter for iterative method 'Idrs'",
)
equation.IdrsParameter = (2, 1, 10, 1)
@@ -682,25 +658,17 @@ class Writer(object):
if equation.LinearSystemSolverDisabled is True:
s["Linear System Solver Disabled"] = equation.LinearSystemSolverDisabled
if equation.LinearSolverType == "Direct":
s["Linear System Direct Method"] = \
equation.LinearDirectMethod
s["Linear System Direct Method"] = equation.LinearDirectMethod
elif equation.LinearSolverType == "Iterative":
s["Linear System Iterative Method"] = \
equation.LinearIterativeMethod
s["Linear System Iterative Method"] = equation.LinearIterativeMethod
if equation.LinearIterativeMethod == "BiCGStabl":
s["BiCGstabl polynomial degree"] = \
equation.BiCGstablDegree
s["BiCGstabl polynomial degree"] = equation.BiCGstablDegree
if equation.LinearIterativeMethod == "Idrs":
s["Idrs Parameter"] = \
equation.IdrsParameter
s["Linear System Max Iterations"] = \
equation.LinearIterations
s["Linear System Convergence Tolerance"] = \
equation.LinearTolerance
s["Linear System Preconditioning"] = \
equation.LinearPreconditioning
s["Steady State Convergence Tolerance"] = \
equation.SteadyStateTolerance
s["Idrs Parameter"] = equation.IdrsParameter
s["Linear System Max Iterations"] = equation.LinearIterations
s["Linear System Convergence Tolerance"] = equation.LinearTolerance
s["Linear System Preconditioning"] = equation.LinearPreconditioning
s["Steady State Convergence Tolerance"] = equation.SteadyStateTolerance
s["Linear System Abort Not Converged"] = False
s["Linear System Residual Output"] = 1
s["Linear System Precondition Recompute"] = 1
@@ -711,8 +679,7 @@ class Writer(object):
equation.setExpression("NonlinearTolerance", str(equation.NonlinearTolerance))
if self._hasExpression(equation) != equation.NonlinearNewtonAfterTolerance:
equation.setExpression(
"NonlinearNewtonAfterTolerance",
str(equation.NonlinearNewtonAfterTolerance)
"NonlinearNewtonAfterTolerance", str(equation.NonlinearNewtonAfterTolerance)
)
def createNonlinearSolver(self, equation):
@@ -721,16 +688,11 @@ class Writer(object):
# write the linear solver
s = self.createLinearSolver(equation)
# write the nonlinear solver
s["Nonlinear System Max Iterations"] = \
equation.NonlinearIterations
s["Nonlinear System Convergence Tolerance"] = \
equation.NonlinearTolerance
s["Nonlinear System Relaxation Factor"] = \
equation.RelaxationFactor
s["Nonlinear System Newton After Iterations"] = \
equation.NonlinearNewtonAfterIterations
s["Nonlinear System Newton After Tolerance"] = \
equation.NonlinearNewtonAfterTolerance
s["Nonlinear System Max Iterations"] = equation.NonlinearIterations
s["Nonlinear System Convergence Tolerance"] = equation.NonlinearTolerance
s["Nonlinear System Relaxation Factor"] = equation.RelaxationFactor
s["Nonlinear System Newton After Iterations"] = equation.NonlinearNewtonAfterIterations
s["Nonlinear System Newton After Tolerance"] = equation.NonlinearNewtonAfterTolerance
return s
# -------------------------------------------------------------------------------------------
@@ -777,7 +739,7 @@ class Writer(object):
return density
def _hasExpression(self, equation):
for (obj, exp) in equation.ExpressionEngine:
for obj, exp in equation.ExpressionEngine:
if obj == equation:
return exp
return None
@@ -824,10 +786,7 @@ class Writer(object):
# To get it back in the original size we let Elmer scale it back
s["Coordinate Scaling Revert"] = True
s["Equation"] = "ResultOutput"
if (
self.solver.SimulationType == "Scanning"
or self.solver.SimulationType == "Transient"
):
if self.solver.SimulationType == "Scanning" or self.solver.SimulationType == "Transient":
# we must execute the post solver every time we output a result
# therefore we must use the same as self.solver.OutputIntervals
s["Exec Intervals"] = self.solver.OutputIntervals
@@ -892,4 +851,5 @@ class Writer(object):
class WriteError(Exception):
pass
## @}

View File

@@ -34,21 +34,19 @@ if FreeCAD.GuiUp:
from pivy import coin
class BaseProxy(object):
class BaseProxy:
BaseType = "App::FeaturePython"
def __init__(self, obj):
obj.Proxy = self
obj.addProperty(
"App::PropertyLinkSubList", "References",
"Base", "")
obj.addProperty("App::PropertyLinkSubList", "References", "Base", "")
def execute(self, obj):
return True
class BaseViewProxy(object):
class BaseViewProxy:
def __init__(self, vobj):
vobj.Proxy = self

View File

@@ -38,11 +38,12 @@ except ImportError:
raise Exception("No Fenics modules found, please install them.\n")
class XDMFReader(object):
class XDMFReader:
"""
Reads XDMF file and provides unified interface for returning
cell functions or facet functions.
"""
def __init__(self, xdmffilename):
"""
Sets filename and sets mesh instance to None.
@@ -69,12 +70,7 @@ class XDMFReader(object):
xdmffile.close()
def readCellExpression(
self,
group_value_dict,
value_type="scalar",
overlap=lambda x: x[0],
*args,
**kwargs
self, group_value_dict, value_type="scalar", overlap=lambda x: x[0], *args, **kwargs
):
"""
Reads cell expression and returns it.
@@ -82,28 +78,19 @@ class XDMFReader(object):
value_type_dictionary = {
"scalar": ScalarCellExpressionFromXDMF,
"vector2d": Vector2DCellExpressionFromXDMF,
"vector3d": Vector3DCellExpressionFromXDMF}
"vector3d": Vector3DCellExpressionFromXDMF,
}
self.readMesh()
xdmffile = fenics.XDMFFile(self.xdmffilename)
cf = value_type_dictionary[value_type.lower()](
group_value_dict,
overlap=overlap,
*args, **kwargs
group_value_dict, overlap=overlap, *args, **kwargs
)
cf.init()
for (key, value) in cf.group_value_dict.items():
cf.markers[key] = fenics.MeshFunction(
"size_t",
self.mesh,
self.mesh.topology().dim()
)
for key, value in cf.group_value_dict.items():
cf.markers[key] = fenics.MeshFunction("size_t", self.mesh, self.mesh.topology().dim())
xdmffile.read(cf.markers[key], key)
cf.dx[key] = fenics.Measure(
"dx",
domain=self.mesh,
subdomain_data=cf.markers[key]
)
cf.dx[key] = fenics.Measure("dx", domain=self.mesh, subdomain_data=cf.markers[key])
xdmffile.close()
return cf
@@ -115,34 +102,30 @@ class XDMFReader(object):
xdmffile = fenics.XDMFFile(self.xdmffilename)
ff = FacetFunctionFromXDMF(group_value_dict, *args, **kwargs)
ff.init()
for (key, value) in ff.group_value_dict.items():
for key, value in ff.group_value_dict.items():
ff.markers[key] = fenics.MeshFunction(
"size_t",
self.mesh,
self.mesh.topology().dim() - 1
"size_t", self.mesh, self.mesh.topology().dim() - 1
)
xdmffile.read(ff.markers[key], key)
ff.marked[key] = value.get("marked", 1)
ff.ds[key] = fenics.Measure(
"ds",
domain=self.mesh,
subdomain_data=ff.markers[key]
)
ff.ds[key] = fenics.Measure("ds", domain=self.mesh, subdomain_data=ff.markers[key])
ff.bcs[key] = value
xdmffile.close()
return ff
class CellExpressionFromXDMF(object):
class CellExpressionFromXDMF:
"""
Creates cell function expression from XDMF file.
"""
def __init__(
self, group_value_dict,
default=lambda x: 0.,
self,
group_value_dict,
default=lambda x: 0.0,
check_marked=(lambda x: x == 1),
overlap=lambda x: x[0],
**kwargs
**kwargs,
):
self.init()
self.group_value_dict = group_value_dict
@@ -160,7 +143,8 @@ class CellExpressionFromXDMF(object):
def eval_cell_backend(self, values, x, cell):
values_list = [
func(x) for (key, func) in self.group_value_dict.items()
func(x)
for (key, func) in self.group_value_dict.items()
if self.check_marked(self.markers[key][cell.index])
]
return_value = self.overlap(values_list)
@@ -183,16 +167,13 @@ class ScalarCellExpressionFromXDMF(fenics.Expression, CellExpressionFromXDMF):
def __init__(
self,
group_value_dict,
default=lambda x: 0.,
default=lambda x: 0.0,
check_marked=(lambda x: x == 1),
overlap=lambda x: x[0],
**kwargs
**kwargs,
):
CellExpressionFromXDMF.__init__(
self, group_value_dict,
default=default,
check_marked=check_marked,
overlap=overlap
self, group_value_dict, default=default, check_marked=check_marked, overlap=overlap
)
def eval_cell(self, values, x, cell):
@@ -209,13 +190,11 @@ class Vector3DCellExpressionFromXDMF(fenics.Expression, CellExpressionFromXDMF):
group_value_dict,
default=lambda x: np.zeros((3,)),
check_marked=(lambda x: x == 1),
overlap=lambda x: x[0], **kwargs
overlap=lambda x: x[0],
**kwargs,
):
CellExpressionFromXDMF.__init__(
self, group_value_dict,
default=default,
check_marked=check_marked,
overlap=overlap
self, group_value_dict, default=default, check_marked=check_marked, overlap=overlap
)
def eval_cell(self, values, x, cell):
@@ -233,14 +212,10 @@ class Vector2DCellExpressionFromXDMF(fenics.Expression, CellExpressionFromXDMF):
default=lambda x: np.zeros((2,)),
check_marked=(lambda x: x == 1),
overlap=lambda x: x[0],
**kwargs
**kwargs,
):
CellExpressionFromXDMF.__init__(
self,
group_value_dict,
default=default,
check_marked=check_marked,
overlap=overlap
self, group_value_dict, default=default, check_marked=check_marked, overlap=overlap
)
def eval_cell(self, values, x, cell):
@@ -250,10 +225,11 @@ class Vector2DCellExpressionFromXDMF(fenics.Expression, CellExpressionFromXDMF):
return (2,)
class FacetFunctionFromXDMF(object):
class FacetFunctionFromXDMF:
"""
Creates facet function from XDMF file.
"""
def __init__(self, group_value_dict, *args, **kwargs):
self.group_value_dict = group_value_dict
self.init()
@@ -266,18 +242,21 @@ class FacetFunctionFromXDMF(object):
def getDirichletBCs(self, vectorspace, *args, **kwargs):
dbcs = []
for (dict_key, dict_value) in self.bcs.items():
for dict_key, dict_value in self.bcs.items():
if dict_value["type"] == "Dirichlet":
bc = fenics.DirichletBC(
vectorspace,
dict_value["value"],
self.markers[dict_key],
dict_value.get("marked", 1),
*args, **kwargs
*args,
**kwargs,
)
dbcs.append(bc)
return dbcs
# TODO: write some functions to return integrals for Neumann and Robin
# boundary conditions for the general case (i.e. vector, tensor)
## @}

View File

@@ -41,22 +41,19 @@ def add_con_fixed(f, model, mystran_writer):
spc_ids.append(conid)
fixed_obj = femobj["Object"]
# print(fixed_obj.Name)
fixed_code += "# {}\n".format(fixed_obj.Name)
fixed_code += f"# {fixed_obj.Name}\n"
# node set
fixed_code += "nodes_{} = {}\n".format(fixed_obj.Name, femobj["Nodes"])
# all nodes in one line may be to long ... FIXME
fixed_code += (
"model.add_spc1(conid={}, components={}, nodes=nodes_{})\n\n"
.format(conid, "123456", fixed_obj.Name)
fixed_code += "model.add_spc1(conid={}, components={}, nodes=nodes_{})\n\n".format(
conid, "123456", fixed_obj.Name
)
# spcadd card
spcadd_code = "# spcadd card, Single-Point Constraint Set Combination from SPC or SPC1 cards\n"
spcadd_code += (
"model.add_spcadd(conid=1, sets={})\n\n".format(spc_ids)
)
spcadd_code += f"model.add_spcadd(conid=1, sets={spc_ids})\n\n"
pynas_code = "{}\n{}".format(fixed_code, spcadd_code)
pynas_code = f"{fixed_code}\n{spcadd_code}"
# print(pynas_code)
# write the pyNastran code

View File

@@ -44,28 +44,26 @@ def add_con_force(f, model, mystran_writer):
force_obj = femobj["Object"]
# print(force_obj.Name)
force_code += "# {}\n".format(force_obj.Name)
force_code += f"# {force_obj.Name}\n"
dirvec = femobj["Object"].DirectionVector
print(femobj["NodeLoadTable"])
for ref_shape in femobj["NodeLoadTable"]:
force_code += "# {}\n".format(ref_shape[0])
force_code += f"# {ref_shape[0]}\n"
for n in sorted(ref_shape[1]):
# the loads in ref_shape[1][n] are without unit
node_load = ref_shape[1][n]
force_code += (
"model.add_force(sid={}, node={}, mag={}, xyz=({}, {}, {}))\n"
.format(sid, n, node_load, dirvec.x, dirvec.y, dirvec.z)
force_code += "model.add_force(sid={}, node={}, mag={}, xyz=({}, {}, {}))\n".format(
sid, n, node_load, dirvec.x, dirvec.y, dirvec.z
)
force_code += "\n"
# generate calce factors lists
# load card, static load combinations
load_code = (
"model.add_load(sid=1, scale=1.0, scale_factors={}, load_ids={})\n\n\n"
.format(scale_factors, load_ids)
load_code = "model.add_load(sid=1, scale=1.0, scale_factors={}, load_ids={})\n\n\n".format(
scale_factors, load_ids
)
pynas_code = "{}\n{}".format(force_code, load_code)
pynas_code = f"{force_code}\n{load_code}"
# print(pynas_code)
# write the pyNastran code

View File

@@ -40,13 +40,9 @@ def add_femelement_geometry(f, model, mystran_writer):
width = beamsec_obj.RectWidth.getValueAs("mm").Value
pynas_code = "# pbarl card, properties of a simple beam element (CBAR entry)\n"
pynas_code += "# defined by cross-sectional dimensions\n"
pynas_code += (
"dim = [{}, {}]\n"
.format(width, height)
)
pynas_code += (
"model.add_pbarl(pid=1, mid=1, Type={}, dim=dim, nsm=0.0)\n"
.format('"BAR"')
pynas_code += f"dim = [{width}, {height}]\n"
pynas_code += "model.add_pbarl(pid=1, mid=1, Type={}, dim=dim, nsm=0.0)\n".format(
'"BAR"'
)
pynas_code += "# pbarl.validate()\n\n\n"
else:
@@ -56,9 +52,8 @@ def add_femelement_geometry(f, model, mystran_writer):
shellth_obj = mystran_writer.member.geos_shellthickness[0]["Object"]
thickness = shellth_obj.Thickness.getValueAs("mm").Value
pynas_code = "# pshell card, thin shell element properties\n"
pynas_code += (
"model.add_pshell(pid=1, mid1=1, t={}, mid2=1, mid3=1)\n\n\n"
.format(thickness)
pynas_code += "model.add_pshell(pid=1, mid1=1, t={}, mid2=1, mid3=1)\n\n\n".format(
thickness
)
else:
pynas_code = "# psolid card, defines solid element\n"

View File

@@ -41,10 +41,7 @@ def add_femelement_material(f, model, mystran_writer):
YM_in_MPa = YM.getValueAs("MPa").Value
PR = float(mat_obj.Material["PoissonRatio"])
pynas_code = "# mat1 card, material properties for linear isotropic material\n"
pynas_code += (
"mat = model.add_mat1(mid=1, E={:.1f}, G=None, nu={})\n\n\n"
.format(YM_in_MPa, PR)
)
pynas_code += f"mat = model.add_mat1(mid=1, E={YM_in_MPa:.1f}, G=None, nu={PR})\n\n\n"
# write the pyNastran code
f.write(pynas_code)

View File

@@ -38,19 +38,14 @@ def add_mesh(f, model, mystran_writer):
if not mystran_writer.femnodes_mesh:
mystran_writer.femnodes_mesh = mystran_writer.femmesh.Nodes
if not mystran_writer.femelement_table:
mystran_writer.femelement_table = meshtools.get_femelement_table(
mystran_writer.femmesh
)
mystran_writer.femelement_table = meshtools.get_femelement_table(mystran_writer.femmesh)
mesh_eletype = exportNastranMesh.get_export_element_type(
mystran_writer.femmesh,
mystran_writer.femelement_table
mystran_writer.femmesh, mystran_writer.femelement_table
)
# get the pynas code
mesh_pynas_code = exportNastranMesh.get_pynastran_mesh(
mystran_writer.femnodes_mesh,
mystran_writer.femelement_table,
mesh_eletype
mystran_writer.femnodes_mesh, mystran_writer.femelement_table, mesh_eletype
)
# print(mesh_pynas_code)

View File

@@ -45,18 +45,16 @@ ANALYSIS_TYPES = ["static"]
def create(doc, name="SolverMystran"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(solverbase.Proxy):
"""The Fem::FemSolver's Proxy python type, add solver specific properties
"""
"""The Fem::FemSolver's Proxy python type, add solver specific properties"""
Type = "Fem::SolverMystran"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.Proxy = self
# mystran_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Mystran")
@@ -67,19 +65,21 @@ class Proxy(solverbase.Proxy):
def createMachine(self, obj, directory, testmode=False):
return run.Machine(
solver=obj, directory=directory,
solver=obj,
directory=directory,
check=tasks.Check(),
prepare=tasks.Prepare(),
solve=tasks.Solve(),
results=tasks.Results(),
testmode=testmode)
testmode=testmode,
)
def editSupported(self):
return True
def edit(self, directory):
pattern = os.path.join(directory, "*.bdf") # TODO Mystran file ending
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
# see comment in oofem solver file
@@ -93,4 +93,5 @@ class ViewProxy(solverbase.ViewProxy):
def getIcon(self):
return ":/icons/FEM_SolverMystran.svg"
## @}

View File

@@ -37,6 +37,7 @@ import FreeCAD
try:
import hfcMystranNeuIn
result_reading = True
except Exception:
FreeCAD.Console.PrintWarning("Module to read results not found.\n")
@@ -120,7 +121,7 @@ class Solve(run.Solve):
args=[binary, infile], # pass empty param fails! [binary, "", infile]
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)
self.signalAbort.add(self._process.terminate)
self._process.communicate()
@@ -132,8 +133,7 @@ class Solve(run.Solve):
class Results(run.Results):
def run(self):
prefs = FreeCAD.ParamGet(
"User parameter:BaseApp/Preferences/Mod/Fem/General")
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
if not prefs.GetBool("KeepResultsOnReRun", False):
self.purge_results()
if result_reading is True:
@@ -160,10 +160,8 @@ class Results(run.Results):
break
else:
# TODO: use solver framework status message system
FreeCAD.Console.PrintError(
"FEM: No results found at {}!\n"
.format(neu_result_file)
)
FreeCAD.Console.PrintError(f"FEM: No results found at {neu_result_file}!\n")
self.fail()
## @}

View File

@@ -52,22 +52,10 @@ from .. import writerbase
class FemInputWriterMystran(writerbase.FemInputWriter):
def __init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name=None,
mat_geo_sets=None
self, analysis_obj, solver_obj, mesh_obj, member, dir_name=None, mat_geo_sets=None
):
writerbase.FemInputWriter.__init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name,
mat_geo_sets
self, analysis_obj, solver_obj, mesh_obj, member, dir_name, mat_geo_sets
)
# basename (only for implementation purpose later delete this code
# the mesh should never be None for Calculix solver
@@ -78,17 +66,14 @@ class FemInputWriterMystran(writerbase.FemInputWriter):
self.basename = "Mesh"
self.solverinput_file = join(self.dir_name, self.basename + ".bdf")
self.pynasinput_file = join(self.dir_name, self.basename + ".py")
FreeCAD.Console.PrintLog(
"FemInputWriterMystran --> self.dir_name --> {}\n"
.format(self.dir_name)
FreeCAD.Console.PrintLog(f"FemInputWriterMystran --> self.dir_name --> {self.dir_name}\n")
FreeCAD.Console.PrintMessage(
"FemInputWriterMystra --> self.solverinput_file --> {}\n".format(
self.solverinput_file
)
)
FreeCAD.Console.PrintMessage(
"FemInputWriterMystra --> self.solverinput_file --> {}\n"
.format(self.solverinput_file)
)
FreeCAD.Console.PrintMessage(
"FemInputWriterMystra --> self.pynasf_name --> {}\n"
.format(self.pynasinput_file)
f"FemInputWriterMystra --> self.pynasf_name --> {self.pynasinput_file}\n"
)
def write_solver_input(self):
@@ -112,8 +97,9 @@ class FemInputWriterMystran(writerbase.FemInputWriter):
model = add_solver_control.add_solver_control(pynasf, model, self)
pynasf.write(
"\n\nmodel.write_bdf('{}', enddata=True)\n"
.format(join(self.dir_name, self.basename + "_pyNas.bdf"))
"\n\nmodel.write_bdf('{}', enddata=True)\n".format(
join(self.dir_name, self.basename + "_pyNas.bdf")
)
)
pynasf.close()
@@ -121,9 +107,8 @@ class FemInputWriterMystran(writerbase.FemInputWriter):
# print(model.get_bdf_stats())
model.write_bdf(self.solverinput_file, enddata=True)
writing_time_string = (
"Writing time input file: {} seconds"
.format(round((time.process_time() - timestart), 2))
writing_time_string = "Writing time input file: {} seconds".format(
round((time.process_time() - timestart), 2)
)
FreeCAD.Console.PrintMessage(writing_time_string + " \n\n")

View File

@@ -46,10 +46,10 @@ def display(report, title=None, text=None):
def displayGui(report, title=None, text=None):
import FreeCADGui as Gui
from . import reportdialog
if not report.isEmpty():
mw = Gui.getMainWindow()
dialog = reportdialog.ReportDialog(
report, title, text, mw)
dialog = reportdialog.ReportDialog(report, title, text, mw)
dialog.exec_()
@@ -62,7 +62,7 @@ def displayLog(report):
App.Console.PrintError("%s\n" % e)
class Report(object):
class Report:
def __init__(self):
self.infos = []
@@ -95,4 +95,5 @@ class Report(object):
def error(self, msg):
self.errors.append(msg)
## @}

View File

@@ -42,7 +42,7 @@ INFO_COLOR = "Logging"
class ReportDialog(QtGui.QDialog):
def __init__(self, report, title="Report", text=None, parent=None):
super(ReportDialog, self).__init__(parent)
super().__init__(parent)
msgDetails = QtGui.QTextEdit()
msgDetails.setReadOnly(True)
msgDetails.setHtml(self._getText(report))
@@ -73,8 +73,7 @@ class ReportDialog(QtGui.QDialog):
return text
def _getColoredLine(self, text, outputwin_color_type):
return '<font color="{}">{}</font>'.format(
getOutputWinColor(outputwin_color_type), text
)
return f'<font color="{getOutputWinColor(outputwin_color_type)}">{text}</font>'
## @}

View File

@@ -40,6 +40,7 @@ import os
import os.path
import shutil
import tempfile
# import threading # not used ATM
import FreeCAD as App
@@ -69,7 +70,7 @@ _dirTypes = {}
def run_fem_solver(solver, working_dir=None):
""" Execute *solver* of the solver framework.
"""Execute *solver* of the solver framework.
Uses :meth:`getMachine <femsolver.solverbase.Proxy.getMachine>` to obtain a
:class:`Machine` instance of the solver. It than executes the Machine with
@@ -101,6 +102,7 @@ def run_fem_solver(solver, working_dir=None):
if solver.Proxy.Type == "Fem::SolverCcxTools":
from femtools.ccxtools import CcxTools as ccx
App.Console.PrintMessage("Run of CalxuliX ccx tools solver started.\n")
fea = ccx(solver)
fea.reset_mesh_purge_results_checked()
@@ -117,7 +119,7 @@ def run_fem_solver(solver, working_dir=None):
fea.ccx_run()
fea.load_results()
else:
App.Console.PrintError("Houston, we have a problem...!\n{}\n".format(message))
App.Console.PrintError(f"Houston, we have a problem...!\n{message}\n")
App.Console.PrintMessage("Run of CalxuliX ccx tools solver finished.\n")
else:
# App.Console.PrintMessage("Frame work solver!\n")
@@ -130,14 +132,12 @@ def run_fem_solver(solver, working_dir=None):
error_message = (
"Please save the file before executing the solver. "
"This must be done because the location of the working "
"directory is set to \"Beside *.FCStd File\"."
'directory is set to "Beside *.FCStd File".'
)
App.Console.PrintError(error_message + "\n")
if App.GuiUp:
QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),
"Can't start Solver",
error_message
FreeCADGui.getMainWindow(), "Can't start Solver", error_message
)
return
except DirectoryDoesNotExistError:
@@ -145,9 +145,7 @@ def run_fem_solver(solver, working_dir=None):
App.Console.PrintError(error_message + "\n")
if App.GuiUp:
QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),
"Can't start Solver",
error_message
FreeCADGui.getMainWindow(), "Can't start Solver", error_message
)
return
if not machine.running:
@@ -158,6 +156,7 @@ def run_fem_solver(solver, working_dir=None):
if machine.failed is True:
App.Console.PrintError("Machine failed to run.\n")
from .report import displayLog
displayLog(machine.report)
if App.GuiUp:
error_message = (
@@ -165,11 +164,12 @@ def run_fem_solver(solver, working_dir=None):
"of the following errors are resolved."
)
from .report import display
display(machine.report, "Run Report", error_message)
def getMachine(solver, path=None):
""" Get or create :class:`Machine` using caching mechanism.
"""Get or create :class:`Machine` using caching mechanism.
:param solver:
A document object which must be a framework compliant solver. This means
@@ -257,14 +257,12 @@ def _getBesideBase(solver):
error_message = (
"Please save the file before executing the solver. "
"This must be done because the location of the working "
"directory is set to \"Beside *.FCStd File\"."
'directory is set to "Beside *.FCStd File".'
)
App.Console.PrintError(error_message + "\n")
if App.GuiUp:
QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),
"Can't start Solver",
error_message
FreeCADGui.getMainWindow(), "Can't start Solver", error_message
)
raise MustSaveError()
# TODO may be do not abort but use a temporary directory
@@ -273,8 +271,7 @@ def _getBesideBase(solver):
def _getCustomDir(solver):
base = _getCustomBase(solver)
specificPath = os.path.join(
base, solver.Document.Name, solver.Label)
specificPath = os.path.join(base, solver.Document.Name, solver.Label)
specificPath = _getUniquePath(specificPath)
if not os.path.isdir(specificPath):
os.makedirs(specificPath)
@@ -288,9 +285,7 @@ def _getCustomBase(solver):
App.Console.PrintError(error_message + "\n")
if App.GuiUp:
QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),
"Can't start Solver",
error_message
FreeCADGui.getMainWindow(), "Can't start Solver", error_message
)
raise DirectoryDoesNotExistError("Invalid path")
return path
@@ -309,7 +304,7 @@ def _getUniquePath(path):
class BaseTask(task.Thread):
def __init__(self):
super(BaseTask, self).__init__()
super().__init__()
self.solver = None
self.directory = None
self.testmode = None
@@ -321,10 +316,8 @@ class BaseTask(task.Thread):
class Machine(BaseTask):
def __init__(
self, solver, directory, check,
prepare, solve, results, testmode):
super(Machine, self).__init__()
def __init__(self, solver, directory, check, prepare, solve, results, testmode):
super().__init__()
self.solver = solver
self.directory = directory
self.signalState = set()
@@ -346,11 +339,7 @@ class Machine(BaseTask):
self._confTasks()
self._isReset = False
self._pendingState = self.state
while (
not self.aborted
and not self.failed
and self._pendingState <= self.target
):
while not self.aborted and not self.failed and self._pendingState <= self.target:
task = self._getTask(self._pendingState)
self._runTask(task)
self.report.extend(task.report)
@@ -363,21 +352,14 @@ class Machine(BaseTask):
self._applyPending()
def reset(self, newState=CHECK):
state = (self.state
if self._pendingState is None
else self._pendingState)
state = self.state if self._pendingState is None else self._pendingState
if newState < state:
self._isReset = True
self._state = newState
signal.notify(self.signalState)
def _confTasks(self):
tasks = [
self.check,
self.prepare,
self.solve,
self.results
]
tasks = [self.check, self.prepare, self.solve, self.results]
for t in tasks:
t.solver = self.solver
t.directory = self.directory
@@ -397,6 +379,7 @@ class Machine(BaseTask):
def killer():
task.abort()
self.signalAbort.add(killer)
task.signalStatus.add(statusProxy)
task.start()
@@ -428,10 +411,7 @@ class Check(BaseTask):
self.fail()
return False
elif len(meshes) > 1:
self.report.error(
"Too many meshes. "
"More than one mesh is not supported."
)
self.report.error("Too many meshes. More than one mesh is not supported.")
self.fail()
return False
return True
@@ -439,10 +419,7 @@ class Check(BaseTask):
def check_material_exists(self):
objs = self.get_several_member("App::MaterialObjectPython")
if len(objs) == 0:
self.report.error(
"Missing a material object. "
"At least one material is required."
)
self.report.error("Missing a material object. At least one material is required.")
self.fail()
return False
return True
@@ -507,10 +484,7 @@ class Check(BaseTask):
if femutils.is_of_type(m, *sc):
supported = True
if not supported:
self.report.warning(
"Ignored unsupported constraint: {}"
.format(m.Label)
)
self.report.warning(f"Ignored unsupported constraint: {m.Label}")
return True
@@ -538,7 +512,7 @@ class Results(BaseTask):
pass
class _DocObserver(object):
class _DocObserver:
_instance = None
_WHITELIST = [
@@ -546,11 +520,7 @@ class _DocObserver(object):
"App::MaterialObject",
"Fem::FemMeshObject",
]
_BLACKLIST_PROPS = [
"Label",
"ElmerOutput",
"ElmerResult"
]
_BLACKLIST_PROPS = ["Label", "ElmerOutput", "ElmerResult"]
def __init__(self):
self._saved = {}

View File

@@ -51,7 +51,7 @@ import FreeCAD
class DirSetting:
""" Enum of possible directory setting values.
"""Enum of possible directory setting values.
Strings used to indicate the solver directory setting set in FreeCADs
setting system. Returned by :func:`get_dir_setting` for that purpose. There
@@ -71,6 +71,7 @@ class DirSetting:
Use directory set below. Create own subdirectory for every solver. Name
directory after the solver label prefixed with the document name.
"""
TEMPORARY = "temporary"
BESIDE = "beside"
CUSTOM = "custom"
@@ -82,7 +83,7 @@ _GENERAL_PARAM = _PARAM_PATH + "General"
def get_binary(name, silent=False):
""" Find binary of solver *name* honoring user settings.
"""Find binary of solver *name* honoring user settings.
Return the specific path set by the user in FreeCADs settings/parameter
system if set or the default binary name if no specific path is set. If no
@@ -101,14 +102,13 @@ def get_binary(name, silent=False):
if not silent:
FreeCAD.Console.PrintError(
"Settings solver name: {} not found in "
"solver settings modules _SOLVER_PARAM dirctionary.\n"
.format(name)
"solver settings modules _SOLVER_PARAM dirctionary.\n".format(name)
)
return None
def get_cores(name):
""" Read number of CPU cores for solver *name* honoring user settings.
"""Read number of CPU cores for solver *name* honoring user settings.
Returns number of CPU cores to be used for the solver run
@@ -120,14 +120,13 @@ def get_cores(name):
else:
FreeCAD.Console.PrintError(
"Settings solver name: {} not found in "
"solver settings modules _SOLVER_PARAM dirctionary.\n"
.format(name)
"solver settings modules _SOLVER_PARAM dirctionary.\n".format(name)
)
return None
def get_write_comments(name):
""" Check whether "write_comments" is set for solver.
"""Check whether "write_comments" is set for solver.
Returns ``True`` if the "write_comments" setting/parameter is set for the
solver with the id *name*. Returns ``False`` otherwise. If the solver is
@@ -140,20 +139,19 @@ def get_write_comments(name):
else:
FreeCAD.Console.PrintError(
"Settings solver name: {} not found in "
"solver settings modules _SOLVER_PARAM dirctionary.\n"
.format(name)
"solver settings modules _SOLVER_PARAM dirctionary.\n".format(name)
)
return None
def get_custom_dir():
""" Get value for :term:`General/CustomDirectoryPath` parameter. """
"""Get value for :term:`General/CustomDirectoryPath` parameter."""
param_group = FreeCAD.ParamGet(_GENERAL_PARAM)
return param_group.GetString("CustomDirectoryPath")
def get_dir_setting():
""" Return directory setting set by the user.
"""Return directory setting set by the user.
Return one of the three possible values of the :class:`DirSetting` enum
depending on the setting set in FreeCAD parameter system. Result dependes
@@ -169,8 +167,7 @@ def get_dir_setting():
def get_default_solver():
""" Return default solver name.
"""
"""Return default solver name."""
solver_map = {0: "None"}
if get_binary("Calculix", True):
solver_map[1] = "CalculiXCcxTools"
@@ -184,8 +181,8 @@ def get_default_solver():
return solver_map[param_group.GetInt("DefaultSolver", 0)]
class _SolverDlg(object):
""" Internal query logic for solver specific settings.
class _SolverDlg:
"""Internal query logic for solver specific settings.
Each instance queries settings for one specific solver (e.g. Elmer) common
among all solvers. To clarify: There are a few settings that are useful
@@ -233,26 +230,26 @@ class _SolverDlg(object):
# without any additional user input
# see ccxttols, it works for Windows and Linux there
binary = self.default
FreeCAD.Console.PrintLog("Solver binary path default: {} \n".format(binary))
FreeCAD.Console.PrintLog(f"Solver binary path default: {binary} \n")
# check if use_default is set to True
# if True the standard binary path will be overwritten with a user binary path
if self.param_group.GetBool(self.use_default, True) is False:
binary = self.param_group.GetString(self.custom_path)
FreeCAD.Console.PrintLog("Solver binary path user setting: {} \n".format(binary))
FreeCAD.Console.PrintLog(f"Solver binary path user setting: {binary} \n")
# get the whole binary path name for the given command or binary path and return it
# None is returned if the binary has not been found
# The user does not know what exactly has going wrong.
from shutil import which as find_bin
the_found_binary = find_bin(binary)
if (the_found_binary is None) and (not silent):
FreeCAD.Console.PrintError(
"The binary has not been found. Full binary search path: {}\n"
.format(binary)
f"The binary has not been found. Full binary search path: {binary}\n"
)
else:
FreeCAD.Console.PrintLog("Found solver binary path: {}\n".format(the_found_binary))
FreeCAD.Console.PrintLog(f"Found solver binary path: {the_found_binary}\n")
return the_found_binary
def get_cores(self):
@@ -268,25 +265,30 @@ _SOLVER_PARAM = {
default="ccx",
param_path=_PARAM_PATH + "Ccx",
use_default="UseStandardCcxLocation",
custom_path="ccxBinaryPath"),
custom_path="ccxBinaryPath",
),
"ElmerSolver": _SolverDlg(
default="ElmerSolver",
param_path=_PARAM_PATH + "Elmer",
use_default="UseStandardElmerLocation",
custom_path="elmerBinaryPath"),
custom_path="elmerBinaryPath",
),
"ElmerGrid": _SolverDlg(
default="ElmerGrid",
param_path=_PARAM_PATH + "Elmer",
use_default="UseStandardGridLocation",
custom_path="gridBinaryPath"),
custom_path="gridBinaryPath",
),
"Mystran": _SolverDlg(
default="mystran",
param_path=_PARAM_PATH + "Mystran",
use_default="UseStandardMystranLocation",
custom_path="mystranBinaryPath"),
custom_path="mystranBinaryPath",
),
"Z88": _SolverDlg(
default="z88r",
param_path=_PARAM_PATH + "Z88",
use_default="UseStandardZ88Location",
custom_path="z88BinaryPath"),
custom_path="z88BinaryPath",
),
}

View File

@@ -33,4 +33,5 @@ def notify(signal, *args):
for slot in signal:
slot(*args)
## @}

View File

@@ -39,10 +39,7 @@ import femsolver.run
_UPDATE_INTERVAL = 50
_REPORT_TITLE = "Run Report"
_REPORT_ERR = (
"Failed to run. Please try again after all "
"of the following errors are resolved."
)
_REPORT_ERR = "Failed to run. Please try again after all of the following errors are resolved."
class ControlTaskPanel(QtCore.QObject):
@@ -56,7 +53,7 @@ class ControlTaskPanel(QtCore.QObject):
machineStateChanged = QtCore.Signal(float)
def __init__(self, machine):
super(ControlTaskPanel, self).__init__()
super().__init__()
self.form = ControlWidget()
self._machine = None
@@ -89,14 +86,12 @@ class ControlTaskPanel(QtCore.QObject):
self.machineStatusChanged.connect(self.form.appendStatus)
self.machineStatusCleared.connect(self.form.clearStatus)
self.machineTimeChanged.connect(self.form.setTime)
self.machineStateChanged.connect(
lambda: self.form.updateState(self.machine))
self.machineStateChanged.connect(lambda: self.form.updateState(self.machine))
self.machineChanged.connect(self._updateTimer)
# Set initial machine. Signal updates the widget.
self.machineChanged.connect(self.updateWidget)
self.form.destroyed.connect(
lambda: self.machineChanged.disconnect(self.updateWidget))
self.form.destroyed.connect(lambda: self.machineChanged.disconnect(self.updateWidget))
self.machine = machine
@@ -125,8 +120,7 @@ class ControlTaskPanel(QtCore.QObject):
@QtCore.Slot()
def edit(self):
self.machine.reset(femsolver.run.SOLVE)
self.machine.solver.Proxy.edit(
self.machine.directory)
self.machine.solver.Proxy.edit(self.machine.directory)
@QtCore.Slot()
def abort(self):
@@ -142,8 +136,7 @@ class ControlTaskPanel(QtCore.QObject):
@QtCore.Slot()
def updateMachine(self):
if self.form.directory() != self.machine.directory:
self.machine = femsolver.run.getMachine(
self.machine.solver, self.form.directory())
self.machine = femsolver.run.getMachine(self.machine.solver, self.form.directory())
@QtCore.Slot()
def _updateTimer(self):
@@ -207,7 +200,7 @@ class ControlWidget(QtGui.QWidget):
directoryChanged = QtCore.Signal()
def __init__(self, parent=None):
super(ControlWidget, self).__init__(parent)
super().__init__(parent)
self._setupUi()
self._inputFileName = ""
@@ -339,8 +332,8 @@ class ControlWidget(QtGui.QWidget):
self._directoryGrp.setDisabled(False)
self._writeBtt.setDisabled(False)
self._editBtt.setDisabled(
not machine.solver.Proxy.editSupported()
or machine.state <= femsolver.run.PREPARE
not machine.solver.Proxy.editSupported() or machine.state <= femsolver.run.PREPARE
)
## @}

View File

@@ -42,7 +42,7 @@ if App.GuiUp:
from . import solver_taskpanel
class Proxy(object):
class Proxy:
BaseType = "Fem::FemSolverObjectPython"
@@ -60,8 +60,7 @@ class Proxy(object):
raise NotImplementedError()
def addEquation(self, obj, eqId):
obj.addObject(self.createEquation(
obj.Document, eqId))
obj.addObject(self.createEquation(obj.Document, eqId))
def editSupported(self):
return False
@@ -73,7 +72,7 @@ class Proxy(object):
return True
class ViewProxy(object):
class ViewProxy:
"""Proxy for FemSolverElmers View Provider."""
def __init__(self, vobj):
@@ -87,23 +86,15 @@ class ViewProxy(object):
error_message = (
"Please save the file before opening the task panel. "
"This must be done because the location of the working "
"directory is set to \"Beside *.FCStd File\"."
'directory is set to "Beside *.FCStd File".'
)
App.Console.PrintError(error_message + "\n")
QtGui.QMessageBox.critical(
Gui.getMainWindow(),
"Can't open Task Panel",
error_message
)
QtGui.QMessageBox.critical(Gui.getMainWindow(), "Can't open Task Panel", error_message)
return False
except DirectoryDoesNotExistError:
error_message = "Selected working directory doesn't exist."
App.Console.PrintError(error_message + "\n")
QtGui.QMessageBox.critical(
Gui.getMainWindow(),
"Can't open Task Panel",
error_message
)
QtGui.QMessageBox.critical(Gui.getMainWindow(), "Can't open Task Panel", error_message)
return False
task = solver_taskpanel.ControlTaskPanel(machine)
Gui.Control.showDialog(task)
@@ -121,4 +112,5 @@ class ViewProxy(object):
def attach(self, vobj):
pass
## @}

View File

@@ -36,7 +36,7 @@ from . import report
from . import signal
class Task(object):
class Task:
def __init__(self):
self.report = None
@@ -57,15 +57,13 @@ class Task(object):
def stopping():
self.stopTime = time.time()
self.running = False
self.signalStopping.add(stopping)
@property
def time(self):
if self.startTime is not None:
endTime = (
self.stopTime
if self.stopTime is not None
else time.time())
endTime = self.stopTime if self.stopTime is not None else time.time()
return endTime - self.startTime
return None
@@ -124,13 +122,12 @@ class Task(object):
class Thread(Task):
def __init__(self):
super(Thread, self).__init__()
super().__init__()
self._thread = None
def start(self):
super(Thread, self).start()
self._thread = threading.Thread(
target=self.protector)
super().start()
self._thread = threading.Thread(target=self.protector)
self._thread.daemon = True
self._thread.start()
self._attachObserver()
@@ -144,8 +141,10 @@ class Thread(Task):
self._thread.join()
signal.notify(self.signalStopping)
signal.notify(self.signalStopped)
thread = threading.Thread(target=waitForStop)
thread.daemon = True
thread.start()
## @}

View File

@@ -36,15 +36,9 @@ import FreeCAD
from femmesh import meshsetsgetter
class FemInputWriter():
class FemInputWriter:
def __init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name=None,
mat_geo_sets=None
self, analysis_obj, solver_obj, mesh_obj, member, dir_name=None, mat_geo_sets=None
):
# class attributes from parameter values
self.analysis = analysis_obj
@@ -73,10 +67,10 @@ class FemInputWriter():
make_tmp_dir = True
if make_tmp_dir is True:
from tempfile import mkdtemp
dir_name = mkdtemp(prefix="fcfem_")
FreeCAD.Console.PrintWarning(
"The working directory '{}' was created and will be used."
.format(dir_name)
f"The working directory '{dir_name}' was created and will be used."
)
self.dir_name = dir_name
@@ -147,12 +141,7 @@ class FemInputWriter():
# ********************************************************************************************
# generic writer for constraints mesh sets and constraints property data
# write constraint node sets, constraint face sets, constraint element sets
def write_constraints_meshsets(
self,
f,
femobjs,
con_module
):
def write_constraints_meshsets(self, f, femobjs, con_module):
if not femobjs:
return
@@ -166,7 +155,7 @@ class FemInputWriter():
for femobj in femobjs:
# femobj --> dict, FreeCAD document object is femobj["Object"]
the_obj = femobj["Object"]
the_file.write("** {}\n".format(the_obj.Label))
the_file.write(f"** {the_obj.Label}\n")
con_module.write_meshdata_constraint(the_file, femobj, the_obj, self)
if write_after != "":
the_file.write(write_after)
@@ -180,8 +169,8 @@ class FemInputWriter():
f.write("** {}\n".format(write_name.replace("_", " ")))
if self.split_inpfile is True:
file_name_split = "{}_{}.inp".format(self.mesh_name, write_name)
f.write("*INCLUDE,INPUT={}\n".format(file_name_split))
file_name_split = f"{self.mesh_name}_{write_name}.inp"
f.write(f"*INCLUDE,INPUT={file_name_split}\n")
inpfile_split = open(join(self.dir_name, file_name_split), "w")
constraint_sets_loop_writing(inpfile_split, femobjs, write_before, write_after)
inpfile_split.close()
@@ -189,12 +178,7 @@ class FemInputWriter():
constraint_sets_loop_writing(f, femobjs, write_before, write_after)
# write constraint property data
def write_constraints_propdata(
self,
f,
femobjs,
con_module
):
def write_constraints_propdata(self, f, femobjs, con_module):
if not femobjs:
return
@@ -208,13 +192,13 @@ class FemInputWriter():
# write constraint to file
f.write("\n{}\n".format(59 * "*"))
f.write("** {}\n".format(con_module.get_constraint_title()))
f.write(f"** {con_module.get_constraint_title()}\n")
if write_before != "":
f.write(write_before)
for femobj in femobjs:
# femobj --> dict, FreeCAD document object is femobj["Object"]
the_obj = femobj["Object"]
f.write("** {}\n".format(the_obj.Label))
f.write(f"** {the_obj.Label}\n")
con_module.write_constraint(f, femobj, the_obj, self)
if write_after != "":
f.write(write_after)

View File

@@ -45,18 +45,16 @@ ANALYSIS_TYPES = ["static"]
def create(doc, name="SolverZ88"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(solverbase.Proxy):
"""The Fem::FemSolver's Proxy python type, add solver specific properties
"""
"""The Fem::FemSolver's Proxy python type, add solver specific properties"""
Type = "Fem::SolverZ88"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super().__init__(obj)
obj.Proxy = self
# z88_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Z88")
@@ -67,39 +65,41 @@ class Proxy(solverbase.Proxy):
def createMachine(self, obj, directory, testmode=False):
return run.Machine(
solver=obj, directory=directory,
solver=obj,
directory=directory,
check=tasks.Check(),
prepare=tasks.Prepare(),
solve=tasks.Solve(),
results=tasks.Results(),
testmode=testmode)
testmode=testmode,
)
def editSupported(self):
return True
def edit(self, directory):
pattern = os.path.join(directory, "z88i1.txt")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
pattern = os.path.join(directory, "z88i2.txt")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
pattern = os.path.join(directory, "z88i5.txt")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
pattern = os.path.join(directory, "z88man.txt")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
pattern = os.path.join(directory, "z88mat.txt")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
pattern = os.path.join(directory, "z88elp.txt")
FreeCAD.Console.PrintMessage("{}\n".format(pattern))
FreeCAD.Console.PrintMessage(f"{pattern}\n")
f = glob.glob(pattern)[0]
FemGui.open(f)
@@ -112,4 +112,5 @@ class ViewProxy(solverbase.ViewProxy):
def getIcon(self):
return ":/icons/FEM_SolverZ88.svg"
## @}

View File

@@ -76,11 +76,7 @@ class Prepare(run.Prepare):
# write solver input
w = writer.FemInputWriterZ88(
self.analysis,
self.solver,
mesh_obj,
meshdatagetter.member,
self.directory
self.analysis, self.solver, mesh_obj, meshdatagetter.member, self.directory
)
path = w.write_solver_input()
# report to user if task succeeded
@@ -111,7 +107,7 @@ class Solve(run.Solve):
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Z88")
solver_index = prefs.GetInt("Solver", 0)
solver_name = SOLVER_TYPES[solver_index]
self.pushStatus("Used solver: {}\n".format(solver_name))
self.pushStatus(f"Used solver: {solver_name}\n")
# run solver test mode
# AFAIK: z88r needs to be run twice
@@ -136,14 +132,14 @@ class Solve(run.Solve):
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
startupinfo=femutils.startProgramInfo(state)
startupinfo=femutils.startProgramInfo(state),
)
else:
self._process = subprocess.Popen(
[binary, command, "-" + solver_name],
cwd=self.directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)
self.signalAbort.add(self._process.terminate)
self._process.communicate()
@@ -155,8 +151,7 @@ class Solve(run.Solve):
class Results(run.Results):
def run(self):
prefs = FreeCAD.ParamGet(
"User parameter:BaseApp/Preferences/Mod/Fem/General")
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
if not prefs.GetBool("KeepResultsOnReRun", False):
self.purge_results()
self.load_results()
@@ -173,18 +168,14 @@ class Results(run.Results):
def load_results(self):
self.pushStatus("Import new results...\n")
# displacements from z88o2 file
disp_result_file = os.path.join(
self.directory, "z88o2.txt")
disp_result_file = os.path.join(self.directory, "z88o2.txt")
if os.path.isfile(disp_result_file):
result_name_prefix = "Z88_" + self.solver.AnalysisType + "_"
importZ88O2Results.import_z88_disp(
disp_result_file, self.analysis, result_name_prefix)
importZ88O2Results.import_z88_disp(disp_result_file, self.analysis, result_name_prefix)
else:
# TODO: use solver framework status message system
FreeCAD.Console.PrintError(
"FEM: No results found at {}!\n"
.format(disp_result_file)
)
FreeCAD.Console.PrintError(f"FEM: No results found at {disp_result_file}!\n")
self.fail()
## @}

View File

@@ -39,21 +39,9 @@ from femmesh import meshtools
class FemInputWriterZ88(writerbase.FemInputWriter):
def __init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name=None
):
def __init__(self, analysis_obj, solver_obj, mesh_obj, member, dir_name=None):
writerbase.FemInputWriter.__init__(
self,
analysis_obj,
solver_obj,
mesh_obj,
member,
dir_name
self, analysis_obj, solver_obj, mesh_obj, member, dir_name
)
self.file_name = join(self.dir_name, "z88")
@@ -63,18 +51,11 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
timestart = time.process_time()
FreeCAD.Console.PrintMessage("\n") # because of time print in separate line
FreeCAD.Console.PrintMessage("Z88 solver input writing...\n")
FreeCAD.Console.PrintLog(
"FemInputWriterZ88 --> self.dir_name --> {}\n"
.format(self.dir_name)
)
FreeCAD.Console.PrintLog(f"FemInputWriterZ88 --> self.dir_name --> {self.dir_name}\n")
FreeCAD.Console.PrintMessage(
"FemInputWriterZ88 --> self.file_name --> {}\n"
.format(self.file_name)
)
FreeCAD.Console.PrintMessage(
"Write z88 input files to: {}\n"
.format(self.dir_name)
f"FemInputWriterZ88 --> self.file_name --> {self.file_name}\n"
)
FreeCAD.Console.PrintMessage(f"Write z88 input files to: {self.dir_name}\n")
control = self.set_z88_elparam()
if control is False:
return None
@@ -86,12 +67,10 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
self.write_z88_integration_properties()
self.write_z88_memory_parameter()
self.write_z88_solver_parameter()
writing_time_string = (
"Writing time input file: {} seconds"
.format(round((time.process_time() - timestart), 2))
writing_time_string = "Writing time input file: {} seconds".format(
round((time.process_time() - timestart), 2)
)
FreeCAD.Console.PrintMessage(
"{}\n\n".format(writing_time_string))
FreeCAD.Console.PrintMessage(f"{writing_time_string}\n\n")
return self.dir_name
# ********************************************************************************************
@@ -107,14 +86,14 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
param = {4: z8804, 24: z8824, 23: z8823, 17: z8817, 16: z8816, 1: z8801, 10: z8810}
# TODO: test elements 17, 16, 10, INTORD etc
self.z88_element_type = importZ88Mesh.get_z88_element_type(
self.femmesh,
self.femelement_table
self.femmesh, self.femelement_table
)
if self.z88_element_type in param:
self.z88_elparam = param[self.z88_element_type]
else:
FreeCAD.Console.PrintError(
"Element type not supported by Z88. Can not write Z88 solver input.\n")
"Element type not supported by Z88. Can not write Z88 solver input.\n"
)
return False
FreeCAD.Console.PrintMessage(self.z88_elparam)
FreeCAD.Console.PrintMessage("\n")
@@ -130,10 +109,7 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
mesh_file_path = self.file_name + "i1.txt"
f = open(mesh_file_path, "w")
importZ88Mesh.write_z88_mesh_to_file(
self.femnodes_mesh,
self.femelement_table,
self.z88_element_type,
f
self.femnodes_mesh, self.femelement_table, self.z88_element_type, f
)
f.close()
@@ -145,9 +121,9 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
# write nodes to constraints_data (different from writing to file in ccxInpWriter
for femobj in self.member.cons_fixed:
for n in femobj["Nodes"]:
constraints_data.append((n, "{} 1 2 0\n".format(n)))
constraints_data.append((n, "{} 2 2 0\n".format(n)))
constraints_data.append((n, "{} 3 2 0\n".format(n)))
constraints_data.append((n, f"{n} 1 2 0\n"))
constraints_data.append((n, f"{n} 2 2 0\n"))
constraints_data.append((n, f"{n} 3 2 0\n"))
# forces constraints
# write node loads to constraints_data
@@ -159,15 +135,15 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
for n in sorted(ref_shape[1]):
# the loads in ref_shape[1][n] are without unit
node_load = ref_shape[1][n]
if (direction_vec.x != 0.0):
if direction_vec.x != 0.0:
v1 = direction_vec.x * node_load
constraints_data.append((n, "{} 1 1 {}\n".format(n, v1)))
if (direction_vec.y != 0.0):
constraints_data.append((n, f"{n} 1 1 {v1}\n"))
if direction_vec.y != 0.0:
v2 = direction_vec.y * node_load
constraints_data.append((n, "{} 2 1 {}\n".format(n, v2)))
if (direction_vec.z != 0.0):
constraints_data.append((n, f"{n} 2 1 {v2}\n"))
if direction_vec.z != 0.0:
v3 = direction_vec.z * node_load
constraints_data.append((n, "{} 3 1 {}\n".format(n, v3)))
constraints_data.append((n, f"{n} 3 1 {v3}\n"))
# write constraints_data to file
constraints_file_path = self.file_name + "i2.txt"
@@ -193,7 +169,7 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
materials_file_path = self.file_name + "mat.txt"
fms = open(materials_file_path, "w")
fms.write("1\n")
fms.write("1 {} {}".format(self.element_count, material_data_file_name))
fms.write(f"1 {self.element_count} {material_data_file_name}")
fms.write("\n")
fms.close()
material_data_file_path = join(self.dir_name, material_data_file_name)
@@ -201,7 +177,7 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
YM = FreeCAD.Units.Quantity(mat_obj.Material["YoungsModulus"])
YM_in_MPa = YM.getValueAs("MPa")
PR = float(mat_obj.Material["PoissonRatio"])
fmd.write("{0} {1:.3f}".format(YM_in_MPa, PR))
fmd.write(f"{YM_in_MPa} {PR:.3f}")
fmd.write("\n")
fmd.close()
@@ -219,35 +195,24 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
elif beam_obj.SectionType == "Circular":
diameter = beam_obj.CircDiameter.getValueAs("mm").Value
from math import pi
area = 0.25 * pi * diameter * diameter
else:
FreeCAD.Console.PrintError(
"Cross section type {} not supported, "
"cross section area will be 0 in solver input.\n"
.format(beam_obj.SectionType)
"cross section area will be 0 in solver input.\n".format(beam_obj.SectionType)
)
# TODO make the check in prechecks and delete it here
# no extensive errorhandling in writer
# this way the solver will fail and an exception is raised somehow
elements_data.append(
"1 {} {} 0 0 0 0 0 0 "
.format(self.element_count, area)
)
FreeCAD.Console.PrintWarning(
"Be aware, only trusses are supported for edge meshes!\n"
)
elements_data.append(f"1 {self.element_count} {area} 0 0 0 0 0 0 ")
FreeCAD.Console.PrintWarning("Be aware, only trusses are supported for edge meshes!\n")
elif meshtools.is_face_femmesh(self.femmesh):
thick_obj = self.member.geos_shellthickness[0]["Object"]
thickness = thick_obj.Thickness.getValueAs("mm").Value
elements_data.append(
"1 {} {} 0 0 0 0 0 0 "
.format(self.element_count, thickness)
)
elements_data.append(f"1 {self.element_count} {thickness} 0 0 0 0 0 0 ")
elif meshtools.is_solid_femmesh(self.femmesh):
elements_data.append(
"1 {} 0 0 0 0 0 0 0"
.format(self.element_count)
)
elements_data.append(f"1 {self.element_count} 0 0 0 0 0 0 0")
else:
FreeCAD.Console.PrintError("Error!\n")
f = open(element_properties_file_path, "w")
@@ -260,14 +225,14 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
# ********************************************************************************************
def write_z88_integration_properties(self):
integration_data = []
integration_data.append("1 {} {} {}".format(
self.element_count,
self.z88_elparam["INTORD"],
self.z88_elparam["INTOS"]
))
integration_data.append(
"1 {} {} {}".format(
self.element_count, self.z88_elparam["INTORD"], self.z88_elparam["INTOS"]
)
)
integration_properties_file_path = self.file_name + "int.txt"
f = open(integration_properties_file_path, "w")
f.write("{}\n".format(len(integration_data)))
f.write(f"{len(integration_data)}\n")
for i in integration_data:
f.write(i)
f.write("\n")
@@ -297,9 +262,9 @@ class FemInputWriterZ88(writerbase.FemInputWriter):
output = ""
for line in template_array:
if line.find("MAXGS") > -1:
line = " MAXGS {}".format(MaxGS)
line = f" MAXGS {MaxGS}"
if line.find("MAXKOI") > -1:
line = " MAXKOI {}".format(MaxKOI)
line = f" MAXKOI {MaxKOI}"
output += line + "\n"
solver_parameter_file_path = self.file_name + ".dyn"