diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt
index da61e3e816..d0847093b6 100755
--- a/src/Mod/Fem/CMakeLists.txt
+++ b/src/Mod/Fem/CMakeLists.txt
@@ -646,6 +646,11 @@ SET(FemGuiViewProvider_SRCS
femviewprovider/view_solver_ccxtools.py
)
+SET(FemGuiPreferencePages_SRCS
+ fempreferencepages/__init__.py
+ fempreferencepages/dlg_settings_netgen.py
+)
+
SET(FemAllGuiScripts
${FemGuiBaseModules_SRCS}
${FemGuiObjects_SRCS}
@@ -653,6 +658,7 @@ SET(FemAllGuiScripts
${FemGuiTests_SRCS}
${FemGuiUtils_SRCS}
${FemGuiViewProvider_SRCS}
+ ${FemGuiPreferencePages_SRCS}
)
if(BUILD_GUI)
@@ -669,4 +675,5 @@ if(BUILD_GUI)
INSTALL(FILES ${FemGuiTests_SRCS} DESTINATION Mod/Fem/femtest/gui/)
INSTALL(FILES ${FemGuiUtils_SRCS} DESTINATION Mod/Fem/femguiutils/)
INSTALL(FILES ${FemGuiViewProvider_SRCS} DESTINATION Mod/Fem/femviewprovider/)
+ INSTALL(FILES ${FemGuiPreferencePages_SRCS} DESTINATION Mod/Fem/fempreferencepages/)
endif(BUILD_GUI)
diff --git a/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui b/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui
index 9289ba959a..bde7af6c61 100644
--- a/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui
+++ b/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui
@@ -301,10 +301,10 @@
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- Set to zero to automatically use maximum number of available cores
+ Number of threads used for analysis
- 0
+ 1
AnalysisNumCPUs
diff --git a/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp b/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp
index bea9df709f..e70b285702 100644
--- a/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp
+++ b/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp
@@ -45,9 +45,6 @@ DlgSettingsFemCcxImp::DlgSettingsFemCcxImp(QWidget* parent)
// set ranges
ui->dsb_ccx_analysis_time->setMaximum(FLOAT_MAX);
ui->dsb_ccx_initial_time_step->setMaximum(FLOAT_MAX);
- // determine number of CPU cores
- int processor_count = QThread::idealThreadCount();
- ui->sb_ccx_numcpu->setMaximum(processor_count);
connect(ui->fc_ccx_binary_path,
&Gui::PrefFileChooser::fileNameChanged,
@@ -117,6 +114,11 @@ void DlgSettingsFemCcxImp::loadSettings()
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Fem/Ccx");
+
+ // determine number of CPU threads
+ int processor_count = hGrp->GetInt("AnalysisNumCPUs", QThread::idealThreadCount());
+ ui->sb_ccx_numcpu->setValue(processor_count);
+
int index = hGrp->GetInt("Solver", 0);
if (index > -1) {
ui->cmb_solver->setCurrentIndex(index);
diff --git a/src/Mod/Fem/Gui/DlgSettingsFemGmsh.ui b/src/Mod/Fem/Gui/DlgSettingsFemGmsh.ui
index e6981dc9f2..eceddf9a99 100644
--- a/src/Mod/Fem/Gui/DlgSettingsFemGmsh.ui
+++ b/src/Mod/Fem/Gui/DlgSettingsFemGmsh.ui
@@ -151,6 +151,32 @@
+ -
+
+
+ Number of threads
+
+
+
+ -
+
+
+ Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ Number of threads used for meshing
+
+
+ 1
+
+
+ NumOfThreads
+
+
+ Mod/Fem/Gmsh
+
+
+
@@ -193,6 +219,11 @@
QComboBox
+
+ Gui::PrefSpinBox
+ QSpinBox
+
+
diff --git a/src/Mod/Fem/Gui/DlgSettingsFemGmshImp.cpp b/src/Mod/Fem/Gui/DlgSettingsFemGmshImp.cpp
index 3c0f8b6d91..c5b789f117 100644
--- a/src/Mod/Fem/Gui/DlgSettingsFemGmshImp.cpp
+++ b/src/Mod/Fem/Gui/DlgSettingsFemGmshImp.cpp
@@ -25,6 +25,7 @@
#include "PreCompiled.h"
#ifndef _PreComp_
#include
+#include
#endif
#include
@@ -53,6 +54,7 @@ void DlgSettingsFemGmshImp::saveSettings()
ui->cb_gmsh_binary_std->onSave();
ui->fc_gmsh_binary_path->onSave();
ui->cb_log_verbosity->onSave();
+ ui->sb_threads->onSave();
}
void DlgSettingsFemGmshImp::loadSettings()
@@ -60,6 +62,11 @@ void DlgSettingsFemGmshImp::loadSettings()
ui->cb_gmsh_binary_std->onRestore();
ui->fc_gmsh_binary_path->onRestore();
+ ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
+ "User parameter:BaseApp/Preferences/Mod/Fem/Gmsh");
+ // determine number of CPU threads
+ ui->sb_threads->setValue(hGrp->GetInt("NumOfThreads", QThread::idealThreadCount()));
+
populateLogVerbosity();
ui->cb_log_verbosity->onRestore();
}
diff --git a/src/Mod/Fem/Gui/Resources/ui/DlgSettingsNetgen.ui b/src/Mod/Fem/Gui/Resources/ui/DlgSettingsNetgen.ui
index d803a33c5d..95d00eb5dc 100644
--- a/src/Mod/Fem/Gui/Resources/ui/DlgSettingsNetgen.ui
+++ b/src/Mod/Fem/Gui/Resources/ui/DlgSettingsNetgen.ui
@@ -1,7 +1,7 @@
- Gui::Dialog::DlgSettingsNetgen
-
+ FemGui::DlgSettingsNetgen
+
0
@@ -45,6 +45,71 @@
+ -
+
+
+ Options
+
+
+
-
+
+
-
+
+
+ Log verbosity
+
+
+
+ -
+
+
+ Level of verbosity printed on the task panel
+
+
+ QComboBox::AdjustToContents
+
+
+ LogVerbosity
+
+
+ Mod/Fem/Netgen
+
+
+
+
+
+
+ -
+
+
+ Number of threads
+
+
+
+ -
+
+
+ Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ Number of threads used for meshing
+
+
+ 1
+
+
+ NumOfThreads
+
+
+ Mod/Fem/Netgen
+
+
+
+
+
+
+
+
-
@@ -60,6 +125,11 @@
QCheckBox
+
+ Gui::PrefSpinBox
+ QSpinBox
+
+
diff --git a/src/Mod/Fem/InitGui.py b/src/Mod/Fem/InitGui.py
index 0ec9b478bf..621a278f17 100644
--- a/src/Mod/Fem/InitGui.py
+++ b/src/Mod/Fem/InitGui.py
@@ -71,8 +71,9 @@ class FemWorkbench(Workbench):
import Fem
import FemGui
import femcommands.commands
+ import fempreferencepages
- FreeCADGui.addPreferencePage(":/ui/DlgSettingsNetgen.ui", "FEM")
+ FreeCADGui.addPreferencePage(fempreferencepages.DlgSettingsNetgen, "FEM")
# dummy usage to get flake8 and lgtm quiet
False if Fem.__name__ else True
diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py
index 33093165b2..1ea5a11813 100644
--- a/src/Mod/Fem/femmesh/gmshtools.py
+++ b/src/Mod/Fem/femmesh/gmshtools.py
@@ -30,7 +30,7 @@ __url__ = "https://www.freecad.org"
import os
import re
import subprocess
-from PySide.QtCore import QProcess
+from PySide.QtCore import QProcess, QThread
import FreeCAD
from FreeCAD import Console
@@ -737,11 +737,12 @@ class GmshTools:
geo.write("// geo file for meshing with Gmsh meshing software created by FreeCAD\n")
geo.write("\n")
- cpu_count = os.cpu_count()
- if cpu_count is not None and cpu_count > 1:
- geo.write("// enable multi-core processing\n")
- geo.write(f"General.NumThreads = {cpu_count};\n")
- geo.write("\n")
+ cpu_count = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Gmsh").GetInt(
+ "NumOfThreads", QThread.idealThreadCount()
+ )
+ geo.write("// enable multi-core processing\n")
+ geo.write(f"General.NumThreads = {cpu_count};\n")
+ geo.write("\n")
geo.write("// open brep geometry\n")
# explicit use double quotes in geo file
diff --git a/src/Mod/Fem/femmesh/netgentools.py b/src/Mod/Fem/femmesh/netgentools.py
index f63a3a2f6e..488f996771 100644
--- a/src/Mod/Fem/femmesh/netgentools.py
+++ b/src/Mod/Fem/femmesh/netgentools.py
@@ -29,10 +29,11 @@ import numpy as np
import shutil
import sys
import tempfile
-from PySide.QtCore import QProcess
+from PySide.QtCore import QProcess, QThread
import FreeCAD
import Fem
+from freecad import utils
try:
from netgen import occ, meshing, config as ng_config
@@ -83,6 +84,7 @@ class NetgenTools:
self.tmpdir = ""
self.process = QProcess()
self.mesh_params = {}
+ self.param_grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Netgen")
def write_geom(self):
if not self.tmpdir:
@@ -95,136 +97,152 @@ class NetgenTools:
geom_trans.Placement = global_pla
self.brep_file = self.tmpdir + "/shape.brep"
self.result_file = self.tmpdir + "/result.npy"
+ self.script_file = self.tmpdir + "/code.py"
geom_trans.exportBrep(self.brep_file)
- code = """
-from femmesh.netgentools import NetgenTools
-
-NetgenTools.run_netgen(**{params})
-"""
-
def prepare(self):
self.write_geom()
self.mesh_params = {
"brep_file": self.brep_file,
- "threads": self.obj.Threads,
+ "threads": self.param_grp.GetInt("NumOfThreads", QThread.idealThreadCount()),
"heal": self.obj.HealShape,
"params": self.get_meshing_parameters(),
"second_order": self.obj.SecondOrder,
"second_order_linear": self.obj.SecondOrderLinear,
"result_file": self.result_file,
"mesh_region": self.get_mesh_region(),
+ "verbosity": self.param_grp.GetInt("LogVerbosity", 2),
}
+ with open(self.script_file, "w") as file:
+ file.write(
+ self.code.format(
+ kwds=self.mesh_params,
+ order_face=NetgenTools.order_face,
+ order_volume=NetgenTools.order_volume,
+ )
+ )
+
def compute(self):
- code_str = self.code.format(params=self.mesh_params)
- self.process.start(sys.executable, ["-c", code_str])
+ self.process.start(utils.get_python_exe(), [self.script_file])
return self.process
- @staticmethod
- def run_netgen(
- brep_file,
- threads,
- heal,
- params,
- second_order,
- second_order_linear,
- result_file,
- mesh_region,
- ):
- geom = occ.OCCGeometry(brep_file)
- ngcore.SetNumThreads(threads)
+ code = """
+from netgen import occ, meshing
+import pyngcore as ngcore
+import numpy as np
- shape = geom.shape
- for items, l in mesh_region:
- for t, n in items:
- if t == "Vertex":
- shape.vertices.vertices[n - 1].maxh = l
- elif t == "Edge":
- shape.edges.edges[n - 1].maxh = l
- elif t == "Face":
- shape.faces.faces[n - 1].maxh = l
- elif t == "Solid":
- shape.solids.solids[n - 1].maxh = l
+order_face = {order_face}
+order_volume = {order_volume}
- with ngcore.TaskManager():
- geom = occ.OCCGeometry(shape)
- if heal:
- geom.Heal()
- mesh = geom.GenerateMesh(mp=meshing.MeshingParameters(**params))
+def run_netgen(
+ brep_file,
+ threads,
+ heal,
+ params,
+ second_order,
+ second_order_linear,
+ result_file,
+ mesh_region,
+ verbosity,
+):
+ geom = occ.OCCGeometry(brep_file)
+ ngcore.SetNumThreads(threads)
- result = {
- "coords": [],
- "Edges": [[], []],
- "Faces": [[], []],
- "Volumes": [[], []],
- }
- groups = {"Edges": [], "Faces": [], "Solids": []}
+ shape = geom.shape
+ for items, l in mesh_region:
+ for t, n in items:
+ if t == "Vertex":
+ shape.vertices.vertices[n - 1].maxh = l
+ elif t == "Edge":
+ shape.edges.edges[n - 1].maxh = l
+ elif t == "Face":
+ shape.faces.faces[n - 1].maxh = l
+ elif t == "Solid":
+ shape.solids.solids[n - 1].maxh = l
- # save empty data if last step is geometry analysis
- if params["perfstepsend"] == NetgenTools.meshing_step["AnalyzeGeometry"]:
- np.save(result_file, [result, groups])
- return None
+ with ngcore.TaskManager():
+ meshing.SetMessageImportance(verbosity)
+ geom = occ.OCCGeometry(shape)
+ if heal:
+ geom.Heal()
+ mesh = geom.GenerateMesh(mp=meshing.MeshingParameters(**params))
- if second_order:
- if second_order_linear:
- mesh.SetGeometry(None)
- mesh.SecondOrder()
-
- coords = mesh.Coordinates()
-
- edges = mesh.Elements1D().NumPy()
- faces = mesh.Elements2D().NumPy()
- volumes = mesh.Elements3D().NumPy()
-
- nod_edges = edges["nodes"]
- nod_faces = faces["nodes"]
- nod_volumes = volumes["nodes"]
-
- np_edges = (nod_edges != 0).sum(axis=1).tolist()
- np_faces = faces["np"].tolist()
- np_volumes = volumes["np"].tolist()
-
- # set smesh node order
- for i in range(faces.size):
- nod_faces[i] = nod_faces[i][NetgenTools.order_face[np_faces[i]]]
-
- for i in range(volumes.size):
- nod_volumes[i] = nod_volumes[i][NetgenTools.order_volume[np_volumes[i]]]
-
- flat_edges = nod_edges[nod_edges != 0].tolist()
- flat_faces = nod_faces[nod_faces != 0].tolist()
- flat_volumes = nod_volumes[nod_volumes != 0].tolist()
-
- result = {
- "coords": coords,
- "Edges": [flat_edges, np_edges],
- "Faces": [flat_faces, np_faces],
- "Volumes": [flat_volumes, np_volumes],
- }
-
- # create groups
- nb_edges = edges.size
- nb_faces = faces.size
- nb_volumes = volumes.size
-
- idx_edges = edges["index"]
- idx_faces = faces["index"]
- idx_volumes = volumes["index"]
-
- for i in np.unique(idx_edges):
- edge_i = (np.nonzero(idx_edges == i)[0] + 1).tolist()
- groups["Edges"].append([i, edge_i])
- for i in np.unique(idx_faces):
- face_i = (np.nonzero(idx_faces == i)[0] + nb_edges + 1).tolist()
- groups["Faces"].append([i, face_i])
-
- for i in np.unique(idx_volumes):
- volume_i = (np.nonzero(idx_volumes == i)[0] + nb_edges + nb_faces + 1).tolist()
- groups["Solids"].append([i, volume_i])
+ result = {{
+ "coords": [],
+ "Edges": [[], []],
+ "Faces": [[], []],
+ "Volumes": [[], []],
+ }}
+ groups = {{"Edges": [], "Faces": [], "Solids": []}}
+ # save empty data if last step is geometry analysis
+ if params["perfstepsend"] == 1:
np.save(result_file, [result, groups])
+ return None
+
+ if second_order:
+ if second_order_linear:
+ mesh.SetGeometry(None)
+ mesh.SecondOrder()
+
+ coords = mesh.Coordinates()
+
+ edges = mesh.Elements1D().NumPy()
+ faces = mesh.Elements2D().NumPy()
+ volumes = mesh.Elements3D().NumPy()
+
+ nod_edges = edges["nodes"]
+ nod_faces = faces["nodes"]
+ nod_volumes = volumes["nodes"]
+
+ np_edges = (nod_edges != 0).sum(axis=1).tolist()
+ np_faces = faces["np"].tolist()
+ np_volumes = volumes["np"].tolist()
+
+ # set smesh node order
+ for i in range(faces.size):
+ nod_faces[i] = nod_faces[i][order_face[np_faces[i]]]
+
+ for i in range(volumes.size):
+ nod_volumes[i] = nod_volumes[i][order_volume[np_volumes[i]]]
+
+ flat_edges = nod_edges[nod_edges != 0].tolist()
+ flat_faces = nod_faces[nod_faces != 0].tolist()
+ flat_volumes = nod_volumes[nod_volumes != 0].tolist()
+
+ result = {{
+ "coords": coords,
+ "Edges": [flat_edges, np_edges],
+ "Faces": [flat_faces, np_faces],
+ "Volumes": [flat_volumes, np_volumes],
+ }}
+
+ # create groups
+ nb_edges = edges.size
+ nb_faces = faces.size
+ nb_volumes = volumes.size
+
+ idx_edges = edges["index"]
+ idx_faces = faces["index"]
+ idx_volumes = volumes["index"]
+
+ for i in np.unique(idx_edges):
+ edge_i = (np.nonzero(idx_edges == i)[0] + 1).tolist()
+ groups["Edges"].append([i, edge_i])
+ for i in np.unique(idx_faces):
+ face_i = (np.nonzero(idx_faces == i)[0] + nb_edges + 1).tolist()
+ groups["Faces"].append([i, face_i])
+
+ for i in np.unique(idx_volumes):
+ volume_i = (np.nonzero(idx_volumes == i)[0] + nb_edges + nb_faces + 1).tolist()
+ groups["Solids"].append([i, volume_i])
+
+ np.save(result_file, [result, groups])
+
+run_netgen(**{kwds})
+ """
def fem_mesh_from_result(self):
fem_mesh = Fem.FemMesh()
@@ -301,7 +319,7 @@ NetgenTools.run_netgen(**{params})
"inverttrigs": self.obj.InvertTrigs,
"autozrefine": self.obj.AutoZRefine,
"parallel_meshing": self.obj.ParallelMeshing,
- "nthreads": self.obj.Threads,
+ "nthreads": self.param_grp.GetInt("NumOfThreads", QThread.idealThreadCount()),
"closeedgefac": self.obj.CloseEdgeFactor,
}
diff --git a/src/Mod/Fem/femobjects/mesh_netgen.py b/src/Mod/Fem/femobjects/mesh_netgen.py
index a437cdaedd..9d7048a4d9 100644
--- a/src/Mod/Fem/femobjects/mesh_netgen.py
+++ b/src/Mod/Fem/femobjects/mesh_netgen.py
@@ -485,15 +485,6 @@ class MeshNetgen(base_fempythonobject.BaseFemPythonObject):
value=True,
)
)
- prop.append(
- _PropHelper(
- type="App::PropertyInteger",
- name="Threads",
- group="Mesh Parameters",
- doc="Number of threads for parallel meshing",
- value=4,
- )
- )
prop.append(
_PropHelper(
type="App::PropertyBool",
diff --git a/src/Mod/Fem/fempreferencepages/__init__.py b/src/Mod/Fem/fempreferencepages/__init__.py
new file mode 100644
index 0000000000..983152cd5c
--- /dev/null
+++ b/src/Mod/Fem/fempreferencepages/__init__.py
@@ -0,0 +1 @@
+from .dlg_settings_netgen import DlgSettingsNetgen
diff --git a/src/Mod/Fem/fempreferencepages/dlg_settings_netgen.py b/src/Mod/Fem/fempreferencepages/dlg_settings_netgen.py
new file mode 100644
index 0000000000..695f7252c2
--- /dev/null
+++ b/src/Mod/Fem/fempreferencepages/dlg_settings_netgen.py
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2024 Mario Passaglia *
+# * *
+# * This file is part of FreeCAD. *
+# * *
+# * FreeCAD is free software: you can redistribute it and/or modify it *
+# * under the terms of the GNU Lesser General Public License as *
+# * published by the Free Software Foundation, either version 2.1 of the *
+# * License, or (at your option) any later version. *
+# * *
+# * FreeCAD is distributed in the hope that it will be useful, but *
+# * WITHOUT ANY WARRANTY; without even the implied warranty of *
+# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+# * Lesser General Public License for more details. *
+# * *
+# * You should have received a copy of the GNU Lesser General Public *
+# * License along with FreeCAD. If not, see *
+# * . *
+# * *
+# ***************************************************************************
+
+__title__ = "Netgen preference page class"
+__author__ = "Mario Passaglia"
+__url__ = "https://www.freecad.org"
+
+from PySide.QtCore import QThread
+
+import FreeCAD
+import FreeCADGui
+
+
+class DlgSettingsNetgen:
+
+ def __init__(self):
+ self.form = FreeCADGui.PySideUic.loadUi(":ui/DlgSettingsNetgen.ui")
+ self.grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Netgen")
+
+ def loadSettings(self):
+ self.form.ckb_legacy.setChecked(self.grp.GetBool("UseLegacyNetgen", True))
+ self.form.sb_threads.setValue(self.grp.GetInt("NumOfThreads", QThread.idealThreadCount()))
+ self.populate_log_verbosity()
+
+ def saveSettings(self):
+ self.grp.SetBool("UseLegacyNetgen", self.form.ckb_legacy.isChecked())
+ self.grp.SetInt("LogVerbosity", self.form.cb_log_verbosity.currentData())
+ self.grp.SetInt("NumOfThreads", self.form.sb_threads.value())
+
+ def populate_log_verbosity(self):
+ values = {
+ "None": 0,
+ "Least": 1,
+ "Little": 2,
+ "Moderate": 3,
+ "Much": 4,
+ "Most": 5,
+ }
+
+ for v in values:
+ self.form.cb_log_verbosity.addItem(v, values[v])
+
+ current = self.grp.GetInt("LogVerbosity", 2)
+ index = self.form.cb_log_verbosity.findData(current)
+ self.form.cb_log_verbosity.setCurrentIndex(index)
diff --git a/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py b/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py
index 55162bdb05..a55c7c9790 100644
--- a/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py
+++ b/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py
@@ -381,11 +381,8 @@ class _TaskPanel:
# Set up for multi-threading. Note: same functionality as ccx_tools.py/start_ccx()
ccx_prefs = FreeCAD.ParamGet(self.PREFS_PATH)
env = QtCore.QProcessEnvironment.systemEnvironment()
- num_cpu_pref = ccx_prefs.GetInt("AnalysisNumCPUs", 0)
- if num_cpu_pref >= 1:
- env.insert("OMP_NUM_THREADS", str(num_cpu_pref))
- else:
- env.insert("OMP_NUM_THREADS", str(QtCore.QThread.idealThreadCount()))
+ num_cpu_pref = ccx_prefs.GetInt("AnalysisNumCPUs", QtCore.QThread.idealThreadCount())
+ env.insert("OMP_NUM_THREADS", str(num_cpu_pref))
self.Calculix.setProcessEnvironment(env)
self.cwd = QtCore.QDir.currentPath()