Source code for gui.menus.Mesh

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program 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 General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""Mesh Menu

Plugin menu with operations on Mesh objects. Most options from the
generic Geometry menu are also applicable to Mesh type objects.
"""
import pyformex as pf
from pyformex.gui.projectmgr import projectmanager as pmgr
_I = pf.gui.widgets._I

##################### Mesh functions ######################################

[docs]def doOnSelectedMeshes(method): """Apply some method to all selected meshes""" pm = pmgr() if pm.check_sel(clas=pf.Mesh): meshes = [method(m) for m in pm.sel_values] pm.replace_sel(meshes)
[docs]def reverseMesh(): """Fuse the nodes of a Mesh""" doOnSelectedMeshes(pf.Mesh.reverse)
def removeDegenerate(): doOnSelectedMeshes(pf.Mesh.removeDegenerate) def removeDuplicate(): doOnSelectedMeshes(pf.Mesh.removeDuplicate)
[docs]def compactMesh(): """Compact the Mesh""" doOnSelectedMeshes(pf.Mesh.compact)
[docs]def peelOffMesh(): """Peel the Mesh""" doOnSelectedMeshes(pf.Mesh.peel)
[docs]def fuseMesh(): """Fuse the nodes of a Mesh""" pm = pmgr() if not pm.check_sel(clas=pf.Mesh): return res = pf.askItems(caption='Fuse parameters', items=[ _I('rtol', 1.e-5, text='Relative Tolerance'), _I('atol', 1.e-5, text='Absolute Tolerance'), _I('shift', 0.5, text='Shift'), _I('ppb', 1, text='Points per box')]) if not res: return meshes = pm.sel_values before = [m.ncoords() for m in meshes] meshes = [m.fuse(**res) for m in meshes] after = [m.ncoords() for m in meshes] print("Number of points before fusing: %s" % before) print("Number of points after fusing: %s" % after) pm.set_sel(meshes, suffix='_fused')
[docs]def smoothMesh(): """Smooth the Mesh.""" pm = pmgr() if not pm.check_sel(clas=pf.Mesh): return _name = 'Smoothing parameters' res = pf.askItems(caption=_name, store=_name+'_data', items=[ _I('niter', 1, min=1), _I('lamb', 0.5, min=0.0, max=1.0), _I('mu', -0.5, min=-1.0, max=0.0), _I('border', choices=['sep', 'fix', 'incl'],), _I('level', 1, min=0, max=3), _I('exclude', []), _I('weight', choices=['uniform', 'inverse', 'distance', 'sqinverse', 'sqdistance']), ],) if res: pm.replace_sel([m.smooth(**res) for m in pm.sel_values])
[docs]def subdivideMesh(): """Create a mesh by subdividing existing elements. """ pm = pmgr() if not pm.check_sel(clas=pf.Mesh): return meshes = pm.sel_values eltypes = {m.elName() for m in meshes} print("eltypes in selected meshes: %s" % eltypes) if len(eltypes) > 1: pf.warning("I can only subdivide meshes with the same element type\n" "Please narrow your selection before trying conversion.") return eltype = eltypes.pop() if eltype in ('line2', 'tri3'): items = [_I('ndiv', 4)] elif eltype == 'quad4': items = [_I('nx', 4), _I('ny', 4)] elif eltype == 'hex8': items = [_I('nx', 4), _I('ny', 4), _I('nz', 4)] else: pf.warning(f"Can not (yet) subdivide meshes of type {eltype}") return items.append(_I('fuse', True, tooltip="Fuse the nodes in the result")) res = pf.askItems(items) if not res: return if eltype == 'tri3': ndiv = [res['ndiv']] elif eltype == 'quad4': ndiv = [res['nx'], res['ny']] pm.replace_sel([m.subdivide(*ndiv, fuse=res['fuse']) for m in meshes])
[docs]def convertMesh(): """Transform the element type of the selected meshes. """ pm = pmgr() if not pm.check_sel(clas=pf.Mesh): return meshes = pm.sel_values eltypes = {m.eltype for m in meshes} print("eltypes in selected meshes: %s" % [str(e) for e in eltypes]) if len(eltypes) > 1: pf.warning("I can only convert meshes with the same element type\n" "Please narrow your selection before trying conversion.") return if len(eltypes) == 1: fromtype = eltypes.pop() choices = [f"{fromtype} -> {to}" for to in fromtype.conversions] if len(choices) == 0: pf.warning(f"Sorry, can not convert a {fromtype} mesh") return res = pf.askItems([ _I('_conversion', itemtype='vradio', text='Conversion Type', choices=choices), _I('_compact', True), _I('_merge', itemtype='hradio', text="Merge Meshes", choices=['None', 'Each', 'All']), ]) if res: _conversion = res['_conversion'] _compact = res['_compact'] _merge = res['_merge'] print("Selected conversion %s" % _conversion) totype = _conversion.split()[-1] meshes = [m.convert(totype) for m in meshes] if _merge == 'Each': meshes = [m.fuse() for m in meshes] elif _merge == 'All': from pyformex.mesh import mergeMeshes coords, elems = mergeMeshes(meshes) meshes = [pf.Mesh(coords, e, m.prop, m.eltype) for e, m in zip(elems, meshes)] if _compact: print("compacting meshes") meshes = [m.compact() for m in meshes] pm.set_sel(meshes, suffix='_converted')
[docs]def renumberMesh(order='elems'): """Renumber the nodes of the selected Meshes. """ pm = pmgr() if pm.check_sel(clas=pf.Mesh): pm.replace_sel([M.renumber(order) for M in pm.sel_values])
[docs]def getBorderMesh(): """Create the border Meshes for the selected Meshes. """ pm = pmgr() if pm.check_sel(clas=pf.Mesh): meshes = [M.borderMesh() for M in pm.sel_values] print("BEFORE", pm.selection) pm.set_sel(meshes, suffix='_border', fname='Mesh') print("AFTER", pm.selection)
def colorByFront(): pm = pmgr() if pm.check_sel(clas=pf.Mesh, single=True): M = pm.sel_values[0] res = pf.askItems([_I('front type', choices=['node', 'edge']), _I('number of colors', -1), _I('front width', 1), _I('start at', 0), _I('first prop', 0), ]) if res: pm.remember_sel() with pf.Timing() as t: ftype = res['front type'] nwidth = res['front width'] maxval = nwidth * res['number of colors'] startat = res['start at'] firstprop = res['first prop'] if ftype == 'node': p = M.frontWalk(level=0, maxval=maxval, startat=startat) else: p = M.frontWalk(level=1, maxval=maxval, startat=startat) M.setProp(p//nwidth + firstprop) nprops = len(M.propSet()) print(f"Colored in {nprops} parts ({t.mem:.6f} sec.)") pm.draw_sel() def partitionByConnection(): pm = pmgr() if pm.check_sel(clas=pf.Mesh, single=True): M = pm.sel_values[0] pm.remember_sel() with pf.Timing() as t: M.prop = M.partitionByConnection() nprops = M.prop.max()+1 print(f"Partitioned in {nprops} parts ({t.mem:.6f} sec.)") pm.draw_sel() ########## The menu ########## menu_items = [ ("&Reverse mesh elements", reverseMesh), ("&Convert element type", convertMesh), ("&Compact", compactMesh), ("&Fuse nodes", fuseMesh), ("&Remove degenerate", removeDegenerate), ("&Remove duplicate", removeDuplicate), ("&Renumber nodes", [ ("In element order", (renumberMesh, 'elems')), ("In random order", (renumberMesh, 'random')), ("In frontal order", (renumberMesh, 'front')), ]), ("&Subdivide", subdivideMesh), ("&Smooth", smoothMesh), ("&Get border mesh", getBorderMesh), ("&Peel off border", peelOffMesh), ("&Color By Front", colorByFront), ("&Partition By Connection", partitionByConnection), # ("---", None), ] # End