Source code for dtOOPythonApp.builder.map3dTo3dGmsh_gridFromMultipleBoundedVolumeAndBlocks

#------------------------------------------------------------------------------
#  dtOO < design tool Object-Oriented >
#    
#    Copyright (C) 2024 A. Tismer.
#------------------------------------------------------------------------------
#License
#    This file is part of dtOO.
#
#    dtOO 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 LICENSE.txt file in the
#    dtOO root directory for more details.
#
#    You should have received a copy of the License along with dtOO.
#
#------------------------------------------------------------------------------

from dtOOPythonApp.tools.dtBundleTools import dtBundleBuilder

from dtOOPythonSWIG import jsonPrimitive
from dtOOPythonSWIG import analyticGeometry
from dtOOPythonSWIG import map2dTo3d
from dtOOPythonSWIG import map3dTo3d
from dtOOPythonSWIG import map3dTo3dGmsh
from dtOOPythonSWIG import bVOMeshRule
from dtOOPythonSWIG import bVOWriteMSH
from dtOOPythonSWIG import bVOReadMSH
from dtOOPythonSWIG import bVONameRegions
from dtOOPythonSWIG import bVOOrientCellVolumes
from dtOOPythonSWIG import bool_map1dTo3dInMap2dTo3d
from dtOOPythonSWIG import dtPoint3
from dtOOPythonSWIG import dtVector3
from dtOOPythonSWIG import bVOAnalyticGeometryToFace
from dtOOPythonSWIG import labeledVectorHandlingAnalyticGeometry
from dtOOPythonSWIG import scaOneD
from dtOOPythonSWIG import bVOFaceToPatchRule
from dtOOPythonSWIG import vectorInt
from dtOOPythonSWIG import dtGmshModel
from dtOOPythonSWIG import vectorReal
from dtOOPythonSWIG import scaTanhGradingOneD
from dtOOPythonSWIG import scaTanhGradingOneDCompound
from dtOOPythonSWIG import sca3PPointsBSplineOneD
from dtOOPythonSWIG import sca3PPointsBSplineOneDCompound
from dtOOPythonSWIG import bVOSetPrescribedElementSize
from dtOOPythonSWIG import bVOSetRotationalPeriodicity
from dtOOPythonSWIG import xYz_rPhiZ
from dtOOPythonSWIG import baseContainer
from dtOOPythonSWIG import multipleBoundedSurface 
from dtOOPythonSWIG import bVODumpModel

import numpy as np
from typing import List, Tuple, Union, Dict
import logging
import math

[docs] class map3dTo3dGmsh_gridFromMultipleBoundedVolumeAndBlocks(dtBundleBuilder): """Create mesh's topology as map3dTo3dGmsh. This class: - Creates the mesh toploogy of bladed channels - Adds the geometries of the unstructured region as a multiple bounded volume and a list of bounding surfaces. - Adds the mesh blocks. - Applies mesh settings. - Returns the toplology into the container of the calling class. Attributes ---------- label_: str Label. channel_: multipleBoundedVolume Channel. channelFaces_: List[ map2dTo3d ] List of bounding faces surrounding the channel blocks_: List[ map3dTo3d ] List of mesh blocks surrounding the blade. nMeanplaneBlocks_: int Number of block faces which are part of the meanplane blade_: map2dTo3d Blade. nBoundaryLayers_: int Number of boundary layers. nElementsSpanwise_: int Number of elements in spanwise direction. nElementsNormal_: int Number of elements on the blade surface. firstElementSizeHubToShroud_: float Size of first element on hub and shroud. firstElementSizeNormalBlade_: float Size of first element at the blade in normal to the blade direction. bladeHubElementSize_: scaOneD Function describing the element size versus the standardized unwrapped length of the blade at the hub. bladeHubElementScale_: float Factor defining the number of elements at the hub for each mesh block. bladeShroudElementSize_: scaOneD Function describing the element size versus the standardized unwrapped length of the blade at the shroud. bladeShroudElementScale_: float Factor defining the number of elements at the shroud for each mesh block. meshTEBlocks_: Bool Marker if trailing edge mesh blocks should be meshed map3dTo3dGmshJson_: jsonPrimitive JSON structure for map3dTo3dGmsh. Returns ------- None Examples -------- The main method of this class is :meth:`build`. The mesh topology is constructed from the channel, represented as a multiple bounded volume ``channel_``, and the blade mesh blocks provided in the list ``blocks_``. Throughout this documentation, ``N`` denotes the number of mesh blocks, i.e., ``len(blocks_)``. The mesh regions are numbered as follows: - ``R_0`` : Channel - ``R_1`` : First mesh block - ... - ``R_(N-1)`` : (N-1)th mesh block The multiple bounded volume is meshed using an unstructured mesh with prismatic boundary layers. The mesh block volumes are meshed as transfinite regions with recursive recombination. The topology settings are initialized from ``map3dTo3dGmshJson_``. The model is created as ``m3dGmsh``. The bounding surfaces of the multiple bounded volume are provided through the list ``channelFaces_``. The surfaces of the mesh topology are either taken directly from ``channelFaces_`` or extracted from the block volumes in ``blocks_`` using the method :meth:`detectFirstAndSecond`. This method takes a block volume as a ``map3dTo3d`` object and a parameter direction as an integer input. It returns the faces located at the normalized positions 0 and 1 along the specified parameter direction. The blade surface is provided through ``blade_``. The following figure illustrates the surfaces in the mesh topology. The hub and shroud surfaces are not shown. .. _meshFaces: .. figure:: bladeFigs/meshFaces_labeled.png :width: 100% :align: center Topology faces and their labels used in this class. Interfaces (red), periodic meanplane faces (yellow/green), coupling faces (cyan), and blade surfaces (gray) are highlighted. By iterating over ``blocks_``, the corresponding surfaces are added to the topology. For each mesh block, the blade surface is added and labeled with the identifier ``"blade"``. The number of mesh block surfaces that belong to the mean plane is specified by the input ``nMeanplaneBlocks_``. These surfaces do not connect to the multiple bounded volume channel. In this class, they are labeled with the identifier ``"block"``. The remaining faces shown in :numref:`meshFaces`, together with the hub, shroud, and coupling surfaces, are added to the topology through ``channelFaces_`` and labeled according to the strings shown in the figure. The coupling faces between the mesh blocks and the channel multiple bounded volume are labeled with the identifier ``"coupling"``. The hub and shroud bounding surfaces are labeled with the identifiers ``"hub"`` and ``"shroud"``, respectively. The periodic faces of the topology are those labeled ``"suction"``, ``"pressure"``, and ``"block"``. The labels ``"suction"`` and ``"pressure"`` correspond to the suction and pressure sides of a turbine blade. Periodically connected faces that are meshed using an unstructured mesh are labeled with the identifier ``"tri"``. The faces periodic to the ``"block"`` faces are meshed as transfinite surfaces and are assigned the identifier ``"quad"``. Periodicity on these faces is defined using the observer ``bVOSetRotationalPeriodicity``. The boolean value ``meshTEBlocks_`` controls whether trailing edge mesh blocks are created. If trailing edge mesh blocks are required, this parameter must be set to ``True``. Each face identifier is assigned an integer suffix for unique identification. The following faces are added to the topology when trailing edge mesh blocks are enabled: - ``"hub_0"`` ... ``"hub_"+str(N)`` - ``"shroud_0"`` ... ``"shroud_"+str(N)`` - ``"inlet_0"`` - ``"outlet_0"`` - ``"suction_tri_0"``, ``"suction_tri_1"``, ``"suction_tri_"+str(nMeanplaneBlocks_+3)``, ``"suction_tri_"+str(nMeanplaneBlocks_+4)`` - ``"block_0"`` ... ``"block_"+str(nMeanplaneBlocks_)`` - ``"pressure_tri_0"``, ``"pressure_tri_1"``, ``"pressure_tri_"+str(nMeanplaneBlocks_+3)``, ``"pressure_tri_"+str(nMeanplaneBlocks_+4)`` - ``"pressure_quad_2"`` ... ``"pressure_quad_"+str(nMeanplaneBlocks_+2)`` - ``"blade_0"`` ... ``"blade_"+str(N-1)`` - ``"coupling_0"`` ... ``"coupling_"+str(N+2-nMeanplaneBlocks_-1)`` Prismatic boundary layers are generated on the ``"hub"`` and ``"shroud"`` faces. The number of elements in these layers is specified by ``nBoundaryLayers_``. The boundary layers extend onto the ``"inlet"``, ``"outlet"``, ``"suction"``, ``"pressure"``, and ``"coupling"`` faces of the multiple bounded volume. The minimum and maximum characteristic mesh sizes of the unstructured mesh are specified by the inputs ``charLengthMin`` and ``charLengthMax``. The method :meth:`extractEdgesInFirstAndSecond` is used to identify the edges of a surface that lie on two other surfaces. The following figure illustrates the edge groups to which mesh settings are applied. Each group is highlighted using a uniform color. .. _channelMeshing0: .. figure:: bladeFigs/guideVane_channelMeshing.png :width: 95% :align: center Edges of the bladed channel to which mesh settings are applied: ``hubToShroudLines`` (orange), ``bladeHubLines`` and ``bladeShroudLines`` (blue), ``bladeToBlockLines`` (green), and the trailing edge mesh block edges contained in ``tEMeshList`` (pink). No explicit mesh settings are applied to the gray edges. Gradings are applied to refine the mesh near the hub, shroud, and blade walls. The method :meth:`addGrading` is used to create the grading functions. The methods :meth:`gradingsTypeTransfinite` and :meth:`gradingsGradingFunctions` are then used to apply these gradings to the mesh setting observer ``bVOMeshRule``. The following table summarizes the edge groups and their corresponding mesh parameters and settings: .. _edgeMeshTab: .. list-table:: Edges and their mesh parameters :header-rows: 1 :align: center * - Edge group - Number of elements / Element size - Grading label / First element size * - ``hubToShroudLines`` - ``nElementsSpanwise_`` - ``"hubToShroud"`` ``firstElementSizeHubToShroud_`` * - ``bladeHubLines`` - ``bladeHubElementSize_`` - ``"tangentialBlade_*"`` * - ``bladeShroudLines`` - ``bladeShroudElementSize_`` - ``"tangentialBlade_*"`` * - ``bladeToBlockLines`` - ``nElementsNormal_`` - ``"normalBlade"`` ``firstElementSizeNormalBlade_`` * - ``tEMeshList`` - ``nElementsNormal_`` - No grading The mesh parameters beginning with ``nElements...`` specify a fixed number of elements along each edge. The parameters beginning with ``firstElementSize...`` define the size of the first element adjacent to the wall on which the grading is applied. The mesh sizes along the blade contour (``bladeHubLines`` and ``bladeShroudLines``) are controlled by the functions ``bladeHubElementSize_`` and ``bladeShroudElementSize_``, respectively. For each edge, a minimum number of elements is first determined from its start and end vertices. The blending factors ``bladeHubElementScale_`` and ``bladeShroudElementScale_`` are then used to interpolate between the element counts at both ends, thereby defining the number of elements assigned to the edge. To ensure a smooth transition in element sizes between consecutive edges, the ``"tangentialBlade_*"`` grading is adjusted at both the start and end vertices. Trailing edge mesh blocks are generated only if ``meshTEBlocks_`` is set to ``True``. The corresponding edges are collected in the list ``tEMeshList``. No grading functions are applied to these edges. The observer ``bVOFaceToPatchRule`` is used to rename the following faces so that they match the boundary naming convention of the `OpenFOAM` case: .. _faceToPatchTable: .. list-table:: Renaming of the faces for `openFOAM` case setup. :header-rows: 1 :align: center * - Original name - Boundary name * - ``"*hub*"`` - ``label_+"_hub"`` * - ``"*shroud*"`` - ``label_+"_shroud"`` * - ``"*blade*"`` - ``label_+"_blade"`` * - ``"*inlet*"`` - ``label_+"_inlet"`` * - ``"*outlet*"`` - ``label_+"_outlet"`` * - ``"*suction*"`` - ``label_+"_suction"`` * - ``"*block*"`` - ``label_+"_suction"`` * - ``"*pressure*"`` - ``label_+"_pressure"`` If debug mode is enabled the geometries are plotted and prefixed with ``"debug_"``. The following observers are also added: - ``bVOReadMSH`` - ``bVODumpModel`` - ``bVOWriteMSH`` - ``bVOOrientCellVolumes`` Finally, the mesh topology is returned to the calling class through ``appendBoundedVolume``. A mesh resulting from this topology is shown in the following figure. .. _gvMesh: .. figure:: bladeFigs/guideVane_mesh.png :width: 95% :align: center Mesh of a bladed channel resultuing from the described topology. """ def __init__( self, label: str, channel: analyticGeometry, channelFaces: List[analyticGeometry], blocks: List[analyticGeometry], nMeanplaneBlocks: int, blade: analyticGeometry, nBoundaryLayers: int, nElementsSpanwise: int, nElementsNormal: int, firstElementSizeHubToShroud: float, firstElementSizeNormalBlade: float, bladeHubElementSize: scaOneD = None, bladeHubElementScale: float = None, bladeShroudElementSize: scaOneD = None, bladeShroudElementScale: float = None, charLengthMin: float = 0.05, charLengthMax: float = 0.10, meshTEBlocks: bool = False ) -> None: """ Parameters ---------- label: str Label. channel: multipleBoundedVolume Channel. channelFaces: List[ map2dTo3d ] List of bounding faces surrounding the channel blocks: List[ map3dTo3d ] List of mesh blocks surrounding the blade. nMeanplaneBlocks: int Number of block faces which are part of the meanplane blade: map2dTo3d Blade. nBoundaryLayers: int Number of boundary layers. nElementsSpanwise: int Number of elements in spanwise direction. nElementsNormal: int Number of elements in normal to the blade direction. firstElementSizeHubToShroud: float Size of first element on hub and shroud. firstElementSizeNormalBlade: float Size of first element at the blade in normal to the blade direction. bladeHubElementSize: scaOneD Function describing the element size versus the standardized unwrapped length of the blade at the hub. bladeHubElementScale: float Factor defining the number of elements at the hub for each mesh block. bladeShroudElementSize: scaOneD Function describing the element size versus the standardized unwrapped length of the blade at the shroud. bladeShroudElementScale: float Factor defining the number of elements at the shroud for each mesh block. charLengthMin: float Sets gmsh attribute `Mesh.CharacteristicLengthMin`. charLengthMax: float Sets gmsh attribute `Mesh.CharacteristicLengthMax`. meshTEBlocks: Bool Marker if trailing edge mesh blocks should be meshed """ logging.info( "Initializing %s ..." % (label) ) super(map3dTo3dGmsh_gridFromMultipleBoundedVolumeAndBlocks, self).__init__() self.label_ = label self.channel_ = channel.clone() self.channelFaces_ = channelFaces self.blocks_ = [] for block in blocks: self.blocks_.append( map3dTo3d.MustDownCast(block.clone()) ) self.nMeanplaneBlocks_ = nMeanplaneBlocks self.blade_ = map2dTo3d.MustDownCast( blade.clone() ) self.nBoundaryLayers_ = nBoundaryLayers self.nElementsSpanwise_ = nElementsSpanwise self.nElementsNormal_ = nElementsNormal self.firstElementSizeHubToShroud_ = firstElementSizeHubToShroud self.firstElementSizeNormalBlade_ = firstElementSizeNormalBlade self.bladeHubElementSize_ = bladeHubElementSize self.bladeHubElementScale_ = bladeHubElementScale self.bladeShroudElementSize_ = bladeShroudElementSize self.bladeShroudElementScale_ = bladeShroudElementScale self.meshTEBlocks_ = meshTEBlocks self.map3dTo3dGmshJson_ = jsonPrimitive( '{' '"label" : "'+self.label_+'", ' '"option" : [' '{"name" : "[gmsh]General.Terminal", "value" : "1."},' '{"name" : "[gmsh]General.Verbosity", "value" : "100."},' '{"name" : "[gmsh]General.ExpertMode", "value" : "1."},' '{' '"name" : "[gmsh]Mesh.LcIntegrationPrecision", ' '"value" : "1.0E-04"' '},' '{' '"name" : "[gmsh]Mesh.CharacteristicLengthMin", ' '"value" : "'+str(charLengthMin)+'"' '},' '{' '"name" : "[gmsh]Mesh.CharacteristicLengthMax", ' '"value" : "'+str(charLengthMax)+'"' '},' '{"name" : "[gmsh]Mesh.Algorithm", "value" : "1"},' '{' '"name" : "[gmsh]Mesh.MeshSizeExtendFromBoundary", ' '"value" : "1"' '},' '{"name" : "[gmsh]Mesh.MeshSizeFromPoints", "value" : "1"}' '],' '"analyticGeometry" : []' '}' )
[docs] def build(self) -> None: """Build part. Parameters ---------- None Returns ------- None The model is initialized as ``m3dGmsh`` from ``map3dTo3dGmshJson_``. The container ``aG`` of type ``labeledVectorHandlingAnalyticGeometry`` is created to manage the analytic geometries. **Add the Grid Channel Volume and Faces** The multiple bounded volume of the grid channel``channel_`` is added to the model. Its bounding faces are added by iterating over the list ``channelFaces_``. Regular faces, for which ``multipleBoundedSurface.ConstDownCast(face) == None`` applies, are added directly to ``aG``. The hub and shroud faces are of the type ``multipleBoundedSurface``. For these faces, the ``else`` branch is executed and their bounding surfaces are added individually. The face labels are assigned during the generation of the multiple bounded volume in the class `multipleBoundedVolume_gridChannel`. **Add the Mesh Block Volumes and Faces** The block volumes and their faces are added by iterating over ``blocks_`` using ``i, block in enumerate(blocks_)``. The block faces on the blade wall and the surrounding surfaces are extracted using the method :meth:`detectFirstAndSecond`. The faces on the blade wall are labeled ``"blade_" + str(i)``, while the surrounding surfaces are labeled ``"block_" + str(i)``. The block volumes are ordered so that their sequence in ``blocks_`` follows the u-direction of the blade surface ``blade_``. If trailing edge mesh blocks exist, they correspond to the first and last entries of ``blocks_``. If trailing edge mesh blocks are enabled (``meshTEBlocks_ == True``), the blade faces of the first and last mesh blocks are not added to ``aG``. Only block faces that are part of the mean plane are added to ``aG`` (``i <= nMeanplaneBlocks_``), corresponding to the ``"block"`` faces shown in :numref:`meshFaces`. The block volumes are added to the model as ``dtRegion`` objects and configured to be meshed using transfinite meshing with recursive recombination. The observer ``bVONameRegions`` is added to establish the naming convention of the regions. **Organize Edges of the Trailing Edge Mesh Blocks** The edges of the trailing edge mesh blocks (shown in pink in :numref:`channelMeshing0`) are extracted by first obtaining the blade and block faces of ``blocks_[0]`` and ``blocks_[-1]`` using :meth:`detectFirstAndSecond`. Their hub and shroud edges are then identified using :meth:`extractEdgesInFirstAndSecond`. Using these edges, the list ``tEMeshList`` is constructed with the following structure: .. code-block:: python tEMeshList = List[ Tuple[ Tuple[List[int], List[int]], int ] ] The top level list entries have the following meaning: - ``tEMeshList[0]`` : Edges extending directly from the blade - ``tEMeshList[1]`` : Edges extending from the outer wall of the first mesh block - ``tEMeshList[2]`` : Edges extending from the outer wall of the last mesh block The lower level entries of ``tEMeshList`` are defined as follows: - ``tEMeshList[i][0]`` : Tuple containing lists of edge identifiers - ``tEMeshList[i][0][0]`` : List of edge identifiers on the hub - ``tEMeshList[i][0][1]`` : List of edge identifiers on the shroud - ``tEMeshList[i][1]`` : Integer specifying the edge direction **Manage Faces** The observer ``bVOAnalyticGeometryToFace`` is added to implement the faces stored in ``aG`` within ``m3dGmsh``. The periodic faces (shown in yellow and green in :numref:`meshFaces`) are organized in the list ``periodics``. The list is constructed such that each entry ``periodics[i]`` is a ``Tuple`` containing a pair of periodic faces. The suction side boundary is stored in ``periodics[i][0]`` and the corresponding pressure side boundary in ``periodics[i][1]``. The faces that are meshed unstructured, and their hub-to-shroud edges, are stored in the list ``unstrFacesAndh2sLines``. These faces are identified in ``aG`` by their physical labels ``"inlet"``, ``"outlet"``, ``"suction_tri"``, and ``"pressure_tri"`` (see :numref:`meshFaces`). The list has the following structure: .. code-block:: python unstrFacesAndh2sLines = List[ List[ map2dTo3d, List[int] ] ] Each entry contains an unstructured face, ``unstrFacesAndh2sLines[i][0]``, and the list of its edges that extend from hub to shroud, ``unstrFacesAndh2sLines[i][1]``. **Manage Edges and Set Number of Elements** The edges to which mesh settings are applied (see :numref:`channelMeshing0`) are identified by extracting and organizing lists of edge identifiers returned by the ``dtGmshModel``. The following edge identifier lists are used to define the mesh settings: - ``hubToShroudLines`` - ``bladeToBlockLines`` - ``bladeHubLines`` - ``bladeShroudLines`` The dictionary ``gradings`` is created, and grading functions for the edges in ``hubToShroudLines`` and ``bladeToBlockLines`` are added using the method :meth:`addGrading`. This method takes the ``gradings`` dictionary, a grading function, a label, the model, and the size of the first element in the grading as input. The grading associated with ``hubToShroudLines`` is assigned the label ``"hubToShroud"`` and uses the first element size ``firstElementSizeHubToShroud_``. The grading associated with ``bladeToBlockLines`` is assigned the label ``"normalBlade"`` and uses the first element size ``firstElementSizeNormalBlade_``. The number of elements and the grading functions are then applied to the edges according to the specifications listed in :numref:`edgeMeshTab`. The mesh settings for the blade edges ``bladeShroudLines`` and ``bladeHubLines`` are applied by iterating over the corresponding edge lists. Mesh settings for the trailing edge mesh block edges are applied only if ``meshTEBlocks_ == True``. **Mesh Settings along the Blade** The orientation of the blade edges is determined using :meth:`boundaryEdgeDirection`. The method is provided with ``m3dGmsh.getModel()`` and a list containing the blade surface ``blade_`` together with the corresponding edge lists. For each edge, the edge length ``eL`` is computed and the start and end vertices ``v0`` and ``v1`` are identified. These points are reparameterized onto the blade surface ``blade_``, yielding the surface parameter coordinates ``p0_uv`` and ``p1_uv``. Depending on the iteration the element size functions ``bladeHubElementSize_`` or ``bladeShroudElementSize_`` are then evaluated at the corresponding parameter coordinates to obtain the local element sizes ``ms_0`` and ``ms_1`` in the appropriate parameter direction. Using these element sizes and the edge length ``eL``, the required numbers of elements, ``nE_0`` and ``nE_1``, are computed and rounded up to the next integer. The final number of elements assigned to the blade edge is calculated as .. code-block:: python nE = math.ceil( min(nE_0, nE_1) + elementScale * abs(nE_1 - nE_0) ) The floating point value ``elementScale`` corresponds to either ``bladeShroudElementScale_`` or ``bladeHubElementScale_``, depending on the current iteration. For each edge, a grading function with the label ``"tangentialBlade_*"`` is created. The start and end element sizes of the grading are set to ``ms_0`` and ``ms_1``, respectively. **Set Mesh Rules** The boundary layer directions of the unstructured faces stored in ``unstrFacesAndh2sLines`` are determined using :meth:`detectBoundaryLayerDir`, which returns the list ``boundaryLayerDir``. The mesh rules are defined using the observer ``bVOMeshRule``. The methods :meth:`gradingsTypeTransfinite` and :meth:`gradingsGradingFunction` are used to retrieve the appropriate grading information from the ``gradings`` dictionary and pass it to the observer. Meshing of the unstructured region is performed using the rules ``"dtMeshGFaceWithTransfiniteLayer"`` and ``"dtMeshGRegionWithBoundaryLayer"``. The rule ``"dtMeshGFaceWithTransfiniteLayer"`` is applied to the faces that are meshed unstructured, namely ``"*inlet*"``, ``"*outlet*"``, ``"*suction_tri*"`` and ``"*pressure_tri*"``. The rule ``"dtMeshGRegionWithBoundaryLayer"`` is applied to the region corresponding to the multiple bounded volume, ``"R_0"``. The hub and shroud faces, ``"hub_0"`` and ``"shroud_0"``, on which the boundary layers are generated, are added to the ``"_faceLabel"`` entry. The boundary layers extend onto the faces of ``"R_0"`` labeled ``"*inlet*"``, ``"*outlet*"``, ``"*pressure_*"``, ``"*suction_*"``, and ``"*coupling_*"``. These faces are added to the ``"_slidableFaceLabel"`` entry. The number of boundary layer elements is specified by ``nBoundaryLayers_``, while the boundary layer orientation is defined by ``boundaryLayerDir``. **Define Observers** The observers ``bVOReadMSH`` and ``bVODumpModel`` are then added. To define rotational periodicity, a reference coordinate system ``theT`` is created and added to the base container object ``bC``. The periodic boundary conditions are established by iterating over ``periodics`` and creating a ``bVOSetRotationalPeriodicity`` observer for each pair of periodic faces. The faces are renamed to match the boundary condition naming convention used in an OpenFOAM case through the observer ``bVOFaceToPatchRule``. The corresponding renaming rules are summarized in :numref:`faceToPatchTable`. The observer ``bVOWriteMSH`` controls the generation of the mesh file. The observer ``bVOOrientCellVolumes`` ensures that all mesh cell volumes have a positive orientation. The created mesh topology ``m3dGmsh`` is returned to the calling class using the method ``appendBoundedVolume``. """ logging.info( "Building %s ..." % (self.label_) ) m3dGmsh = map3dTo3dGmsh() m3dGmsh.jInit( self.map3dTo3dGmshJson_, None, None, None, None, None ) # # create lvh # aG = labeledVectorHandlingAnalyticGeometry() # # add channel (multiple bounded volume (MBV)) # channelId = m3dGmsh.getModel().addIfToGmshModel( self.channel_ ) # # add bounding faces of the multiple bounded volume # for face in self.channelFaces_: # not multi bounded faces # those are: inlet, outlet suction, pressure and coupling faces if multipleBoundedSurface.ConstDownCast(face) == None: aG.push_back(face.clone()) # multi bounded faces # those are: hub and shroud faces else: # surfaceConstPtr returns the rectangular bounding box in which the mbs was created scp = multipleBoundedSurface.MustDownCast(face).surfaceConstPtr() scp.setLabel(face.getLabel()) aG.push_back(scp.clone()) # # add mesh blocks # for i, block in enumerate(self.blocks_): # find face on blade and block (parallel to blade) bladeFace, blockFace = self.detectFirstAndSecond(block, 3) if self.meshTEBlocks_ == True: # bladeFace at i == 0 and i == len(self.blocks_)-1 are the same # when trailing edge blocks were build. # this face is not added if i != 0 and i < len(self.blocks_)-1: aG.push_back(bladeFace << "blade_"+str(i)) else: aG.push_back(bladeFace << "blade_"+str(i)) # only the block faces which are part of the meanplane are pushed if i <= self.nMeanplaneBlocks_: aG.push_back(blockFace << "block_"+str(i)) # add block volumes, those are 6-sided and meshed transfinite rid = m3dGmsh.getModel().addIfRegionToGmshModel(block) m3dGmsh.getModel().getDtGmshRegionByTag( rid ).meshTransfiniteRecursive() m3dGmsh.getModel().getDtGmshRegionByTag( rid ).meshWNElements(1,1,1) m3dGmsh.getModel().getDtGmshRegionByTag( rid ).meshRecombineRecursive() # # name regions # # - R_0 : channel # - R_1 : First mesh block # - ... # - R_(N-1) : (N-1)^th mesh block # - R_(N) : (N)^th mesh block # ob = bVONameRegions() ob.jInit( jsonPrimitive('{ "_regionLabel" : [] }'), m3dGmsh ) ob.preUpdate() # # trailing edge mesh block edges extending in flow direction # from the blade, the first and the last block # if self.meshTEBlocks_ == True: # faces from hub to shroud # first block tEBlade, tEBlock0 = self.detectFirstAndSecond( self.blocks_[0], 3 ) # last block tEBlade, tEBlock1 = self.detectFirstAndSecond( self.blocks_[-1], 3 ) # edges on hub and shroud # trailing edge tEHub, tEShroud = self.extractEdgesInFirstAndSecond( m3dGmsh.getModel(), [tEBlade], map2dTo3d.MustDownCast(aG["hub"]), map2dTo3d.MustDownCast(aG["shroud"]) ) # first block tEBlock0_Hub, tEBlock0_Shroud = self.extractEdgesInFirstAndSecond( m3dGmsh.getModel(), [tEBlock0], map2dTo3d.MustDownCast(aG["hub"]), map2dTo3d.MustDownCast(aG["shroud"]) ) # last block tEBlock1_Hub, tEBlock1_Shroud = self.extractEdgesInFirstAndSecond( m3dGmsh.getModel(), [tEBlock1], map2dTo3d.MustDownCast(aG["hub"]), map2dTo3d.MustDownCast(aG["shroud"]) ) # create a mesh list containing the edges and a float # >/<0 for grading directions tEMeshList = [ [[tEHub, tEShroud], -1.0], [[tEBlock0_Hub, tEBlock0_Shroud], -1.0], [[tEBlock1_Hub, tEBlock1_Shroud], 1.0] ] # # name faces # ob = bVOAnalyticGeometryToFace() ob.jInit( jsonPrimitive( '{' '"analyticGeometry" : [' '{"label" : "hub"},' '{"label" : "shroud"},' '{"label" : "inlet"},' '{"label" : "outlet"},' '{"labels" : "suction_*"},' '{"labels" : "pressure_*"},' '{"labels" : "blade_*"},' '{"labels" : "block_*"},' '{"labels" : "coupling_*"}' '],' '"_inc" : 10.0,' '"_facesPerEntry" : []' '}' ), None, None, None, aG, None, m3dGmsh ) ob.preUpdate() # # Manage Faces and Edges # # organize periodic faces # the order is set by adding the faces seqentially and the naming periodic_pressure = m3dGmsh.getModel().getDtGmshFaceListByPhysical("*pressure_*") # suction faces periodic_suction = [] periodic_suction.append(m3dGmsh.getModel().getDtGmshFaceByPhysical("*suction_tri_0*")) periodic_suction.append(m3dGmsh.getModel().getDtGmshFaceByPhysical("*suction_tri_1*")) periodic_block = m3dGmsh.getModel().getDtGmshFaceListByPhysical("*block_*") for face in periodic_block: periodic_suction.append(face) l = len(periodic_suction) periodic_suction.append(m3dGmsh.getModel().getDtGmshFaceByPhysical("*suction_tri_"+str(l)+"*")) periodic_suction.append(m3dGmsh.getModel().getDtGmshFaceByPhysical("*suction_tri_"+str(l+1)+"*")) # list with periodic faces periodics = list(zip(periodic_suction, periodic_pressure)) # all hub to shroud lines hubToShroudLines = \ m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*hub*", "*shroud*" ) # unstructured faces and their lines, they will later get a boundary layer # structure of the resulting list: # unstrFacesAndh2sLines = List[List[face0, List[edges0]]] unstrFacesAndh2sLines = [] for i, face in enumerate(aG): # faces with boundary layer if face.getLabel().startswith( ("inlet", "outlet", "suction_tri_", "pressure_tri_") ): f = map2dTo3d.MustDownCast(face) # get all edges on the face faceLines = m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*"+face.getLabel()+"*", "*"+face.getLabel()+"*" ) # keep the edges extending from hub to shroud hsLines = ( set(faceLines)&set(hubToShroudLines) ).union( set(faceLines)&set(-np.array(hubToShroudLines)) ) unstrFacesAndh2sLines.append([f, hsLines]) # getting other relevant edges hubLines = \ m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*hub*", "*hub*" ) shroudLines = \ m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*shroud*", "*shroud*" ) hAndSLines = set(hubLines).union(set(shroudLines)) bladeToBlockLines_0 = \ m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*blade_*", "*block_*" ) bladeToBlockLines_1 = \ m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*blade_*", "*coupling_*" ) bladeToBlockLines = set(bladeToBlockLines_0).union(set(bladeToBlockLines_1)) bladeToBlockLines = set(bladeToBlockLines)&set(hAndSLines) bladeLines = \ m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "*blade_*", "*blade_*" ) bladeHubLines = set(bladeLines)&set(hubLines) bladeShroudLines = set(bladeLines)&set(shroudLines) # trailing edge hub and shroud edges have to be removed here if # trailing edge mesh blocks exist if self.meshTEBlocks_ == True: bladeHubLines = \ set(bladeHubLines)-set(-np.array(tEHub))-set(tEHub) bladeShroudLines = \ set(bladeShroudLines)-set(-np.array(tEShroud))-set(tEShroud) # # add debug faces and line # if self.debug(): ## curves for visualisation purposes only #tEList = [tEHub[0], tEShroud[0], tEBlock0_Hub[0], tEBlock0_Shroud[0], tEBlock1_Hub[0], tEBlock1_Shroud[0]] #otherHubLines = set(hubLines) \ # - set([-x for x in bladeHubLines]) \ # - set(bladeHubLines) #otherHubLines = set(otherHubLines) \ # - set([-x for x in bladeToBlockLines]) \ # - set(bladeToBlockLines) #otherHubLines = set(otherHubLines) \ # - set([-x for x in tEList]) \ # - set(tEList) # #otherShroudLines = set(shroudLines) \ # - set([-x for x in bladeShroudLines]) \ # - set(bladeShroudLines) #otherShroudLines = set(otherShroudLines) \ # - set([-x for x in bladeToBlockLines]) \ # - set(bladeToBlockLines) #otherShroudLines = set(otherShroudLines) \ # - set([-x for x in tEList]) \ # - set(tEList) # all faces for faceLabel in [ "*hub*", "*shroud*", "*inlet*", "*outlet*", "*suction_*", "*pressure_*", "*blade_*", "*block_*", "*coupling_*" ]: faces = m3dGmsh.getModel().getDtGmshFaceListByPhysical(faceLabel) for face in faces: self.appendAnalyticGeometry( face.getMap2dTo3d(), "debug_faceAll_"+self.label_+"_"+face.getPhysicalString() ) # slidable faces for boundary layer meshing of MBV for faceLabel in [ "*inlet*", "*outlet*", "*pressure_*", "*suction_*", "*coupling_*" ]: faces = m3dGmsh.getModel().getDtGmshFaceListByPhysical(faceLabel) for face in faces: self.appendAnalyticGeometry( face.getMap2dTo3d(), "debug_faceMBV_"+self.label_+"_"+face.getPhysicalString() ) # lines to which element numbers and gradings are assigned for lab, lines in zip( [ "hubToShroudLines", "bladeToBlockLines", "bladeHubLines", #"TrailingEdgeLines", "bladeShroudLines", #"otherHubLines", #"otherShroudLines" ], [ hubToShroudLines, bladeToBlockLines, bladeHubLines, #tEList, bladeShroudLines, #otherHubLines, #otherShroudLines ] ): for ii in lines: self.appendAnalyticGeometry( m3dGmsh.getModel().getDtGmshEdgeByTag( ii ).getMap1dTo3d(), "debug_lineNE_"+self.label_+"_"+lab+"_"+str(ii) ) # faces and lines which will be meshed unstructured with boundary layer for faceAndLines in unstrFacesAndh2sLines: self.appendAnalyticGeometry( faceAndLines[0], "debug_faceUnstr_"+self.label_+"_"+faceAndLines[0].getLabel() ) for i, line in enumerate(faceAndLines[1]): self.appendAnalyticGeometry( m3dGmsh.getModel().getDtGmshEdgeByTag(line).getMap1dTo3d(), "debug_lineUnstr_"+self.label_+"_"+faceAndLines[0].getLabel()+"_"+str(i) ) # periodic boundary faces for i, periodic in enumerate(periodics): self.appendAnalyticGeometry( periodic[0].getMap2dTo3d(), "debug_periodic0_"+str(i)+"_"+periodic[0].getPhysicalString() ) self.appendAnalyticGeometry( periodic[1].getMap2dTo3d(), "debug_periodic1_"+str(i)+"_"+periodic[1].getPhysicalString() ) # # gradings # gradings = {} # hub to shroud gradings = self.addGrading( gradings, scaTanhGradingOneDCompound( scaTanhGradingOneD( vectorReal([0.5, 0.5, -1.0, 2.0]), 1.0, 0.1, 5.0 ) ), "hubToShroud", m3dGmsh, self.firstElementSizeHubToShroud_ ) # blade normal gradings = self.addGrading( gradings, scaTanhGradingOneDCompound( scaTanhGradingOneD( vectorReal([1.0, -1.0, 1.0, -1.0]), 1.0, 0.1, 5.0 ) ), "normalBlade", m3dGmsh, self.firstElementSizeNormalBlade_ ) # getting blade direction for grading along the blade bladeHubShroudDirection = self.boundaryEdgeDirection( m3dGmsh.getModel(), [ [self.blade_, bladeHubLines], [self.blade_, bladeShroudLines], ] ) logging.info("bladeHubShroudDirection = %d" % bladeHubShroudDirection) # # mesh settings # # hub to shroud for line in hubToShroudLines: theEdge = m3dGmsh.getModel().getDtGmshEdgeByTag( line ) theEdge.setNElements( self.nElementsSpanwise_ ) theEdge.setGrading( 1.0, gradings["hubToShroud"][0] ) # along blade for lines, elementSize, elementScale, outStr in zip( [bladeShroudLines, bladeHubLines,], [self.bladeShroudElementSize_, self.bladeHubElementSize_,], [self.bladeShroudElementScale_, self.bladeHubElementScale_,], ["BladeShroud", "BladeHub",], ): direction = bladeHubShroudDirection for line in lines: theEdge = m3dGmsh.getModel().getDtGmshEdgeByTag( line ) if elementSize!=None: eL = theEdge.getMap1dTo3d().length() v0 = theEdge.getMap1dTo3d().getPointPercent(0.0) v1 = theEdge.getMap1dTo3d().getPointPercent(1.0) p_0_uv = self.blade_.reparamPercentOnFace( v0 ) p_1_uv = self.blade_.reparamPercentOnFace( v1 ) ms_0 = elementSize( p_0_uv[direction] )[0] ms_1 = elementSize( p_1_uv[direction] )[0] nE_0 = math.ceil(eL/ms_0) nE_1 = math.ceil(eL/ms_1) nE = math.ceil( min(nE_0,nE_1) + elementScale * abs(nE_1 - nE_0) ) logging.info( "\n %s Edge %d / length %f " "\n p_uv: (%f, %f) -> (%f, %f) " "\n meshLength: (%f) -> (%f) " "\n nElements (%f -> %d) -> (%f -> %d) => %d" % ( outStr, line, eL, p_0_uv[0], p_0_uv[1], p_1_uv[0], p_1_uv[1], ms_0, ms_1, eL/ms_0, nE_0, eL/ms_1, nE_1, nE ) ) theEdge.meshTransfiniteWNElements( 1, 1.0, nE ) gradings = self.addGrading( gradings, sca3PPointsBSplineOneDCompound( sca3PPointsBSplineOneD( 1./nE, ms_0 / eL, 1.-1./nE, (eL-ms_1) / eL, ) ), "tangentialBlade_"+str(line), m3dGmsh, ms_0, ms_1 ) theEdge.setGrading( 1.0, gradings["tangentialBlade_"+str(line)][0] ) else: theEdge.meshTransfiniteWNElements( 1, 1.0, 5 ) # from blade to block for line in bladeToBlockLines: theEdge = m3dGmsh.getModel().getDtGmshEdgeByTag( line ) theEdge.setNElements( self.nElementsNormal_ ) theEdge.setGrading( 1.0, gradings["normalBlade"][0] ) # makes mesh settings for Trailing edge mesh blocks if self.meshTEBlocks_ == True: # meshing trailing edge lines, tEMeshList has the following format: # tEMeshList = [ # [[[tEHub], [tEShroud]], -1.0], # ... # ] for meshList in tEMeshList: for edgeTag in meshList[0]: edge = m3dGmsh.getModel().getDtGmshEdgeByTag(edgeTag[0]) edge.setNElements( self.nElementsNormal_ ) #edge.setGrading( meshList[1], gradings["normalBlade"][0] ) # # add observers # # read mesh ob = bVOReadMSH() ob.thisown = False ob.jInit( jsonPrimitive( '{' '"_filename" : "",' '"_mustRead" : false' '}' ), None, None, None, None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) # # rotational periodicity # theT = xYz_rPhiZ() theT.jInit( jsonPrimitive()\ .appendStr("label", "xYz_rPhiZ")\ .appendDtPoint3("_origin", dtPoint3(0,0,0))\ .appendDtVector3("_rotAxis", dtVector3(0,0,-1))\ .appendDtVector3("_refAxis", dtVector3(1,0,0)), None, None, None, None ) bC = baseContainer() bC.ptrTransformerContainer().add( theT ) # add observers for all periodic faces for periodic in periodics: ob = bVOSetRotationalPeriodicity() ob.jInit( jsonPrimitive()\ .appendStr("_faceMaster", periodic[0].getPhysicalString())\ .appendStr("_faceSlave", periodic[1].getPhysicalString())\ .appendDtTransformer( "_dtT", theT ), bC, None, None, None, None, m3dGmsh ) ob.preUpdate() # dump model information ob = bVODumpModel() ob.thisown = False ob.jInit( jsonPrimitive(), None, None, None, None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) # getting boundary layer direction for unstructured faces # with transfinite layers boundaryLayerDir = self.detectBoundaryLayerDirection( m3dGmsh.getModel(), unstrFacesAndh2sLines ) # # setting mesh rules # ob = bVOMeshRule() ob.thisown = False ob.jInit( jsonPrimitive( '{' '"option" : [' '{"name" : "debug", "value" : "true"}' '],' '"_rule1D" : [' '"dtMeshFreeGradingGEdge(*)"' '],' '"_rule2D" : [' '"dtMeshGFaceWithTransfiniteLayer(*inlet*)",' '"dtMeshGFaceWithTransfiniteLayer(*outlet*)",' '"dtMeshGFaceWithTransfiniteLayer(*suction_tri_*)",' '"dtMeshGFaceWithTransfiniteLayer(*pressure_tri_*)",' '"dtMeshTransfiniteGFace(*)",' '"dtMeshGFace(*)"' '],' '"_rule3D" : [' '"dtMeshGRegionWithBoundaryLayer(R_0)",' '"dtMeshGRegion(*)"' '],' '"_only" : [],' '"dtMeshOperator" : [' '{' '"name" : "dtMeshFreeGradingGEdge",' '"label" : "dtMeshFreeGradingGEdge",' '"typeTransfinite" : '+self.gradingsTypeTransfinite(gradings)+',' '"gradingFunctions" : {' '"analyticFunction" ' ': '+self.gradingsGradingFunctions(gradings)+' ' '}' '},' '{' '"name" : "dtMeshGEdge",' '"label" : "dtMeshGEdge"' '},' '{' '"name" : "dtMeshGFace",' '"label" : "dtMeshGFace"' '},' '{' '"name" : "dtMeshTransfiniteGFace",' '"label" : "dtMeshTransfiniteGFace"' '},' '{' '"name" : "dtMeshGRegion",' '"label" : "dtMeshGRegion",' '"_minQShapeMetric" : 0.0,' '"_relax" : 0.1,' '"_nPyramidOpenSteps" : 10,' '"_nSmooths" : 3' '},' '{' '"option" : [' '{"name" : "debug", "value" : "true"}' '],' '"name" : "dtMeshGRegionWithBoundaryLayer",' '"label" : "dtMeshGRegionWithBoundaryLayer",' #'"_nSpacingSteps" : [' #+str(self.nBoundaryLayers_-1)+', ' # +str(self.nBoundaryLayers_-1)+ #'],' '"_nSpacingSteps" ' ': '+str( [-(self.nBoundaryLayers_-1), -(self.nBoundaryLayers_-1)] )+',' '"_nNormalSmoothingSteps" : 1000,' '"_nGrowingSmoothingSteps" : 1000,' '"_maxGrowingRatePerStep" : 1.10,' '"_maxDihedralAngle" : '+str(float(np.pi*360./180.))+',' '"_faceLabel" : [' '"hub_0", "shroud_0"' '],"' '_slidableFaceLabel" : [' '"*inlet*", "*outlet*", "*pressure_*", "*suction_*", "*coupling_*"' '],' '"dtMesh3DOperator" : "dtMeshGRegion",' '"_fixedFaceLabel" : [' ']' '},' +jsonPrimitive()\ .appendStr("name", "dtMeshGFaceWithTransfiniteLayer")\ .appendStr("label", "dtMeshGFaceWithTransfiniteLayer")\ .appendVectorInt("_nLayers", vectorInt( [ self.nBoundaryLayers_, self.nBoundaryLayers_ ] ) )\ .appendInt("_direction", boundaryLayerDir)\ .appendInt("_nSmooth", 3)\ .toStdString()+ ']' '}' ), None, None, self.lVH_aF(), None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) # face to patch rule ob = bVOFaceToPatchRule() ob.thisown = False ob.jInit( jsonPrimitive( '{' '"_patchRule" : [' '":*hub*::'+self.label_+'_hub:",' '":*shroud*::'+self.label_+'_shroud:",' '":*blade*::'+self.label_+'_blade:",' '":*inlet*::'+self.label_+'_inlet:",' '":*outlet*::'+self.label_+'_outlet:",' '":*suction*::'+self.label_+'_suction:",' '":*block*::'+self.label_+'_suction:",' '":*pressure*::'+self.label_+'_pressure:",' '":*coupling*:::"' '],' '"_regRule" : [' '":*::'+self.label_+':"' ']' '}' ), m3dGmsh ) m3dGmsh.attachBVObserver(ob) # write mesh ob = bVOWriteMSH() ob.thisown = False ob.jInit( jsonPrimitive('{"_filename" : "", "_saveAll" : true}'), None, None, None, None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) #ob.postUpdate() # orient volumes ob = bVOOrientCellVolumes() ob.thisown = False ob.jInit( jsonPrimitive('{"_positive" : true}'), m3dGmsh ) m3dGmsh.attachBVObserver(ob) m3dGmsh.thisown = False self.appendBoundedVolume(m3dGmsh) return
[docs] @staticmethod def detectFirstAndSecond( channel: map3dTo3d, direction: int ) -> Tuple[map2dTo3d, map2dTo3d]: """Detect first and second faces in a volume's parameter direction. This method returns the faces of a `map3dTo3d` object at 0 and 100 percent of the u-, v-, or w-paramter. Parameters ---------- channel: map3dTo3d Volume. direction: int Direction in uvw - 1 -> U - 2 -> V - 3 -> W Returns ------- first: map2dTo3d First segment in direction second: map2dTo3d Second segment in direction """ firstPar = 0.0 secondPar = 1.0 if direction<0: firstPar = 1.0 secondPar = 0.0 if direction == 1: first = channel.segmentConstUPercent(firstPar) second = channel.segmentConstUPercent(secondPar) elif direction == 2: first = channel.segmentConstVPercent(firstPar) second = channel.segmentConstVPercent(secondPar) elif direction == 3: first = channel.segmentConstWPercent(firstPar) second = channel.segmentConstWPercent(secondPar) else: raise ValueError("Wrong direction.") return first, second
[docs] @staticmethod def extractEdgesInFirstAndSecond( theModel: dtGmshModel, faces: List[map2dTo3d], first: map2dTo3d, second: map2dTo3d ) -> Tuple[List[int], List[int]]: """Extracts edges of faces which lie on another first or second face. This method: - Iterates over ``faces`` and extracts the edges of each face - Checks if any of the edges are located on the ``first`` face - Appends the edge to ``firstEdges`` if the check applies - Checks if any of the edges are located on the ``second`` face - Appends the edge to ``secondEdges`` if the check applies - Returns ``firstEdges`` and ``secondEdges`` Parameters ---------- theModel: dtGmshModel Gmsh model faces: List[map2dTo3d] List of faces first: map2dTo3d First face second: map2dTo3d Second face Returns ------- firstEdges: List[int] Edges of on first face secondEdges: List[int] Edges of on second face """ firstEdges = [] secondEdges = [] # iterate over faces for face in faces: # iterate over the edges for edge in [ face.segmentConstUPercent(0.0), face.segmentConstVPercent(1.0), face.segmentConstUPercent(1.0), face.segmentConstVPercent(0.0), ]: # check if edge lies on the first face if bool_map1dTo3dInMap2dTo3d(edge, first).result(): firstEdges.append( theModel.addIfEdgeToGmshModel( edge ) ) logging.debug("Detect first edge tag = %d" % firstEdges[-1] ) # check if the face lies on the second face elif bool_map1dTo3dInMap2dTo3d(edge, second).result(): secondEdges.append( theModel.addIfEdgeToGmshModel( edge ) ) logging.debug("Detect second edge tag = %d" % secondEdges[-1]) logging.info( "firstEdges = %s, secondEdges = %s" % ( str(firstEdges), str(secondEdges) ) ) # return the edge lists return firstEdges, secondEdges
[docs] @staticmethod def boundaryEdgeDirection( theModel: dtGmshModel, boundaryLayerDirCheck: List[ List[ Union[ map2dTo3d, List[int] ] ] ] ) -> int: """Determine the boundary layer direction of a face from two edges. This method: - Iterates over the faces and edges in the input list. - Checks whether the u- or v-parameters of the start and end points of the edges on a face are equal within a specified tolerance. - Determines and returns the corresponding boundary layer direction. Parameters ---------- theModel : dtGmshModel Gmsh model. boundaryLayerDirCheck : List[List[map2dTo3d, List[int]]] List containing faces and their corresponding edges. The entries are organized as follows: - ``boundaryLayerDirCheck[i]`` : One face and its associated edges. - ``boundaryLayerDirCheck[i][0]`` : Face. - ``boundaryLayerDirCheck[i][1]`` : List of edges. Returns ------- int Integer encoding the boundary layer direction. The boundary layer direction is determined by iterating over ``faceLines in boundaryLayerDirCheck`` in an outer loop and over the edges ``line in faceLines[1]`` in the inner loop. During each iteration of the inner loop, the start and end points ``p0_uv`` and ``p1_uv`` of the current edge are reparameterized in the parameter space of the surface ``face``. The method :meth:`inTolerance` is used to determine whether the difference between the u- or v-parameters lies within the tolerance specified by ``tol``. Depending on the result, the value of ``boundaryLayerDirT`` is incremented. The resulting value is appended to the list ``boundaryLayerDir``. At the end of each outer loop iteration, duplicate entries in ``boundaryLayerDir`` are removed using ``boundaryLayerDir = list(dict.fromkeys(boundaryLayerDir))``. Within this workflow, all boundary layer directions must be oriented consistently. The condition ``len(boundaryLayerDir) != 1`` is therefore used to verify that all detected directions are identical. If this condition is not satisfied, an exception is raised. Finally, the boundary layer direction is encoded as an integer return value. If ``boundaryLayerDir[0]`` equals ``1``, the method returns ``0``. If ``boundaryLayerDir[0]`` equals ``2``, the method returns ``1``. Any other value results in an exception. """ # set tolerance tol = 0.01 # prepare direction list boundaryLayerDir = [] # iterate over entires for faceLines in boundaryLayerDirCheck: # iterate over the edges of the current entry for line in faceLines[1]: theEdge = theModel.getDtGmshEdgeByTag( line ) # # reparametrize the start and end points of the edge # in the uv-parameters of the face (faceLines[0]) # # start point p0_uv = faceLines[0].reparamPercentOnFace( theEdge.getMap1dTo3d().getPointPercent(0.0) ) # end point p1_uv = faceLines[0].reparamPercentOnFace( theEdge.getMap1dTo3d().getPointPercent(1.0) ) boundaryLayerDirT = 0 # # Check the tolerance of the start and end points in u and v direction # Increment boundaryLayerDirT accordingly # ## Original code with dtOO tolerance check ## some geometries fail due to high tolerances #if ( not analyticGeometry.inUVWTolerance( p0_uv.x(), p1_uv.x() ) ): # boundaryLayerDirT = boundaryLayerDirT + 1 #if ( not analyticGeometry.inUVWTolerance( p0_uv.y(), p1_uv.y() ) ): # boundaryLayerDirT = boundaryLayerDirT + 2 if ( not map3dTo3dGmsh_gridFromMultipleBoundedVolumeAndBlocks.inTolerance(tol, p0_uv.x(), p1_uv.x() ) ): boundaryLayerDirT = boundaryLayerDirT + 1 if ( not map3dTo3dGmsh_gridFromMultipleBoundedVolumeAndBlocks.inTolerance(tol, p0_uv.y(), p1_uv.y() ) ): boundaryLayerDirT = boundaryLayerDirT + 2 logging.debug("face: %s" % faceLines[0].getLabel()) logging.debug("p0_uv = (%f, %f)" % (p0_uv.x(), p0_uv.y())) logging.debug("p1_uv = (%f, %f)" % (p1_uv.x(), p1_uv.y())) logging.debug("boundaryLayerDirT = %d" % (boundaryLayerDirT)) # append the value to the list boundaryLayerDir.append( boundaryLayerDirT ) # delete duplicate entries boundaryLayerDir = list(dict.fromkeys(boundaryLayerDir)) # check that all directions are uniform if len(boundaryLayerDir)!=1: raise ValueError("BoundaryLayerDirection is not equal in all faces.") boundaryLayerDir = boundaryLayerDir[0] logging.info("boundaryLayerDir = %d" % boundaryLayerDir) # return the direction depending on the value if boundaryLayerDir==1: return 0 elif boundaryLayerDir ==2: return 1 else: raise ValueError("Direction should be 1 or 2.")
[docs] @staticmethod def detectBoundaryLayerDirection( theModel: dtGmshModel, boundaryLayerDirCheck: List[ List[ Union[ map2dTo3d, List[int] ] ] ] ) -> int: """Determine the boundary layer direction of a face from two edges. This method: - Runs :meth:`boundaryEdgeDirection` - Returns integer value encoding the boudary edge direction Parameters ---------- theModel: dtGmshModel Gmsh model boundaryLayerDirCheck: List[ List[ map2dTo3d, List[int]]] List containing faces and corresponding edges. Entries correlate to: - boundaryLayerDirCheck[i] : Set of one face and its edges - boundaryLayerDirCheck[i][0] : Face - boundaryLayerDirCheck[i][1] : List of edges Returns ------- int """ boundaryLayerDir = \ map3dTo3dGmsh_gridFromMultipleBoundedVolumeAndBlocks.boundaryEdgeDirection( theModel, boundaryLayerDirCheck ) # (1,0)[boundaryLayerDir==1]) if boundaryLayerDir==0: return 1 elif boundaryLayerDir ==1: return 0 else: return -1
[docs] def inTolerance( tol: float, p0: float, p1: float ) -> bool: """Check if two float values lie within a tolerance. This method: - Calculates the difference between two float values - Returns boolean depending if tolerance is met Parameters ---------- tol: float Tolerance p0: float First value p1: float Second value Returns ------- bool """ if p0 >= p1: diff = p0-p1 else: diff = p1-p0 if diff > tol: return False else: return True
[docs] def addGrading( self, gradings: Dict, theRef: scaOneD, gradingLabel: str, m3dGmsh: map3dTo3dGmsh = None, firstElementSize: float = 0.0, lastElementSize: float = 0.0 ) -> Dict: """Create a grading and add it to the grading dictionary. This method: - Creates an entry in the grading dictionary. - Labels the grading function and appends it to the container. - Creates an observer for the grading. - Returns the updated grading dictionary. Parameters ---------- gradings : Dict Grading dictionary. Each key corresponds to a grading identifier and contains the following information: - ``gradingLabel`` (``List[int, str]``): A list containing the grading number and a string identifier. theRef : scaOneD Grading function. gradingLabel : str Grading label. m3dGmsh : map3dTo3dGmsh Gmsh topology object. firstElementSize : float Size of the first element in the grading. lastElementSize : float Size of the last element in the grading. Returns ------- Dict Updated grading dictionary. Each key corresponds to a grading identifier and contains the following information: - ``gradingLabel`` (``List[int, str]``): A list containing the grading number and a string identifier. For a new grading, an entry is created in the dictionary ``gradings`` using ``gradingLabel`` as the key. The corresponding value is a list containing a unique grading number and identifier string of the form :: label_ + "_gradings_" + str(gradingNumber) + "_" + gradingLabel The grading function ``theRef`` is assigned this identifier string and added to the analytic function container of the calling class. Depending on whether the grading specifies only the first element size, ``(firstElementSize > 0.0) and (lastElementSize == 0.0)``, or both the first and last element sizes, ``(firstElementSize > 0.0) and (lastElementSize > 0.0)``, an observer of type ``bVOSetPrescribedElementSize`` is created using the specified element sizes and the grading function. The observer is then added to the topology. Finally, the updated grading dictionary is returned. """ # set grading number gradingNumber = 2 for ii in gradings: gradingNumber = max( gradingNumber, gradings[ii][0] ) gradingNumber = gradingNumber + 1 # create entry to grading dictionary gradings[gradingLabel] = [ gradingNumber, self.label_+"_gradings_"+str(gradingNumber)+"_"+gradingLabel ] logging.info( "%d -> %s" % ( gradings[gradingLabel][0], gradings[gradingLabel][1] ) ) # label and push grading function theRef.setLabel( gradings[gradingLabel][1] ) self.appendAnalyticFunction( theRef.clone() ) # create observer if ( (firstElementSize>0.0) and (lastElementSize==0.0) ): ob = bVOSetPrescribedElementSize() ob.thisown = False ob.jInit( jsonPrimitive()\ .appendInt("_type", gradings[gradingLabel][0])\ .appendReal("_firstElementSize", firstElementSize)\ .appendAnalyticFunction( "_grading", self.lVH_aF()[ gradings[gradingLabel][1] ] ), None, None, self.lVH_aF(), None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) elif ( (firstElementSize>0.0) and (lastElementSize>0.0) ): ob = bVOSetPrescribedElementSize() ob.thisown = False ob.jInit( jsonPrimitive()\ .appendInt("_type", gradings[gradingLabel][0])\ .appendReal("_firstElementSize", firstElementSize)\ .appendReal("_lastElementSize", lastElementSize)\ .appendAnalyticFunction( "_grading", self.lVH_aF()[ gradings[gradingLabel][1] ] ), None, None, self.lVH_aF(), None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) return gradings
[docs] @staticmethod def gradingsTypeTransfinite(gradings: Dict) -> str: """Get the list of grading numbers and return it as a string. This method: - Iterates over the entries of the grading dictionary - Appends the grading numbers to a list - Converts the list to a string and returns it Parameters ---------- gradings: Dict Grading dictionary with each key containing identifiers for a grading: - gradingLabel (List[int, str]): Grading label key contains a list with grading number and a identifier string Returns ------- str """ retList = list() for ii in gradings: retList.append( gradings[ii][0] ) logging.debug("Convert to %s" % str(retList)) return str(retList)
[docs] @staticmethod def gradingsGradingFunctions(gradings: Dict) -> str: """Create a string of grading identifiers in a jsonPrimitive format. This method: - Iterates over the entries of the grading dictionary - Appends the grading identifiers in the format of a jsonPrimitive - Returns the string Parameters ---------- gradings: Dict Grading dictionary with each key containing identifiers for a grading: - gradingLabel (List[int, str]): Grading label key contains a list with grading number and a identifier string Returns ------- retStr: str Return string """ retStr = '[' first = True for ii in gradings: if first: retStr = retStr + '{ "label" : "'+gradings[ii][1]+'" }' first = False else: retStr = retStr + ', { "label" : "'+gradings[ii][1]+'" }' retStr = retStr + ']' logging.debug("Convert to %s" % retStr) return retStr