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 +
Gui/QuantitySpinBox.h
+
+
+ + +
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