Merge pull request #16433 from marioalexis84/fem-mesh_netgen

Fem: New implementation of FemMesh Netgen object
This commit is contained in:
Yorik van Havre
2024-09-16 17:56:56 +02:00
committed by GitHub
13 changed files with 1689 additions and 16 deletions

View File

@@ -44,11 +44,21 @@
<UserDocu>Add an edge by setting two node indices.</UserDocu>
</Documentation>
</Methode>
<Methode Name="addEdgeList">
<Documentation>
<UserDocu>Add list of edges by list of node indices and list of nodes per edge.</UserDocu>
</Documentation>
</Methode>
<Methode Name="addFace">
<Documentation>
<UserDocu>Add a face by setting three node indices.</UserDocu>
</Documentation>
</Methode>
<Methode Name="addFaceList">
<Documentation>
<UserDocu>Add list of faces by list of node indices and list of nodes per face.</UserDocu>
</Documentation>
</Methode>
<Methode Name="addQuad">
<Documentation>
<UserDocu>Add a quad by setting four node indices.</UserDocu>
@@ -59,6 +69,11 @@
<UserDocu>Add a volume by setting an arbitrary number of node indices.</UserDocu>
</Documentation>
</Methode>
<Methode Name="addVolumeList">
<Documentation>
<UserDocu>Add list of volumes by list of node indices and list of nodes per volume.</UserDocu>
</Documentation>
</Methode>
<Methode Name="read">
<Documentation>
<UserDocu>Read in a various FEM mesh file formats.

View File

@@ -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<const SMDS_MeshNode*> nodes;
for (Py::List::iterator it = nodesList.begin(); it != nodesList.end(); ++it) {
Py::Long n(*it);
const SMDS_MeshNode* node = meshDS->FindNode(static_cast<int>(n));
if (!node) {
throw std::runtime_error("Failed to get node of the given indices");
}
nodes.push_back(node);
}
std::vector<const SMDS_MeshNode*>::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<const SMDS_MeshNode*> 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<const SMDS_MeshNode*> nodes;
for (Py::List::iterator it = nodesList.begin(); it != nodesList.end(); ++it) {
Py::Long n(*it);
const SMDS_MeshNode* node = meshDS->FindNode(static_cast<int>(n));
if (!node) {
throw std::runtime_error("Failed to get node of the given indices");
}
nodes.push_back(node);
}
std::vector<const SMDS_MeshNode*>::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<const SMDS_MeshNode*> 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<const SMDS_MeshNode*> nodes;
for (Py::List::iterator it = nodesList.begin(); it != nodesList.end(); ++it) {
Py::Long n(*it);
const SMDS_MeshNode* node = meshDS->FindNode(static_cast<int>(n));
if (!node) {
throw std::runtime_error("Failed to get node of the given indices");
}
nodes.push_back(node);
}
std::vector<const SMDS_MeshNode*>::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<const SMDS_MeshNode*> 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, "")) {

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NetgenMesh</class>
<widget class="QWidget" name="NetgenMesh">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>475</height>
</rect>
</property>
<property name="windowTitle">
<string>FEM Mesh by Netgen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gpb_mesh_params">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>1677215</height>
</size>
</property>
<property name="title">
<string>Mesh Parameters</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QFormLayout" name="formLayout_1">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="lbl_fineness">
<property name="text">
<string>Fineness:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cb_fineness"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="l_max">
<property name="text">
<string>Maximal Size:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::QuantitySpinBox" name="qsb_max_size">
<property name="enabled">
<bool>true</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>20</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="keyboardTracking">
<bool>true</bool>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>1000000000000000000000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1000.000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="l_min">
<property name="text">
<string>Minimal Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::QuantitySpinBox" name="qsb_min_size">
<property name="enabled">
<bool>true</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>20</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="keyboardTracking">
<bool>true</bool>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>1000000000000000000000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="ckb_second_order">
<property name="text">
<string>Second Order</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Growth Rate:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="dsb_growth_rate">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Curvature Safety:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="dsb_curvature_safety">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Segments Per Edge:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="dsb_seg_per_edge">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb03_run_netgen">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>1677215</height>
</size>
</property>
<property name="title">
<string>Netgen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="1">
<layout class="QGridLayout" name="gl_actions">
<item row="0" column="0">
<widget class="QTextEdit" name="te_output">
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="l_time">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Time:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0">
<widget class="QPushButton" name="pb_get_netgen_version">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Netgen version</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,297 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2024 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * 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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__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)

View File

@@ -0,0 +1,486 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2024 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * 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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__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

View File

@@ -0,0 +1,164 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2024 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * 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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__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

View File

@@ -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(

View File

@@ -0,0 +1,158 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2024 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * 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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__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