diff --git a/src/Mod/Fem/App/FemMeshPy.xml b/src/Mod/Fem/App/FemMeshPy.xml
index c9710a5eb7..e3f11ca5e8 100755
--- a/src/Mod/Fem/App/FemMeshPy.xml
+++ b/src/Mod/Fem/App/FemMeshPy.xml
@@ -44,11 +44,21 @@
Add an edge by setting two node indices.
+
+
+ Add list of edges by list of node indices and list of nodes per edge.
+
+
Add a face by setting three node indices.
+
+
+ Add list of faces by list of node indices and list of nodes per face.
+
+
Add a quad by setting four node indices.
@@ -59,6 +69,11 @@
Add a volume by setting an arbitrary number of node indices.
+
+
+ Add list of volumes by list of node indices and list of nodes per volume.
+
+
Read in a various FEM mesh file formats.
diff --git a/src/Mod/Fem/App/FemMeshPyImp.cpp b/src/Mod/Fem/App/FemMeshPyImp.cpp
index 146ac4f41c..e4e5e8d354 100644
--- a/src/Mod/Fem/App/FemMeshPyImp.cpp
+++ b/src/Mod/Fem/App/FemMeshPyImp.cpp
@@ -809,6 +809,275 @@ PyObject* FemMeshPy::addVolume(PyObject* args)
return nullptr;
}
+PyObject* FemMeshPy::addEdgeList(PyObject* args)
+{
+ PyObject* nodesObj = nullptr;
+ PyObject* npObj = nullptr;
+ ;
+ if (!PyArg_ParseTuple(args, "O!O!", &PyList_Type, &nodesObj, &PyList_Type, &npObj)) {
+ return nullptr;
+ }
+
+ Py::List nodesList(nodesObj);
+ Py::List npList(npObj);
+ SMESHDS_Mesh* meshDS = getFemMeshPtr()->getSMesh()->GetMeshDS();
+
+ std::vector nodes;
+ for (Py::List::iterator it = nodesList.begin(); it != nodesList.end(); ++it) {
+ Py::Long n(*it);
+ const SMDS_MeshNode* node = meshDS->FindNode(static_cast(n));
+ if (!node) {
+ throw std::runtime_error("Failed to get node of the given indices");
+ }
+ nodes.push_back(node);
+ }
+
+ std::vector::iterator nodeIt = nodes.begin();
+ SMDS_MeshEdge* edge = nullptr;
+ Py::List result;
+ int np = 0;
+ for (Py::List::iterator it = npList.begin(); it != npList.end(); ++it, nodeIt += np) {
+ np = Py::Long(*it);
+ std::vector nodesElem(nodeIt, nodeIt + np);
+ switch (np) {
+ case 2:
+ edge = meshDS->AddEdge(nodesElem[0], nodesElem[1]);
+ break;
+ case 3:
+ edge = meshDS->AddEdge(nodesElem[0], nodesElem[1], nodesElem[2]);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "Unknown node count, [2|3] are allowed");
+ return nullptr;
+ }
+ if (edge) {
+ result.append(Py::Long(edge->GetID()));
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError, "Failed to add edge");
+ return nullptr;
+ }
+ }
+
+ return Py::new_reference_to(result);
+}
+
+
+PyObject* FemMeshPy::addFaceList(PyObject* args)
+{
+ PyObject* nodesObj = nullptr;
+ PyObject* npObj = nullptr;
+ ;
+ if (!PyArg_ParseTuple(args, "O!O!", &PyList_Type, &nodesObj, &PyList_Type, &npObj)) {
+ return nullptr;
+ }
+
+ Py::List nodesList(nodesObj);
+ Py::List npList(npObj);
+ SMESHDS_Mesh* meshDS = getFemMeshPtr()->getSMesh()->GetMeshDS();
+
+ std::vector nodes;
+ for (Py::List::iterator it = nodesList.begin(); it != nodesList.end(); ++it) {
+ Py::Long n(*it);
+ const SMDS_MeshNode* node = meshDS->FindNode(static_cast(n));
+ if (!node) {
+ throw std::runtime_error("Failed to get node of the given indices");
+ }
+ nodes.push_back(node);
+ }
+
+ std::vector::iterator nodeIt = nodes.begin();
+ SMDS_MeshFace* face = nullptr;
+ Py::List result;
+ int np = 0;
+ for (Py::List::iterator it = npList.begin(); it != npList.end(); ++it, nodeIt += np) {
+ np = Py::Long(*it);
+ std::vector nodesElem(nodeIt, nodeIt + np);
+ switch (np) {
+ case 3:
+ face = meshDS->AddFace(nodesElem[0], nodesElem[1], nodesElem[2]);
+ break;
+ case 4:
+ face = meshDS->AddFace(nodesElem[0], nodesElem[1], nodesElem[2], nodesElem[3]);
+ break;
+ case 6:
+ face = meshDS->AddFace(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5]);
+ break;
+ case 8:
+ face = meshDS->AddFace(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5],
+ nodesElem[6],
+ nodesElem[7]);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "Unknown node count, [3|4|6|8] are allowed");
+ return nullptr;
+ }
+ if (face) {
+ result.append(Py::Long(face->GetID()));
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError, "Failed to add face");
+ return nullptr;
+ }
+ }
+
+ return Py::new_reference_to(result);
+}
+
+
+PyObject* FemMeshPy::addVolumeList(PyObject* args)
+{
+ PyObject* nodesObj = nullptr;
+ PyObject* npObj = nullptr;
+ ;
+ if (!PyArg_ParseTuple(args, "O!O!", &PyList_Type, &nodesObj, &PyList_Type, &npObj)) {
+ return nullptr;
+ }
+
+ Py::List nodesList(nodesObj);
+ Py::List npList(npObj);
+ SMESHDS_Mesh* meshDS = getFemMeshPtr()->getSMesh()->GetMeshDS();
+
+ std::vector nodes;
+ for (Py::List::iterator it = nodesList.begin(); it != nodesList.end(); ++it) {
+ Py::Long n(*it);
+ const SMDS_MeshNode* node = meshDS->FindNode(static_cast(n));
+ if (!node) {
+ throw std::runtime_error("Failed to get node of the given indices");
+ }
+ nodes.push_back(node);
+ }
+
+ std::vector::iterator nodeIt = nodes.begin();
+ SMDS_MeshVolume* vol = nullptr;
+ Py::List result;
+ int np = 0;
+ for (Py::List::iterator it = npList.begin(); it != npList.end(); ++it, nodeIt += np) {
+ np = Py::Long(*it);
+ std::vector nodesElem(nodeIt, nodeIt + np);
+ switch (np) {
+ case 4:
+ vol = meshDS->AddVolume(nodesElem[0], nodesElem[1], nodesElem[2], nodesElem[3]);
+ break;
+ case 5:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4]);
+ break;
+ case 6:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5]);
+ break;
+ case 8:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5],
+ nodesElem[6],
+ nodesElem[7]);
+ break;
+ case 10:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5],
+ nodesElem[6],
+ nodesElem[7],
+ nodesElem[8],
+ nodesElem[9]);
+ break;
+ case 13:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5],
+ nodesElem[6],
+ nodesElem[7],
+ nodesElem[8],
+ nodesElem[9],
+ nodesElem[10],
+ nodesElem[11],
+ nodesElem[12]);
+ break;
+ case 15:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5],
+ nodesElem[6],
+ nodesElem[7],
+ nodesElem[8],
+ nodesElem[9],
+ nodesElem[10],
+ nodesElem[11],
+ nodesElem[12],
+ nodesElem[13],
+ nodesElem[14]);
+ break;
+ case 20:
+ vol = meshDS->AddVolume(nodesElem[0],
+ nodesElem[1],
+ nodesElem[2],
+ nodesElem[3],
+ nodesElem[4],
+ nodesElem[5],
+ nodesElem[6],
+ nodesElem[7],
+ nodesElem[8],
+ nodesElem[9],
+ nodesElem[10],
+ nodesElem[11],
+ nodesElem[12],
+ nodesElem[13],
+ nodesElem[14],
+ nodesElem[15],
+ nodesElem[16],
+ nodesElem[17],
+ nodesElem[18],
+ nodesElem[19]);
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError,
+ "Unknown node count, [4|5|6|8|10|13|15|20] are allowed");
+ return nullptr;
+ }
+ if (vol) {
+ result.append(Py::Long(vol->GetID()));
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError, "Failed to add face");
+ return nullptr;
+ }
+ }
+
+ return Py::new_reference_to(result);
+}
+
+
PyObject* FemMeshPy::copy(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt
index 9a53d41f94..da61e3e816 100755
--- a/src/Mod/Fem/CMakeLists.txt
+++ b/src/Mod/Fem/CMakeLists.txt
@@ -165,6 +165,7 @@ SET(FemMesh_SRCS
femmesh/gmshtools.py
femmesh/meshsetsgetter.py
femmesh/meshtools.py
+ femmesh/netgentools.py
)
SET(FemObjects_SRCS
@@ -194,6 +195,7 @@ SET(FemObjects_SRCS
femobjects/mesh_boundarylayer.py
femobjects/mesh_gmsh.py
femobjects/mesh_group.py
+ femobjects/mesh_netgen.py
femobjects/mesh_region.py
femobjects/mesh_result.py
femobjects/result_mechanical.py
@@ -592,6 +594,7 @@ SET(FemGuiTaskPanels_SRCS
femtaskpanels/task_mesh_gmsh.py
femtaskpanels/task_mesh_group.py
femtaskpanels/task_mesh_region.py
+ femtaskpanels/task_mesh_netgen.py
femtaskpanels/task_result_mechanical.py
femtaskpanels/task_solver_ccxtools.py
)
@@ -636,6 +639,7 @@ SET(FemGuiViewProvider_SRCS
femviewprovider/view_mesh_boundarylayer.py
femviewprovider/view_mesh_gmsh.py
femviewprovider/view_mesh_group.py
+ femviewprovider/view_mesh_netgen.py
femviewprovider/view_mesh_region.py
femviewprovider/view_mesh_result.py
femviewprovider/view_result_mechanical.py
diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt
index 302f201b33..ec05485b75 100755
--- a/src/Mod/Fem/Gui/CMakeLists.txt
+++ b/src/Mod/Fem/Gui/CMakeLists.txt
@@ -415,6 +415,7 @@ SET(FemGuiPythonUI_SRCS
Resources/ui/MeshGmsh.ui
Resources/ui/MeshGroup.ui
Resources/ui/MeshGroupXDMFExport.ui
+ Resources/ui/MeshNetgen.ui
Resources/ui/MeshRegion.ui
Resources/ui/ResultHints.ui
Resources/ui/ResultShow.ui
diff --git a/src/Mod/Fem/Gui/Resources/ui/MeshNetgen.ui b/src/Mod/Fem/Gui/Resources/ui/MeshNetgen.ui
new file mode 100644
index 0000000000..7a9d334dc1
--- /dev/null
+++ b/src/Mod/Fem/Gui/Resources/ui/MeshNetgen.ui
@@ -0,0 +1,277 @@
+
+
+ NetgenMesh
+
+
+
+ 0
+ 0
+ 400
+ 475
+
+
+
+ FEM Mesh by Netgen
+
+
+ -
+
+
+
+ 16777215
+ 1677215
+
+
+
+ Mesh Parameters
+
+
+
-
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
-
+
+
+ Fineness:
+
+
+
+ -
+
+
+ -
+
+
+ Maximal Size:
+
+
+
+ -
+
+
+ true
+
+
+ mm
+
+
+
+ 100
+ 20
+
+
+
+ Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ true
+
+
+ 0.000000000000000
+
+
+ 1000000000000000000000.000000000000000
+
+
+ 1.000000000000000
+
+
+ 1000.000000000000
+
+
+
+ -
+
+
+ Minimal Size:
+
+
+
+ -
+
+
+ true
+
+
+ mm
+
+
+
+ 100
+ 20
+
+
+
+ Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ true
+
+
+ 0.000000000000000
+
+
+ 1000000000000000000000.000000000000000
+
+
+ 1.000000000000000
+
+
+ 0.000000000000000
+
+
+
+ -
+
+
+ Second Order
+
+
+
+ -
+
+
+ Growth Rate:
+
+
+
+ -
+
+
+ false
+
+
+ 0.010000000000000
+
+
+ 1.000000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ Curvature Safety:
+
+
+
+ -
+
+
+ false
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ Segments Per Edge:
+
+
+
+ -
+
+
+ false
+
+
+ 0.100000000000000
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 1677215
+
+
+
+ Netgen
+
+
+
-
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
-
+
+
-
+
+
+ QTextEdit::NoWrap
+
+
+
+ -
+
+
+
+ 12
+
+
+
+ Time:
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Netgen version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gui::QuantitySpinBox
+ QWidget
+
+
+
+
+
+
diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp
index e2ea74f046..adcdc7ad12 100644
--- a/src/Mod/Fem/Gui/Workbench.cpp
+++ b/src/Mod/Fem/Gui/Workbench.cpp
@@ -154,10 +154,8 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
Gui::ToolBarItem* mesh = new Gui::ToolBarItem(root);
mesh->setCommand("Mesh");
-#ifdef FCWithNetgen
- *mesh << "FEM_MeshNetgenFromShape";
-#endif
- *mesh << "FEM_MeshGmshFromShape"
+ *mesh << "FEM_MeshNetgenFromShape"
+ << "FEM_MeshGmshFromShape"
<< "Separator"
<< "FEM_MeshBoundaryLayer"
<< "FEM_MeshRegion"
@@ -311,10 +309,8 @@ Gui::MenuItem* Workbench::setupMenuBar() const
Gui::MenuItem* mesh = new Gui::MenuItem;
root->insertItem(item, mesh);
mesh->setCommand("M&esh");
-#ifdef FCWithNetgen
- *mesh << "FEM_MeshNetgenFromShape";
-#endif
- *mesh << "FEM_MeshGmshFromShape"
+ *mesh << "FEM_MeshNetgenFromShape"
+ << "FEM_MeshGmshFromShape"
<< "Separator"
<< "FEM_MeshBoundaryLayer"
<< "FEM_MeshRegion"
diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py
index 4c6042ad56..5148f2b891 100644
--- a/src/Mod/Fem/ObjectsFem.py
+++ b/src/Mod/Fem/ObjectsFem.py
@@ -525,8 +525,15 @@ def makeMeshGroup(doc, base_mesh, use_label=False, name="MeshGroup"):
def makeMeshNetgen(doc, name="MeshNetgen"):
"""makeMeshNetgen(document, [name]):
- makes a Fem MeshShapeNetgenObject object"""
- obj = doc.addObject("Fem::FemMeshShapeNetgenObject", name)
+ makes a Netgen FEM mesh object"""
+ obj = doc.addObject("Fem::FemMeshShapeBaseObjectPython", name)
+ from femobjects import mesh_netgen
+
+ mesh_netgen.MeshNetgen(obj)
+ if FreeCAD.GuiUp:
+ from femviewprovider import view_mesh_netgen
+
+ view_mesh_netgen.VPMeshNetgen(obj.ViewObject)
return obj
diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py
index 8bd97dc065..bc41a058a4 100644
--- a/src/Mod/Fem/femcommands/commands.py
+++ b/src/Mod/Fem/femcommands/commands.py
@@ -830,6 +830,7 @@ class _MeshNetgenFromShape(CommandManager):
self.selobj.Name
)
)
+ FreeCADGui.doCommand("FreeCAD.ActiveDocument.ActiveObject.Fineness = 'Moderate'")
# Netgen mesh object could be added without an active analysis
# but if there is an active analysis move it in there
import FemGui
diff --git a/src/Mod/Fem/femmesh/netgentools.py b/src/Mod/Fem/femmesh/netgentools.py
new file mode 100644
index 0000000000..45b7170b01
--- /dev/null
+++ b/src/Mod/Fem/femmesh/netgentools.py
@@ -0,0 +1,297 @@
+# 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__ = "Tools for the work with Netgen mesher"
+__author__ = "Mario Passaglia"
+__url__ = "https://www.freecad.org"
+
+import numpy as np
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import FreeCAD
+import Fem
+
+try:
+ from netgen import occ, config as ng_config
+except ModuleNotFoundError:
+ FreeCAD.Console.PrintError("To use FemMesh Netgen objects, install the Netgen Python bindings")
+
+
+class NetgenTools:
+
+ # to change order of nodes from netgen to smesh
+ order_edge = {
+ 2: [0, 1, 2], # seg2
+ 3: [0, 1, 2], # seg3
+ }
+ order_face = {
+ 3: [0, 1, 2, 3, 4, 5, 6, 7], # tria3
+ 6: [0, 1, 2, 5, 3, 4, 6, 7], # tria6
+ 4: [0, 1, 2, 3, 4, 5, 6, 7], # quad4
+ 8: [0, 1, 2, 3, 4, 7, 5, 6], # quad8
+ }
+ order_volume = {
+ 4: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], # tetra4
+ 10: [0, 1, 2, 3, 4, 7, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], # tetra10
+ 8: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], # hexa8
+ 20: [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 9, 10, 12, 15, 13, 14, 16, 17, 18, 19], # hexa20
+ 5: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], # pyra5
+ 13: [0, 1, 2, 3, 4, 5, 8, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], # pyra13
+ 6: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], # penta6
+ 15: [0, 1, 2, 3, 4, 5, 6, 8, 7, 12, 14, 13, 9, 10, 11, 15, 16, 17, 18, 19], # penta15
+ }
+
+ name = "Netgen"
+
+ def __init__(self, obj):
+ self.obj = obj
+ self.fem_mesh = None
+ self.process = None
+ self.tmpdir = ""
+
+ def write_geom(self):
+ if not self.tmpdir:
+ self.tmpdir = tempfile.mkdtemp(prefix="fem_")
+
+ sh = self.obj.Shape.getPropertyOfGeometry()
+ self.brep_file = self.tmpdir + "/shape.brep"
+ self.result_file = self.tmpdir + "/result.npy"
+ sh.exportBrep(self.brep_file)
+
+ code = """
+from femmesh.netgentools import NetgenTools
+
+NetgenTools.run_netgen(**{params})
+"""
+
+ def compute(self):
+ self.write_geom()
+ mesh_params = {
+ "brep_file": self.brep_file,
+ "threads": self.obj.Threads,
+ "heal": self.obj.HealShape,
+ "fineness": self.obj.Fineness,
+ "params": self.get_meshing_parameters(),
+ "second_order": self.obj.SecondOrder,
+ "result_file": self.result_file,
+ }
+
+ code_str = self.code.format(params=mesh_params)
+
+ cmd_list = [
+ sys.executable,
+ "-c",
+ code_str,
+ ]
+ self.process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = self.process.communicate()
+ if self.process.returncode != 0:
+ raise RuntimeError(err.decode("utf-8"))
+
+ return True
+
+ @staticmethod
+ def run_netgen(brep_file, threads, heal, fineness, params, second_order, result_file):
+ import pyngcore as ngcore
+ from netgen import meshing
+
+ geom = occ.OCCGeometry(brep_file)
+ ngcore.SetNumThreads(threads)
+
+ if fineness == "UserDefined":
+ mp = meshing.MeshingParameters(**params)
+ elif fineness == "VeryCoarse":
+ mp = meshing.meshsize.very_coarse
+ elif fineness == "Coarse":
+ mp = meshing.meshsize.coarse
+ elif fineness == "Moderate":
+ mp = meshing.meshsize.moderate
+ elif fineness == "Fine":
+ mp = meshing.meshsize.fine
+ elif fineness == "VeryFine":
+ mp = meshing.meshsize.very_fine
+
+ with ngcore.TaskManager():
+ if heal:
+ geom.Heal()
+ mesh = geom.GenerateMesh(mp=mp)
+
+ if second_order:
+ 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"]
+
+ groups = {"Edges": [], "Faces": [], "Solids": []}
+ 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])
+
+ def fem_mesh_from_result(self):
+ fem_mesh = Fem.FemMesh()
+
+ # load Netgen result
+ netgen_result, groups = np.load(self.result_file, allow_pickle=True)
+
+ for node in netgen_result["coords"]:
+ fem_mesh.addNode(*node)
+
+ fem_mesh.addEdgeList(*netgen_result["Edges"])
+ fem_mesh.addFaceList(*netgen_result["Faces"])
+ fem_mesh.addVolumeList(*netgen_result["Volumes"])
+
+ for g in groups["Edges"]:
+ grp_id = fem_mesh.addGroup("Edge" + str(g[0]), "Edge")
+ fem_mesh.addGroupElements(grp_id, g[1])
+
+ for g in groups["Faces"]:
+ grp_id = fem_mesh.addGroup("Face" + str(g[0]), "Face")
+ fem_mesh.addGroupElements(grp_id, g[1])
+
+ for g in groups["Solids"]:
+ grp_id = fem_mesh.addGroup("Solid" + str(g[0]), "Volume")
+ fem_mesh.addGroupElements(grp_id, g[1])
+
+ return fem_mesh
+
+ def update_properties(self):
+ self.obj.FemMesh = self.fem_mesh_from_result()
+
+ def get_meshing_parameters(self):
+ params = {
+ "optimize3d": self.obj.Optimize3d,
+ "optimize2d": self.obj.Optimize2d,
+ "optsteps3d": self.obj.OptimizationSteps3d,
+ "optsteps2d": self.obj.OptimizationSteps2d,
+ "opterrpow": self.obj.OptimizationErrorPower,
+ "blockfill": self.obj.BlockFill,
+ "filldist": self.obj.FillDistance.Value,
+ "safety": self.obj.Safety,
+ "relinnersafety": self.obj.RelinnerSafety,
+ "uselocalh": self.obj.UseLocalH,
+ "grading": self.obj.GrowthRate,
+ "delaunay": self.obj.Delaunay,
+ "delaunay2d": self.obj.Delaunay2d,
+ "maxh": self.obj.MaxSize.Value,
+ "minh": self.obj.MinSize.Value,
+ "startinsurface": self.obj.StartInSurface,
+ "checkoverlap": self.obj.CheckOverlap,
+ "checkoverlappingboundary": self.obj.CheckOverlappingBoundary,
+ "checkchartboundary": self.obj.CheckChartBoundary,
+ "curvaturesafety": self.obj.CurvatureSafety,
+ "segmentsperedge": self.obj.SegmentsPerEdge,
+ "elsizeweight": self.obj.ElementSizeWeight,
+ "parthread": self.obj.ParallelMeshing,
+ "perfstepsstart": self.obj.StartStep,
+ "perfstepsend": self.obj.EndStep,
+ "giveuptol2d": self.obj.GiveUpTolerance2d,
+ "giveuptol": self.obj.GiveUpTolerance,
+ "giveuptolopenquads": self.obj.GiveUpToleranceOpenQuads,
+ "maxoutersteps": self.obj.MaxOuterSteps,
+ "starshapeclass": self.obj.StarShapeClass,
+ "baseelnp": self.obj.BaseElementNp,
+ "sloppy": self.obj.Sloppy,
+ "badellimit": self.obj.BadElementLimit,
+ "check_impossible": self.obj.CheckImpossible,
+ "only3D_domain_nr": self.obj.Only3dDomainNr,
+ "secondorder": self.obj.SecondOrder,
+ "elementorder": self.obj.ElementOrder,
+ "quad_dominated": self.obj.QuadDominated,
+ "try_hexes": self.obj.TryHexes,
+ "inverttets": self.obj.InvertTets,
+ "inverttrigs": self.obj.InvertTrigs,
+ "autozrefine": self.obj.AutoZRefine,
+ "parallel_meshing": self.obj.ParallelMeshing,
+ "nthreads": self.obj.Threads,
+ "closeedgefac": self.obj.CloseEdgeFactor,
+ }
+
+ return params
+
+ @staticmethod
+ def version():
+ result = "{}: {}\n" + "{}: {}\n" + "{}: {}\n" + "{}: {}"
+ return result.format(
+ "Netgen",
+ ng_config.version,
+ "Python",
+ ng_config.PYTHON_VERSION,
+ "OpenCASCADE",
+ occ.occ_version,
+ "Use MPI",
+ ng_config.USE_MPI,
+ )
+
+ def __del__(self):
+ if self.tmpdir:
+ shutil.rmtree(self.tmpdir)
diff --git a/src/Mod/Fem/femobjects/mesh_netgen.py b/src/Mod/Fem/femobjects/mesh_netgen.py
new file mode 100644
index 0000000000..c04482b978
--- /dev/null
+++ b/src/Mod/Fem/femobjects/mesh_netgen.py
@@ -0,0 +1,486 @@
+# 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__ = "FreeCAD FEM mesh netgen document object"
+__author__ = "Mario Passaglia"
+__url__ = "https://www.freecad.org"
+
+## @package mesh_netgen
+# \ingroup FEM
+# \brief mesh gmsh object
+
+from FreeCAD import Base
+from . import base_fempythonobject
+
+_PropHelper = base_fempythonobject._PropHelper
+
+
+class MeshNetgen(base_fempythonobject.BaseFemPythonObject):
+ """
+ A Fem::FemMeshShapeBaseObject python type, add Netgen specific properties
+ """
+
+ Type = "Fem::FemMeshNetgen"
+
+ def __init__(self, obj):
+ super().__init__(obj)
+
+ for prop in self._get_properties():
+ prop.add_to_object(obj)
+
+ def _get_properties(self):
+ prop = []
+
+ prop.append(
+ _PropHelper(
+ type="App::PropertyString",
+ name="Optimize3d",
+ group="Mesh Parameters",
+ doc="3d optimization strategy.\n"
+ + "m: move nodes, M: move nodes, cheap functional\n"
+ + "s: swap faces, c: combine elements, d: divide elements,\n"
+ + "D: divide and join opposite edges, remove element,\n"
+ + "p: plot, no pause, P: plot, Pause,\n"
+ + "h: Histogramm, no pause, H: Histogramm, pause",
+ value="cmdDmustm",
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyEnumeration",
+ name="Fineness",
+ group="Mesh Parameters",
+ doc="Fineness",
+ value=["VeryCoarse", "Coarse", "Moderate", "Fine", "VeryFine", "UserDefined"],
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="OptimizationSteps3d",
+ group="Mesh Parameters",
+ doc="Number of 3d optimization steps",
+ value=3,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyString",
+ name="Optimize2d",
+ group="Mesh Parameters",
+ doc="2d optimization strategy.\n"
+ + "s: swap opt 6 lines/node, S: swap optimal elements,\n"
+ + "m: move nodes, p: plot, no pause\n"
+ + "P: plot, pause, c: combine",
+ value="smcmSmcmSmcm",
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="OptimizationSteps2d",
+ group="Mesh Parameters",
+ doc="Number of 2d optimization steps",
+ value=3,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="OptimizationErrorPower",
+ group="Mesh Parameters",
+ doc="Power of error to approximate max error optimization",
+ value=2.0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="BlockFill",
+ group="Mesh Parameters",
+ doc="Do block filling",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyLength",
+ name="FillDistance",
+ group="Mesh Parameters",
+ doc="Block filling up to distance",
+ value=0.1,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="Safety",
+ group="Mesh Parameters",
+ doc="Radius of local environment (times h)",
+ value=5.0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="RelinnerSafety",
+ group="Mesh Parameters",
+ doc="Radius of active environment (times h)",
+ value=3.0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="LocalH",
+ group="Mesh Parameters",
+ doc="Use local h",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="UseLocalH",
+ group="Mesh Parameters",
+ doc="Use local H",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="GrowthRate",
+ group="Mesh Parameters",
+ doc="Grading for local h",
+ value=0.3,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="Delaunay",
+ group="Mesh Parameters",
+ doc="Use Delaunay for 3d meshing",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="Delaunay2d",
+ group="Mesh Parameters",
+ doc="Use Delaunay for 2d meshing",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyLength",
+ name="MaxSize",
+ group="Mesh Parameters",
+ doc="Maximal mesh size",
+ value="1000 mm",
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyLength",
+ name="MinSize",
+ group="Mesh Parameters",
+ doc="Minimal mesh size",
+ value="0 mm",
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="CloseEdgeFactor",
+ group="Mesh Parameters",
+ doc="Factor to restrict meshing based on close edges",
+ value=2.0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="StartInSurface",
+ group="Mesh Parameters",
+ doc="Start surface meshing from everywhere in surface",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="CheckOverlap",
+ group="Mesh Parameters",
+ doc="Check overlapping surfaces",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="CheckOverlappingBoundary",
+ group="Mesh Parameters",
+ doc="Check overlapping surface mesh before volume meshing",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="CheckChartBoundary",
+ group="Mesh Parameters",
+ doc="Check chart boundary",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="CurvatureSafety",
+ group="Mesh Parameters",
+ doc="Safety factor for curvatures (elements per radius)",
+ value=2.0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="SegmentsPerEdge",
+ group="Mesh Parameters",
+ doc="Minimal number of segments per edge",
+ value=2.0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="ElementSizeWeight",
+ group="Mesh Parameters",
+ doc="Weight of element size respect to element shape",
+ value=0.2,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="StartStep",
+ group="Mesh Parameters",
+ doc="Start at step",
+ value=0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="EndStep",
+ group="Mesh Parameters",
+ doc="EndStep",
+ value=6,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="GiveUpTolerance2d",
+ group="Mesh Parameters",
+ doc="Give up quality class, 2d meshing",
+ value=200,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="GiveUpTolerance",
+ group="Mesh Parameters",
+ doc="Give up quality class, 3d meshing",
+ value=10,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="GiveUpToleranceOpenQuads",
+ group="Mesh Parameters",
+ doc="Give up quality class, for closing open quads, greather than 100 for free pyramids",
+ value=15,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="MaxOuterSteps",
+ group="Mesh Parameters",
+ doc="Maximal outer steps",
+ value=10,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="StarShapeClass",
+ group="Mesh Parameters",
+ doc="Class starting star-shape filling",
+ value=5,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="BaseElementNp",
+ group="Mesh Parameters",
+ doc="If non-zero, baseelement must have BaseElementlNp points",
+ value=0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="Sloppy",
+ group="Mesh Parameters",
+ doc="Quality tolerances are handled less careful",
+ value=10,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyFloat",
+ name="BadElementLimit",
+ group="Mesh Parameters",
+ doc="Limit for max element angle (150-180)",
+ value=175,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="CheckImpossible",
+ group="Mesh Parameters",
+ doc="",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="Only3dDomainNr",
+ group="Mesh Parameters",
+ doc="",
+ value=0,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="SecondOrder",
+ group="Mesh Parameters",
+ doc="Second order element meshing",
+ value=True,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyInteger",
+ name="ElementOrder",
+ group="Mesh Parameters",
+ doc="High order element curvature",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="QuadDominated",
+ group="Mesh Parameters",
+ doc="Quad-dominated surface meshing",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="TryHexes",
+ group="Mesh Parameters",
+ doc="Try hexahedral elements",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="InvertTets",
+ group="Mesh Parameters",
+ doc="",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="InvertTrigs",
+ group="Mesh Parameters",
+ doc="",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="AutoZRefine",
+ group="Mesh Parameters",
+ doc="Automatic Z refine",
+ value=False,
+ )
+ )
+ prop.append(
+ _PropHelper(
+ type="App::PropertyBool",
+ name="ParallelMeshing",
+ group="Mesh Parameters",
+ doc="Use parallel meshing",
+ 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",
+ name="HealShape",
+ group="Mesh Parameters",
+ doc="Heal shape before meshing",
+ value=False,
+ )
+ )
+
+ return prop
diff --git a/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py b/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py
new file mode 100644
index 0000000000..6675e0ad70
--- /dev/null
+++ b/src/Mod/Fem/femtaskpanels/task_mesh_netgen.py
@@ -0,0 +1,164 @@
+# 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__ = "FreeCAD FEM mesh netgen task panel for mesh netgen object"
+__author__ = "Mario Passaglia"
+__url__ = "https://www.freecad.org"
+
+## @package task_mesh_netgen
+# \ingroup FEM
+# \brief task panel for mesh netgen object
+
+from PySide import QtCore
+
+import FreeCAD
+import FreeCADGui
+
+from femmesh import netgentools
+
+from . import base_femmeshtaskpanel
+
+
+class _TaskPanel(base_femmeshtaskpanel._BaseMeshTaskPanel):
+ """
+ The TaskPanel for editing References property of
+ MeshNetgen objects and creation of new FEM mesh
+ """
+
+ def __init__(self, obj):
+ super().__init__(obj)
+ self.form = FreeCADGui.PySideUic.loadUi(
+ FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/MeshNetgen.ui"
+ )
+
+ self.tool = netgentools.NetgenTools(obj)
+
+ QtCore.QObject.connect(
+ self.form.qsb_max_size,
+ QtCore.SIGNAL("valueChanged(Base::Quantity)"),
+ self.max_size_changed,
+ )
+ QtCore.QObject.connect(
+ self.form.qsb_min_size,
+ QtCore.SIGNAL("valueChanged(Base::Quantity)"),
+ self.min_size_changed,
+ )
+ QtCore.QObject.connect(
+ self.form.dsb_seg_per_edge,
+ QtCore.SIGNAL("valueChanged(double)"),
+ self.seg_per_edge_changed,
+ )
+ QtCore.QObject.connect(
+ self.form.dsb_curvature_safety,
+ QtCore.SIGNAL("valueChanged(double)"),
+ self.curvature_safety_changed,
+ )
+ QtCore.QObject.connect(
+ self.form.dsb_growth_rate,
+ QtCore.SIGNAL("valueChanged(double)"),
+ self.growth_rate_changed,
+ )
+ QtCore.QObject.connect(
+ self.form.ckb_second_order, QtCore.SIGNAL("toggled(bool)"), self.second_order_changed
+ )
+ QtCore.QObject.connect(
+ self.form.cb_fineness,
+ QtCore.SIGNAL("currentIndexChanged(int)"),
+ self.fineness_changed,
+ )
+ QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.update_timer_text)
+ QtCore.QObject.connect(
+ self.form.pb_get_netgen_version, QtCore.SIGNAL("clicked()"), self.get_version
+ )
+
+ self.get_mesh_params()
+ self.set_widgets()
+
+ def get_mesh_params(self):
+ self.min_size = self.obj.MinSize
+ self.max_size = self.obj.MaxSize
+ self.fineness = self.obj.Fineness
+ self.growth_rate = self.obj.GrowthRate
+ self.curvature_safety = self.obj.CurvatureSafety
+ self.seg_per_edge = self.obj.SegmentsPerEdge
+ self.second_order = self.obj.SecondOrder
+
+ def set_mesh_params(self):
+ self.obj.MinSize = self.min_size
+ self.obj.MaxSize = self.max_size
+ self.obj.Fineness = self.fineness
+ self.obj.GrowthRate = self.growth_rate
+ self.obj.CurvatureSafety = self.curvature_safety
+ self.obj.SegmentsPerEdge = self.seg_per_edge
+ self.obj.SecondOrder = self.second_order
+
+ def set_widgets(self):
+ "fills the widgets"
+ self.form.qsb_max_size.setProperty("value", self.max_size)
+ FreeCADGui.ExpressionBinding(self.form.qsb_max_size).bind(self.obj, "MaxSize")
+
+ self.form.qsb_min_size.setProperty("value", self.min_size)
+ FreeCADGui.ExpressionBinding(self.form.qsb_min_size).bind(self.obj, "MinSize")
+
+ self.fineness_enum = self.obj.getEnumerationsOfProperty("Fineness")
+ index = self.fineness_enum.index(self.fineness)
+ self.form.cb_fineness.addItems(self.fineness_enum)
+ self.form.cb_fineness.setCurrentIndex(index)
+ self.form.dsb_growth_rate.setValue(self.growth_rate)
+ self.form.dsb_curvature_safety.setValue(self.curvature_safety)
+ self.form.dsb_seg_per_edge.setValue(self.seg_per_edge)
+
+ self.form.ckb_second_order.setChecked(self.second_order)
+
+ def max_size_changed(self, base_quantity_value):
+ self.max_size = base_quantity_value
+
+ def min_size_changed(self, base_quantity_value):
+ self.min_size = base_quantity_value
+
+ def seg_per_edge_changed(self, value):
+ self.seg_per_edge = value
+
+ def curvature_safety_changed(self, value):
+ self.curvature_safety = value
+
+ def growth_rate_changed(self, value):
+ self.growth_rate = value
+
+ def fineness_changed(self, index):
+ self.fineness = self.fineness_enum[index]
+ if self.fineness == "UserDefined":
+ self.form.qsb_min_size.setEnabled(True)
+ self.form.qsb_max_size.setEnabled(True)
+ self.form.dsb_seg_per_edge.setEnabled(True)
+ self.form.dsb_growth_rate.setEnabled(True)
+ self.form.dsb_curvature_safety.setEnabled(True)
+ else:
+ self.form.qsb_min_size.setEnabled(False)
+ self.form.qsb_max_size.setEnabled(False)
+ self.form.dsb_seg_per_edge.setEnabled(False)
+ self.form.dsb_growth_rate.setEnabled(False)
+ self.form.dsb_curvature_safety.setEnabled(False)
+
+ def second_order_changed(self, bool_value):
+ self.second_order = bool_value
diff --git a/src/Mod/Fem/femtest/app/test_object.py b/src/Mod/Fem/femtest/app/test_object.py
index 31cc829fb9..57471886cb 100644
--- a/src/Mod/Fem/femtest/app/test_object.py
+++ b/src/Mod/Fem/femtest/app/test_object.py
@@ -240,9 +240,7 @@ class TestObjectType(unittest.TestCase):
)
self.assertEqual("Fem::MeshGroup", type_of_obj(ObjectsFem.makeMeshGroup(doc, mesh)))
self.assertEqual("Fem::MeshRegion", type_of_obj(ObjectsFem.makeMeshRegion(doc, mesh)))
- self.assertEqual(
- "Fem::FemMeshShapeNetgenObject", type_of_obj(ObjectsFem.makeMeshNetgen(doc))
- )
+ self.assertEqual("Fem::FemMeshNetgen", type_of_obj(ObjectsFem.makeMeshNetgen(doc)))
self.assertEqual("Fem::MeshResult", type_of_obj(ObjectsFem.makeMeshResult(doc)))
self.assertEqual("Fem::ResultMechanical", type_of_obj(ObjectsFem.makeResultMechanical(doc)))
solverelmer = ObjectsFem.makeSolverElmer(doc)
@@ -409,7 +407,7 @@ class TestObjectType(unittest.TestCase):
)
self.assertTrue(is_of_type(ObjectsFem.makeMeshGroup(doc, mesh), "Fem::MeshGroup"))
self.assertTrue(is_of_type(ObjectsFem.makeMeshRegion(doc, mesh), "Fem::MeshRegion"))
- self.assertTrue(is_of_type(ObjectsFem.makeMeshNetgen(doc), "Fem::FemMeshShapeNetgenObject"))
+ self.assertTrue(is_of_type(ObjectsFem.makeMeshNetgen(doc), "Fem::FemMeshNetgen"))
self.assertTrue(is_of_type(ObjectsFem.makeMeshResult(doc), "Fem::MeshResult"))
self.assertTrue(is_of_type(ObjectsFem.makeResultMechanical(doc), "Fem::ResultMechanical"))
solverelmer = ObjectsFem.makeSolverElmer(doc)
@@ -745,7 +743,7 @@ class TestObjectType(unittest.TestCase):
# FemMeshShapeNetgenObject
mesh_netgen = ObjectsFem.makeMeshNetgen(doc)
self.assertTrue(is_derived_from(mesh_netgen, "App::DocumentObject"))
- self.assertTrue(is_derived_from(mesh_netgen, "Fem::FemMeshShapeNetgenObject"))
+ self.assertTrue(is_derived_from(mesh_netgen, "Fem::FemMeshShapeBaseObjectPython"))
# MeshResult
mesh_result = ObjectsFem.makeMeshResult(doc)
@@ -972,7 +970,7 @@ class TestObjectType(unittest.TestCase):
self.assertTrue(ObjectsFem.makeMeshGroup(doc, mesh).isDerivedFrom("Fem::FeaturePython"))
self.assertTrue(ObjectsFem.makeMeshRegion(doc, mesh).isDerivedFrom("Fem::FeaturePython"))
self.assertTrue(
- ObjectsFem.makeMeshNetgen(doc).isDerivedFrom("Fem::FemMeshShapeNetgenObject")
+ ObjectsFem.makeMeshNetgen(doc).isDerivedFrom("Fem::FemMeshShapeBaseObjectPython")
)
self.assertTrue(ObjectsFem.makeMeshResult(doc).isDerivedFrom("Fem::FemMeshObjectPython"))
self.assertTrue(
diff --git a/src/Mod/Fem/femviewprovider/view_mesh_netgen.py b/src/Mod/Fem/femviewprovider/view_mesh_netgen.py
new file mode 100644
index 0000000000..34a60d3698
--- /dev/null
+++ b/src/Mod/Fem/femviewprovider/view_mesh_netgen.py
@@ -0,0 +1,158 @@
+# 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__ = "FreeCAD FEM mesh netgen ViewProvider for the document object"
+__author__ = "Mario Passaglia"
+__url__ = "https://www.freecad.org"
+
+## @package view_mesh_netgen
+# \ingroup FEM
+# \brief view provider for mesh netgen object
+
+import FreeCAD
+import FreeCADGui
+
+import FemGui
+from PySide import QtGui
+from femtaskpanels import task_mesh_netgen
+from femtools.femutils import is_of_type
+from femviewprovider import view_base_femobject
+
+
+class VPMeshNetgen(view_base_femobject.VPBaseFemObject):
+ """
+ A View Provider for the MeshNetgen object
+ """
+
+ def __init__(self, vobj):
+ vobj.Proxy = self
+
+ def getIcon(self):
+ return ":/icons/FEM_MeshNetgenFromShape.svg"
+
+ def setEdit(self, vobj, mode):
+ # hide all FEM meshes and VTK FemPost* objects
+ for obj in vobj.Object.Document.Objects:
+ if obj.isDerivedFrom("Fem::FemMeshObject") or obj.isDerivedFrom("Fem::FemPostObject"):
+ obj.ViewObject.hide()
+ # show the mesh we like to edit
+ self.ViewObject.show()
+ # show task panel
+ taskd = task_mesh_netgen._TaskPanel(self.Object)
+ FreeCADGui.Control.showDialog(taskd)
+ return True
+
+ def doubleClicked(self, vobj):
+ # Group meshing is only active on active analysis
+ # we should make sure the analysis the mesh belongs too is active
+ gui_doc = FreeCADGui.getDocument(vobj.Object.Document)
+ if not gui_doc.getInEdit():
+ # may be go the other way around and just activate the
+ # analysis the user has doubleClicked on ?!
+ # not a fast one, we need to iterate over all member of all
+ # analysis to know to which analysis the object belongs too!!!
+ # first check if there is an analysis in the active document
+ found_an_analysis = False
+ for o in gui_doc.Document.Objects:
+ if o.isDerivedFrom("Fem::FemAnalysisPython"):
+ found_an_analysis = True
+ break
+ if found_an_analysis:
+ if FemGui.getActiveAnalysis() is not None:
+ if FemGui.getActiveAnalysis().Document is FreeCAD.ActiveDocument:
+ if self.Object in FemGui.getActiveAnalysis().Group:
+ if not gui_doc.getInEdit():
+ gui_doc.setEdit(vobj.Object.Name)
+ else:
+ FreeCAD.Console.PrintError(
+ "Activate the analysis this Netgen FEM "
+ "mesh object belongs too!\n"
+ )
+ else:
+ FreeCAD.Console.PrintMessage(
+ "Netgen FEM mesh object does not belong to the active analysis.\n"
+ )
+ found_mesh_analysis = False
+ for o in gui_doc.Document.Objects:
+ if o.isDerivedFrom("Fem::FemAnalysisPython"):
+ for m in o.Group:
+ if m == self.Object:
+ found_mesh_analysis = True
+ FemGui.setActiveAnalysis(o)
+ FreeCAD.Console.PrintMessage(
+ "The analysis the Netgen FEM mesh object "
+ "belongs to was found and activated: {}\n".format(
+ o.Name
+ )
+ )
+ gui_doc.setEdit(vobj.Object.Name)
+ break
+ if not found_mesh_analysis:
+ FreeCAD.Console.PrintLog(
+ "Netgen FEM mesh object does not belong to an analysis. "
+ "Analysis group meshing can not be used.\n"
+ )
+ gui_doc.setEdit(vobj.Object.Name)
+ else:
+ FreeCAD.Console.PrintError("Active analysis is not in active document.\n")
+ else:
+ FreeCAD.Console.PrintLog(
+ "No active analysis in active document, "
+ "we are going to have a look if the Netgen FEM mesh object "
+ "belongs to a non active analysis.\n"
+ )
+ found_mesh_analysis = False
+ for o in gui_doc.Document.Objects:
+ if o.isDerivedFrom("Fem::FemAnalysisPython"):
+ for m in o.Group:
+ if m == self.Object:
+ found_mesh_analysis = True
+ FemGui.setActiveAnalysis(o)
+ FreeCAD.Console.PrintMessage(
+ "The analysis the Netgen FEM mesh object "
+ "belongs to was found and activated: {}\n".format(o.Name)
+ )
+ gui_doc.setEdit(vobj.Object.Name)
+ break
+ if not found_mesh_analysis:
+ FreeCAD.Console.PrintLog(
+ "Netgen FEM mesh object does not belong to an analysis. "
+ "Analysis group meshing can not be used.\n"
+ )
+ gui_doc.setEdit(vobj.Object.Name)
+ else:
+ FreeCAD.Console.PrintLog("No analysis in the active document.\n")
+ gui_doc.setEdit(vobj.Object.Name)
+ else:
+ from PySide.QtGui import QMessageBox
+
+ message = "Active Task Dialog found! Please close this one before opening a new one!"
+ QMessageBox.critical(None, "Error in tree view", message)
+ FreeCAD.Console.PrintError(message + "\n")
+ return True
+
+ def dumps(self):
+ return None
+
+ def loads(self, state):
+ return None