Merge branch 'master' into bugfix/dogbone-on-straight-edges-and-noop-moves
This commit is contained in:
@@ -45,19 +45,19 @@ def init_doc(doc=None):
|
||||
|
||||
|
||||
def get_information():
|
||||
info = {"name": "Box Analysis Static",
|
||||
"meshtype": "solid",
|
||||
"meshelement": "Tet10",
|
||||
"constraints": [],
|
||||
"solvers": ["calculix", "elmer"],
|
||||
"material": "solid",
|
||||
"equation": "elasticity"
|
||||
}
|
||||
return info
|
||||
return {
|
||||
"name": "NonGui Tutorial 01 - Eigenvalue of elastic beam",
|
||||
"meshtype": "solid",
|
||||
"meshelement": "Tet10",
|
||||
"constraints": [],
|
||||
"solvers": ["calculix", "ccxtools", "elmer"],
|
||||
"material": "solid",
|
||||
"equation": "elasticity" # "frequency", but list not allowed here
|
||||
}
|
||||
|
||||
|
||||
def setup_base(doc=None, solvertype="ccxtools"):
|
||||
# setup box base model
|
||||
def setup(doc=None, solvertype="elmer"):
|
||||
# setup model
|
||||
|
||||
if doc is None:
|
||||
doc = init_doc()
|
||||
@@ -76,6 +76,41 @@ def setup_base(doc=None, solvertype="ccxtools"):
|
||||
# analysis
|
||||
analysis = ObjectsFem.makeAnalysis(doc, "Analysis")
|
||||
|
||||
# solver
|
||||
if solvertype == "calculix":
|
||||
solver_object = analysis.addObject(
|
||||
ObjectsFem.makeSolverCalculix(doc, "SolverCalculiX")
|
||||
)[0]
|
||||
elif solvertype == "ccxtools":
|
||||
solver_object = analysis.addObject(
|
||||
ObjectsFem.makeSolverCalculixCcxTools(doc, "CalculiXccxTools")
|
||||
)[0]
|
||||
solver_object.WorkingDir = u""
|
||||
elif solvertype == "elmer":
|
||||
solver_object = analysis.addObject(
|
||||
ObjectsFem.makeSolverElmer(doc, "SolverElmer")
|
||||
)[0]
|
||||
eq_obj = ObjectsFem.makeEquationElasticity(doc, solver_object)
|
||||
eq_obj.LinearSolverType = "Direct"
|
||||
# direct solver was used in the turorial, thus used here too
|
||||
# the iterative is much faster and gives the same results
|
||||
eq_obj.DoFrequencyAnalysis = True
|
||||
eq_obj.CalculateStresses = True
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Not known or not supported solver type: {}. "
|
||||
"No solver object was created.\n".format(solvertype)
|
||||
)
|
||||
if solvertype == "calculix" or solvertype == "ccxtools":
|
||||
solver_object.AnalysisType = "frequency"
|
||||
solver_object.GeometricalNonlinearity = "linear"
|
||||
solver_object.ThermoMechSteadyState = False
|
||||
solver_object.MatrixSolverType = "default"
|
||||
solver_object.IterationsControlParameterTimeUse = False
|
||||
solver_object.EigenmodesCount = 5
|
||||
solver_object.EigenmodeHighLimit = 1000000.0
|
||||
solver_object.EigenmodeLowLimit = 0.01
|
||||
|
||||
# material
|
||||
material_object = analysis.addObject(
|
||||
ObjectsFem.makeMaterialSolid(doc, "MechanicalMaterial")
|
||||
@@ -87,6 +122,15 @@ def setup_base(doc=None, solvertype="ccxtools"):
|
||||
mat["Density"] = "2330 kg/m^3"
|
||||
material_object.Material = mat
|
||||
|
||||
# fixed_constraint
|
||||
fixed_constraint = analysis.addObject(
|
||||
ObjectsFem.makeConstraintFixed(doc, name="FemConstraintFixed")
|
||||
)[0]
|
||||
fixed_constraint.References = [
|
||||
(geom_obj, "Face1"),
|
||||
(geom_obj, "Face2")
|
||||
]
|
||||
|
||||
# mesh
|
||||
from .meshes.mesh_eigenvalue_of_elastic_beam_tetra10 import create_nodes
|
||||
from .meshes.mesh_eigenvalue_of_elastic_beam_tetra10 import create_elements
|
||||
@@ -106,41 +150,3 @@ def setup_base(doc=None, solvertype="ccxtools"):
|
||||
|
||||
doc.recompute()
|
||||
return doc
|
||||
|
||||
|
||||
def setup(doc=None, solvertype="elmer"):
|
||||
# setup box static, add a fixed, force and a pressure constraint
|
||||
|
||||
doc = setup_base(doc, solvertype)
|
||||
analysis = doc.Analysis
|
||||
|
||||
# solver
|
||||
if solvertype == "calculix":
|
||||
solver_object = analysis.addObject(
|
||||
ObjectsFem.makeSolverCalculix(doc, "SolverCalculiX")
|
||||
)[0]
|
||||
elif solvertype == "ccxtools":
|
||||
solver_object = analysis.addObject(
|
||||
ObjectsFem.makeSolverCalculixCcxTools(doc, "CalculiXccxTools")
|
||||
)[0]
|
||||
solver_object.WorkingDir = u""
|
||||
elif solvertype == "elmer":
|
||||
solver_object = analysis.addObject(
|
||||
ObjectsFem.makeSolverElmer(doc, "SolverElmer")
|
||||
)[0]
|
||||
ObjectsFem.makeEquationElasticity(doc, solver_object)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Not known or not supported solver type: {}. "
|
||||
"No solver object was created.\n".format(solvertype)
|
||||
)
|
||||
if solvertype == "calculix" or solvertype == "ccxtools":
|
||||
solver_object.SplitInputWriter = False
|
||||
solver_object.AnalysisType = "static"
|
||||
solver_object.GeometricalNonlinearity = "linear"
|
||||
solver_object.ThermoMechSteadyState = False
|
||||
solver_object.MatrixSolverType = "default"
|
||||
solver_object.IterationsControlParameterTimeUse = False
|
||||
|
||||
doc.recompute()
|
||||
return doc
|
||||
|
||||
@@ -370,32 +370,36 @@ class GmshTools():
|
||||
def get_gmsh_version(self):
|
||||
self.get_gmsh_command()
|
||||
if os.path.exists(self.gmsh_bin):
|
||||
found_message = "executable found: " + self.gmsh_bin
|
||||
found_message = "file found: " + self.gmsh_bin
|
||||
Console.PrintMessage(found_message + "\n")
|
||||
else:
|
||||
found_message = "executable not found: " + self.gmsh_bin
|
||||
found_message = "file not found: " + self.gmsh_bin
|
||||
Console.PrintError(found_message + "\n")
|
||||
return (None, None, None), found_message
|
||||
|
||||
command_list = [self.gmsh_bin, "--info"]
|
||||
p = subprocess.Popen(
|
||||
command_list,
|
||||
shell=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
gmsh_stdout, gmsh_stderr = p.communicate()
|
||||
try:
|
||||
p = subprocess.Popen(
|
||||
command_list,
|
||||
shell=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
except Exception as e:
|
||||
Console.PrintMessage(str(e) + "\n")
|
||||
return (None, None, None), found_message + "\n\n" + "Error: " + str(e)
|
||||
|
||||
gmsh_stdout, gmsh_stderr = p.communicate()
|
||||
Console.PrintMessage("Gmsh: StdOut:\n" + gmsh_stdout + "\n")
|
||||
if gmsh_stderr:
|
||||
Console.PrintError("Gmsh: StdErr:\n" + gmsh_stderr + "\n")
|
||||
|
||||
match = re.search("^Version\s*:\s*(\d+)\.(\d+)\.(\d+)", gmsh_stdout)
|
||||
if match:
|
||||
return match.group(1, 2, 3), found_message + "\n\n" + gmsh_stdout # major, minor, patch, fullmessage
|
||||
return match.group(1, 2, 3), found_message + "\n\n" + gmsh_stdout # (major, minor, patch), fullmessage
|
||||
else:
|
||||
return (None, None, None), found_message + "\n\n" + gmsh_stdout
|
||||
return (None, None, None), found_message + "\n\n" + "Warning: Output not recognized\n\n" + gmsh_stdout
|
||||
|
||||
def get_region_data(self):
|
||||
# mesh regions
|
||||
|
||||
@@ -636,11 +636,24 @@ class Writer(object):
|
||||
return None
|
||||
|
||||
def _handleElasticityMaterial(self, bodies):
|
||||
# density
|
||||
# is needed for self weight constraints and frequency analysis
|
||||
density_needed = False
|
||||
for equation in self.solver.Group:
|
||||
if femutils.is_of_type(equation, "Fem::EquationElmerElasticity"):
|
||||
if equation.DoFrequencyAnalysis is True:
|
||||
density_needed = True
|
||||
break # there could be a second equation without frequency
|
||||
gravObj = self._getSingleMember("Fem::ConstraintSelfWeight")
|
||||
if gravObj is not None:
|
||||
density_needed = True
|
||||
# temperature
|
||||
tempObj = self._getSingleMember("Fem::ConstraintInitialTemperature")
|
||||
if tempObj is not None:
|
||||
refTemp = self._getFromUi(tempObj.initialTemperature, "K", "O")
|
||||
for name in bodies:
|
||||
self._material(name, "Reference Temperature", refTemp)
|
||||
# get the material data for all boddies
|
||||
for obj in self._getMember("App::MaterialObject"):
|
||||
m = obj.Material
|
||||
refs = (
|
||||
@@ -649,12 +662,11 @@ class Writer(object):
|
||||
else self._getAllBodies()
|
||||
)
|
||||
for name in (n for n in refs if n in bodies):
|
||||
# density has to be written even without self weight constraint
|
||||
# https://forum.freecadweb.org/viewtopic.php?f=18&t=56590#p487117
|
||||
self._material(
|
||||
name, "Density",
|
||||
self._getDensity(m)
|
||||
)
|
||||
if density_needed is True:
|
||||
self._material(
|
||||
name, "Density",
|
||||
self._getDensity(m)
|
||||
)
|
||||
self._material(
|
||||
name, "Youngs Modulus",
|
||||
self._getYoungsModulus(m)
|
||||
|
||||
@@ -171,7 +171,11 @@ class _TaskPanel:
|
||||
def get_gmsh_version(self):
|
||||
from femmesh import gmshtools
|
||||
version, full_message = gmshtools.GmshTools(self.mesh_obj, self.analysis).get_gmsh_version()
|
||||
QtGui.QMessageBox.information(
|
||||
if version[0] and version[1] and version[2]:
|
||||
messagebox = QtGui.QMessageBox.information
|
||||
else:
|
||||
messagebox = QtGui.QMessageBox.warning
|
||||
messagebox(
|
||||
None,
|
||||
"Gmsh - Information",
|
||||
full_message
|
||||
|
||||
@@ -53,7 +53,6 @@ Body 1
|
||||
End
|
||||
|
||||
Material 1
|
||||
Density = Real 7.9e-06
|
||||
Poisson ratio = Real 0.3
|
||||
Youngs Modulus = Real 200000000.0
|
||||
End
|
||||
|
||||
@@ -53,7 +53,6 @@ Body 1
|
||||
End
|
||||
|
||||
Material 1
|
||||
Density = Real 7.9e-06
|
||||
Poisson ratio = Real 0.3
|
||||
Youngs Modulus = Real 210000000.0
|
||||
End
|
||||
|
||||
@@ -54,7 +54,6 @@ Body 1
|
||||
End
|
||||
|
||||
Material 1
|
||||
Density = Real 7900.0
|
||||
Poisson ratio = Real 0.3
|
||||
Youngs Modulus = Real 210000000000.0
|
||||
End
|
||||
|
||||
@@ -53,7 +53,6 @@ Body 1
|
||||
End
|
||||
|
||||
Material 1
|
||||
Density = Real 7.9e-06
|
||||
Poisson ratio = Real 0.3
|
||||
Youngs Modulus = Real 210000000.0
|
||||
End
|
||||
|
||||
@@ -53,7 +53,6 @@ Body 1
|
||||
End
|
||||
|
||||
Material 1
|
||||
Density = Real 7.9e-06
|
||||
Poisson ratio = Real 0.3
|
||||
Youngs Modulus = Real 210000000.0
|
||||
End
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_sphere(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "sphere.scad"
|
||||
filename = temp_dir + os.path.sep + "sphere.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("sphere(10.0);")
|
||||
f.close()
|
||||
@@ -86,7 +86,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_cylinder(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "cylinder.scad"
|
||||
filename = temp_dir + os.path.sep + "cylinder.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("cylinder(50.0,d=10.0);")
|
||||
f.close()
|
||||
@@ -99,7 +99,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_cube(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "cube.scad"
|
||||
filename = temp_dir + os.path.sep + "cube.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("cube([1.0,2.0,3.0]);")
|
||||
f.close()
|
||||
@@ -113,7 +113,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_circle(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "circle.scad"
|
||||
filename = temp_dir + os.path.sep + "circle.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("circle(10.0);")
|
||||
f.close()
|
||||
@@ -125,7 +125,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_square(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "square.scad"
|
||||
filename = temp_dir + os.path.sep + "square.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("square([1.0,2.0]);")
|
||||
f.close()
|
||||
@@ -138,7 +138,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_text(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "text.scad"
|
||||
filename = temp_dir + os.path.sep + "text.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("text(\"X\");") # Keep it short to keep the test fast-ish
|
||||
f.close()
|
||||
@@ -152,7 +152,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_polygon_nopath(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "polygon_nopath.scad"
|
||||
filename = temp_dir + os.path.sep + "polygon_nopath.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("polygon(points=[[0,0],[100,0],[130,50],[30,50]]);")
|
||||
f.close()
|
||||
@@ -164,7 +164,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_polygon_path(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "polygon_path.scad"
|
||||
filename = temp_dir + os.path.sep + "polygon_path.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write("polygon([[0,0],[100,0],[130,50],[30,50]], paths=[[0,1,2,3]]);")
|
||||
f.close()
|
||||
@@ -176,7 +176,7 @@ class TestImportCSG(unittest.TestCase):
|
||||
|
||||
def test_import_polyhedron(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + "polyhedron.scad"
|
||||
filename = temp_dir + os.path.sep + "polyhedron.scad"
|
||||
f = open(filename,"w+")
|
||||
f.write(
|
||||
"""
|
||||
@@ -197,7 +197,7 @@ polyhedron(
|
||||
|
||||
def utility_create_scad(self, scadCode, name):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
filename = temp_dir + os.pathsep + name + ".scad"
|
||||
filename = temp_dir + os.path.sep + name + ".scad"
|
||||
f = open(filename,"w+")
|
||||
f.write(scadCode)
|
||||
f.close()
|
||||
@@ -313,38 +313,48 @@ polyhedron(
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
def test_import_surface(self):
|
||||
testfile = join(self.test_dir, "Surface.dat").replace('\\','/')
|
||||
doc = self.utility_create_scad(f"surface(file = \"{testfile}\", center = true, convexity = 5);", "surface_simple_dat")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 275.000000, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMin, -4.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMax, 4.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMin, -4.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMax, 4.5, 6)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
testfile = join(self.test_dir, "Surface.dat").replace('\\','/')
|
||||
doc = self.utility_create_scad(f"surface(file = \"{testfile}\", convexity = 5);", "surface_uncentered_dat")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 275.000000, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMin, 0, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMax, 9, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMin, 0, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMax, 9, 6)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
# Workaround for absolute vs. relative path issue
|
||||
# Inside the OpenSCAD file an absolute path name to Surface.dat is used
|
||||
# but by using the OpenSCAD executable to create a CSG file it's converted
|
||||
# into a path name relative to the output filename.
|
||||
# In order to open the CAG file correctly the cwd must be temporarily changed
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
cwd = os.getcwd()
|
||||
os.chdir(temp_dir)
|
||||
|
||||
testfile = join(self.test_dir, "Surface2.dat").replace('\\','/')
|
||||
doc = self.utility_create_scad(f"surface(file = \"{testfile}\", center = true, convexity = 5);", "surface_rectangular_dat")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 24.5500000, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMin, -2, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMax, 2, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMin, -1.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMax, 1.5, 6)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
testfile = join(self.test_dir, "Surface.dat").replace('\\','/')
|
||||
doc = self.utility_create_scad(f"surface(file = \"{testfile}\", center = true, convexity = 5);", "surface_simple_dat")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 275.000000, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMin, -4.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMax, 4.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMin, -4.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMax, 4.5, 6)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
testfile = join(self.test_dir, "Surface.dat").replace('\\','/')
|
||||
doc = self.utility_create_scad(f"surface(file = \"{testfile}\", convexity = 5);", "surface_uncentered_dat")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 275.000000, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMin, 0, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMax, 9, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMin, 0, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMax, 9, 6)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
testfile = join(self.test_dir, "Surface2.dat").replace('\\','/')
|
||||
doc = self.utility_create_scad(f"surface(file = \"{testfile}\", center = true, convexity = 5);", "surface_rectangular_dat")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 24.5500000, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMin, -2, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.XMax, 2, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMin, -1.5, 6)
|
||||
self.assertAlmostEqual (object.Shape.BoundBox.YMax, 1.5, 6)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
os.chdir(cwd)
|
||||
|
||||
def test_import_projection(self):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user