Source code for dtOOPythonApp.builder.map3dTo3dGmsh_gridFromLayers

from dtOOPythonApp.tools.dtBundleTools import dtBundleBuilder

from dtOOPythonSWIG import jsonPrimitive
from dtOOPythonSWIG import bVOMeshRule
from dtOOPythonSWIG import analyticGeometry
from dtOOPythonSWIG import map2dTo3d
from dtOOPythonSWIG import map3dTo3d
from dtOOPythonSWIG import map3dTo3dGmsh
from dtOOPythonSWIG import jsonPrimitive
from dtOOPythonSWIG import bVOWriteMSH
from dtOOPythonSWIG import bVONameRegions
from dtOOPythonSWIG import bVOFaceToPatchRule
from dtOOPythonSWIG import bVOAnalyticGeometryToFace
from dtOOPythonSWIG import labeledVectorHandlingAnalyticGeometry
from dtOOPythonSWIG import labeledVectorHandlingAnalyticFunction
from dtOOPythonSWIG import vectorReal
from dtOOPythonSWIG import scaTanhGradingOneD
from dtOOPythonSWIG import scaTanhGradingOneDCompound
from dtOOPythonSWIG import bVOSetPrescribedElementSize
from dtOOPythonSWIG import bVOSetPrescribedMeshSizeAtPoints
from dtOOPythonSWIG import bVOOrientCellVolumes

import logging
import numpy
from typing import List, Tuple

[docs] class map3dTo3dGmsh_gridFromLayers (dtBundleBuilder): """Create mesh topology as map3dTo3dGmsh. This class: - Creates a map3dTo3dGmsh topology object. - Adds the unstructured region to the topology. - Adds layer volumes to the topology. - Manages layer faces in the topology. - Applies mesh settings to the edges. - Applies gradings and mesh rules. - Renames faces. - Applies mesh settings to topology. Attributes ---------- label_: str Label. layerList_: List[List[List[analyticGeometry] | List[bool]]] Layer lists for hub and shroud with 3d regions and bool list. nLayers_: int Number of elements normal to the walls in the layer volumes. firstElement_: float Size of first element on the walls. elementSizeSw_: float Element size in streamwise direction. elementSizeCirc_: float Element size in circumferential direction. unstructured_: analyticGeometry Multi bounded volume of the unstructured region. unstructuredSurfaces_: List[analyticGeometry] Bounding faces of the mult bounded volume. map3dTo3dGmshJson_: jsonPrimitive JSON structure for map3dTo3dGmsh. Examples -------- None The class is used to create the mesh topology of a flow channel consisting of five- or six-sided layer volumes on the hub and shroud walls and a multiple bounded volume inside the flow domain, connecting to the layer volumes. The layer volumes are meshed transfinite and the multiple bounded volume is meshed unstructured. In the constructor, the input parameters are instantiated. The multiple bounded volume is instantiated as ``unstructured_``. The list with its bounding surfaces is instantiated as ``unstructuredSurfaces_``. The layer list is instantiated as ``layerList``. Its structure is as follows: .. code-block:: python layerList = List[ List[ List[analyticGeometry], List[bool] ] ] The entries correspond to the following values: - ``layerList[0]``: Hub layers - ``layerList[1]``: Shroud layers - ``layerList[i][0]``: List of layer volumes. - ``layerList[i][1]``: List of Boolean values indicating whether the corresponding layer is located on a radius of zero. The layers are meshed with a grading extending from the wall faces into the flow domain. The number of elements in the grading is defined as ``nLayers_``. The size of the first element on the wall is specified with ``firstElement_``. The number of elements in streamwise and circumferential direction is set with ``elementSizeSw_`` and ``elementSizeCirc_``. These parameters correspond to maximal element sizes on the hub and shroud walls. The size of the elements in the unstructured region is set with the minimal and maximal characteristic lengths ``charLengthMin`` and ``charLengthMax``. The topology settings are defined with a ``jsonPrimitive`` object instantiated as ``map3dTo3dGmshJson_``. Here, the characteristic lengths of the unstructured mesh elements are applied. With the :meth:`build` method, the mesh settings are applied to the topology. The methods :meth:`detectFirstAndSecond` and :meth:`getCommonEdgesByPhysicalFaces` are used to organize the faces and edges of the layer volumes. The mesh topology is appended to the bounded volume container at the end of the :meth:`build` method. """ def __init__(self, mv: analyticGeometry, bs: List[analyticGeometry], label: str, layers: List[List[List[analyticGeometry] | List[bool]]], nElementsLayer: int, firstElement: float, elementSize_sw: float, elementSize_circ: float, charLengthMin: float = 0.05, charLengthMax: float = 0.10 ) -> None : """Constructor Parameters ---------- mv: analyticGeometry Multi bounded volume of the unstructured region bs: List[analyticGeometry] Bounding faces of the mult bounded volume label: str Label. layers: List[List[List[analyticGeometry] | List[bool]]] Layer lists for hub and shroud with 3d regions and bool list. nElementLayer: int Number of elements in grading. firstElement: float size of first element in grading. elementSize_sw: float Element size in streamwise direction elementSize_circ: float Element size in circumferential direction charLengthMin: float Minimal characteristic length of elements in unstructured region. charLengthMax: float Maximal characteristic length of elements in unstructured region. Returns ------- None """ super(map3dTo3dGmsh_gridFromLayers, self).__init__() # setting global params self.label_ = label self.layerList_ = layers self.nLayers_ = nElementsLayer self.firstElement_ = firstElement self.elementSizeSW_ = elementSize_sw self.elementSizeCIRC_ = elementSize_circ self.unstructured_ = mv self.unstructuredSurfaces_ = bs logging.info( "Building %s ..." % (self.label_) ) #logMe.initLog('layerMesh.log') # setting up volume 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.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. This method is the main method of the class. - Creates a ``map3dTo3dGmsh`` topology object. - Adds the unstructured region to the topology. - Adds layer volumes to the topology. - Manages layer faces in the topology. - Applies mesh settings to the edges. - Applies gradings and mesh rules. - Renames faces. - Applies mesh settings to the topology. Parameters ---------- None Returns ------- None The topology object ``m3Gmsh`` is created with the settings in ``map3dTo3dGmsh_``. The labeled vector handling objects ``aG`` and ``aF`` are created to handle analytic geometries and functions in this method. The bounding faces of the unstructured region in ``unstructuredSurfaces_`` are labeled in the getter method ``getUnstructuredRegion`` of the class ``analyticGeometry_layerRegion``. The labels are as follows: - ``"periodicUnstruct_0"`` : First periodic surface - ``"periodicUnstruct_1"`` : Second periodic surface - ``"interface_unstruct"`` : Inlet of the flow domain's unstructured region - ``"outlet_unstruct"`` : Outlet of the flow domain's unstructured region - ``"para" + str(i)`` : Connecting faces to the layer volumes The bounding faces that are not labeled with the string ``para`` are pushed into ``aG`` by iterating over the list. :numref:`gridLayersMeshFaces` shows the face ``outlet_unstruct`` in orange and ``interface_unstruct`` in red. The faces labeled with ``para`` are equal to the wall parallel faces of the layer volumes (purple). The periodic faces of the unstructured region are not shown. .. _gridLayersMeshFaces: .. figure:: meridionalFigs/layersMeshFaces.png :width: 50% :align: center Faces in the layered flow domain. The multiple bounded volume of the unstructured region is added to the topology and allocated in ``unstrct3d``. In a nested loop over ``layerList_``, the layer volumes of the hub and shroud are added to the topology. The faces of the layers are added to ``aG``. The activities inside the loop are illustrated in the following figure. .. _gridLayers_activity0: .. figure:: meridionalFigs/gridLayers_activity0.png :width: 70% :align: center Activities for adding the layer volumes to the topology. **Loop over Hub and Shroud** :: i_hs in range(len(layerList_)) The first level loop iterates over the hub and shroud data in ``layerList_``. According to the iterator ``i_hs``, the ``label`` variable is set to the strings ``hub`` or ``shroud``. **Loop over Layers** :: i_l in range(len(layerList[i_hs][0])) The second level loop iterates over the hub or shroud layers in ``layerList_[i_hs]``. The iterator is ``i_l``. With the method :meth:`detectFirstAndSecond`, the faces of each layer are identified. The method takes the layer volume as a ``map3dTo3d`` object and the parameter direction as an integer input. By calling the function once for each parameter direction, all faces of the layer volume are returned. The returned faces are allocated to the following parameters: - ``ortho0`` and ``ortho1`` : Faces orthogonal to the wall (:numref:`gridLayersMeshFaces` dark blue) - ``periodic0`` and ``periodic1`` : Periodic faces on the flow domain segment (:numref:`gridLayersMeshFaces` light blue (``periodic0`` not shown)) - ``channel`` : Wall faces on hub and shroud (:numref:`gridLayersMeshFaces` green) - ``parallel`` : Faces extending parallel to the wall (:numref:`gridLayersMeshFaces` purple) Here, ``ortho0`` is the upstream face of each layer volume and ``ortho1`` is the downstream face. The faces ``ortho0`` and ``ortho1`` are labeled as follows: - ``ortho0`` : ``"ortho_"+label+str(i_l)`` - ``ortho1`` : ``"ortho_"+label+str(i_l)+1`` The layer volumes are added to the topology ``m3dGmsh`` and receive the region ID ``rID``. The labeling of the other faces depends on whether the layer volume is five- or six-sided. A layer is five-sided if it is located on the radius of zero. This is checked with the list in ``layerList_[i_hs][1]``. This differentiation is necessary, because different meshing strategies have to be applied. **The layer is six sided:** The layer is six sided when ``layerList_[i_hs][1][i_l] == False`` applies. In this case, the naming of the faces is as follows: - ``periodic1`` : ``"periodic0_"+label+str(i_l)`` - ``periodic0`` : ``"periodic1_"+label+str(i_l)`` - ``channel`` : ``"channel_"+label+str(i_l)`` - ``parallel`` : ``"parallel_"+label+str(i_l)`` The mesh settings are set to transfinite and recursive. **The layer is five sided:** If the layer is five sided, the string ``5s`` is added to the face names: - ``periodic1`` : ``"periodic05s_"+label+str(i_l)`` - ``periodic0`` : ``"periodic15s_"+label+str(i_l)`` - ``channel`` : ``"channel5s_"+label+str(i_l)`` - ``parallel`` : ``"parallel5s_"+label+str(i_l)`` In this case, the layer cannot be meshed completely transfinite recursive. Its mesh settings have to be applied directly to the edges. The labeled faces are pushed into ``aG``. ``ortho0`` is pushed into ``aG`` in every iteration. ``ortho1`` is only pushed in the last iteration ``i_l == len(layerList_[i_hs][0])-1`` if the last layer is not on a radius of zero ``layerList_[i_hs][1][i_l] == False``. **Apply Mesh Sizing** With an observer of the class ``bVOAnalyticGeometryToFace``, the labeled faces in ``aG`` are added to the topology ``m3dGmsh``. The mesh settings are applied to the edges of the layer volumes. The following figure shows the edges on which mesh settings are applied. .. _layersMeshSetting: .. figure:: meridionalFigs/layersMeshSetting.png :width: 50% :align: center Edges in the flow domain on which mesh settings are applied. ``channelToParallelLines`` (green), ``swLines`` (magenta), and ``circLines`` (blue). On grey edges no mesh settings are applied. **Apply Gradings on the Walls** The grading on the hub and shroud layers is set on the edges extending from the ``channel`` to ``parallel`` faces. The edges are stored in ``channelToParallelLines``. By iterating over these edges, the grading is applied with the method ``setGrading``. The first input of this method is the direction of the grading, and the second one is an identifier for the grading function. The number of elements is set to ``nLayers_``. While iterating over the edges, the sum of their edge lengths is calculated. Using the number of edges in ``channelToParallelLines``, the mean edge length is calculated. By dividing this value by ``nLayers_``, the mesh size ``meshSizeAtPoints`` at the connection face between the layer and the unstructured volume is estimated. To set the number of elements on the edges in the streamwise (:numref:`layersMeshSetting` (magenta)) and circumferential directions (:numref:`layersMeshSetting` (blue)), an iteration over the layer list is performed. The following figure illustrates the processes in an activity diagram. .. _gridLayers_activity1: .. figure:: meridionalFigs/gridLayers_activity1.png :width: 90% :align: center Activities for the mesh settings in the streamwise and circumferential directions. Pink action blocks correspond to operations on the streamwise edges, blue action blocks correspond to operations on circumferential edges. **Prepare List** The list ``lChannel_circ = [0,0]`` is prepared. The two values in this list are used in the loop to store the maximum lengths of the circumferential edges on the hub and shroud walls. **Loop over Hub And Shroud** :: i_hs in range(len(layerList_)) Similarly to the loop in :numref:`gridLayers_activity0`, a nested loop over the two levels of ``layerList_`` is performed. The first level loop iterates over the hub and shroud layers. The string value of ``label`` is set accordingly. **Loop over Layers** ``i_l in range(len(layerList_[i_hs][0]))`` The second level loop iterates over the specific layers in the hub or shroud set. **Edges in Streamwise Direction** The length ``lChannel_sw`` of the wall edge of the current layer in the streamwise direction is calculated. The edge is returned as the common edge between the ``channel`` and the ``periodic0`` faces with the method :meth:`getCommonEdgesByPhysicalFaces`. With this length, the number of elements ``nE`` is calculated by dividing the length through the element size ``elementSizeSW_``. By rounding ``nE`` to the next highest integer value, it is ensured that ``elementSizeSW_`` is the maximal element size on the wall face in the streamwise direction. The handling of six- and five-sided layers differs here. This is checked with the value in ``layerList_[i_hs][1][i_l]``. **Layer is six sided** ``layerList_[i_hs][1][i_l] == False`` The streamwise edges (:numref:`layersMeshSetting` (magenta)) extending between the ``ortho`` faces of the layer are returned by the method ``getDtGmshEdgeTagListByFromToPhysical`` of the Gmsh model. **Layer is five sided** ``layerList_[i_hs][1][i_l] == True`` The four edges are returned by the method :meth:`getCommonEdgesByPhysicalFaces`. Here, the edges of the ``channel`` or the ``parallel`` faces are compared with the ``periodic0`` and ``periodic1`` faces. The common edges of these faces are stored in the list ``swLines``. The meshing of the four sided faces ``ortho``, ``periodic0``, and ``periodic1`` of the layer is set to transfinite with a recombine. **Apply Mesh Settings on Edges in Streamwise Direction** By iterating over ``swLines``, the number of elements ``nE`` is set on the edges. **Edges in Cricumferential Direction** The length ``l_ortho`` of the upstream circumferential edge of the layer volume is returned. If the length is greater than the value stored in ``lChannel_circ``, the value replaces the current entry in the list. If the iteration is at the last layer and the layer is not located on a radius of zero, the length ``l_ortho`` is calculated and compared for the downstream circumferential edge. **Apply Mesh Settings on Edges in Circumferential Direction** After the second level loop for a layer region has concluded, the number of elements according to the maximal edge length is set on the circumferential edges. The number of elements ``nE`` is calculated with the maximal edge length ``lChannel_circ[i_hs]`` and the element size ``elementSizeCirc_``. By rounding the value of ``nE`` to the next higher integer, it is ensured that the specified element size represents a maximum size along the circumferential direction of the layer wall. The circumferential edges ``circLines`` (:numref:`layersMeshSetting` (blue)) extend between the ``periodic`` faces. By iterating over ``circLines``, the number of elements is set. **Create Observers** The observer ``bVOSetPrescribedMeshSizeAtPoints`` is applied. This observer is used to set the mesh size of the unstructured region at the connection points with the layer volumes to the value calculated in ``meshSizeAtPoints``. The object of the grading function ``theRef`` is created with the `dtOO` class `scaTanhGradingOneDCompound`. It is labeled ``aF_grading`` and pushed into ``aF``. An observer of the class ``bVOSetPrescribedElementSize`` is created. This observer combines the analytic grading function specified in ``theRef`` with the ``_type`` identifier and the size of the first element in the grading ``firstElement_``. The mesh rules are applied with the observer ``bVOMeshRule``. The rules ``dtMeshFreeGradingGEdge``, ``dtMeshGFace``, and ``dtMeshGRegion`` are used for all edges, surfaces, and volumes. The observer of the class ``bVOFaceToPatchRule`` is used to rename the faces. This is done so that the naming of the face regions is consistent with the setup rules of the simulation. The faces are renamed with the label ``label_`` and a string for the region. The following names are assigned to the faces: - ``label_ + '_hub'`` : hub walls - ``label_ + '_shroud'`` : shroud walls - ``label_ + '_inlet'`` : inlet surfaces - ``label_ + '_outlet'`` : outlet surfaces - ``label_ + '_periodic0'`` : first periodic segment faces - ``label_ + '_periodic1'`` : second periodic segment faces With an if-condition, it is checked whether the last hub wall extends to a radius of zero. If this is the case (see :numref:`gridLayersMeshFaces`), the hub layer regions are not part of the outlet. If the last hub wall is not on zero, the last ``ortho`` face of the hub regions is added as an outlet face. With the observer of the class ``bVOWriteMSH``, the settings for the created ``.msh`` file are defined. The observer ``bVOOrientCellVolumes`` is applied with the setting ``"_positive" : true``. **Returns** The method `appendBoundedVolume` is used to append the topology object ``m3dGmsh`` to the container objects in the main class. A mesh resulting from this topology is shown in the following figure. .. _layerMesh: .. figure:: meridionalFigs/layersMesh.png :width: 40% :align: center Mesh of a draft tube cone resulting from the described topology. """ # # create the topology object # m3dGmsh = map3dTo3dGmsh() m3dGmsh.jInit( self.map3dTo3dGmshJson_, None, None, None, None, None ) # # create the labeled vector handling objects # aF = labeledVectorHandlingAnalyticFunction() aG = labeledVectorHandlingAnalyticGeometry() logging.info("Creating Layer Mesh on hub and shroud") # # add the unstructured region to the model # unstruct3d = m3dGmsh.getModel().addIfToGmshModel(self.unstructured_) logging.info("unstruct3d = %d" % unstruct3d) self.appendAnalyticGeometry( self.unstructured_.clone(), "debug_unstructuredRegion_"+self.label_) # # iterate over the surfaces of the unstructured region # the faces were already labeled in getUnstructuredRegion() # for face in self.unstructuredSurfaces_: # the interface and the outlet surfaces as well as the periodic surfaces # are added. Not the faces parallel to the hub and shroud curves ("para") if not (face.getLabel().startswith("para")): aG.push_back(face.clone()) # self.layerList_ has the following format: # self.layerList_ = [[hub layer lists],[shroud layer list]] # with: # [hub layer lists] = [[3d layer domain], [bool list radius zero]] # [bool list radius zero] tracks which layer segment has a radius of zero # those layers are meshed five sided the entrie for those layers is True # # iterate over the layers and pushing their respective surfaces # six and five sided layers are treated differently # for i_hs in range(len(self.layerList_)): if i_hs == 0: label = "hub" else: label = "shroud" logging.info("Adding %s Layers. Number of Layers: %i" % (label, len(self.layerList_[i_hs][0]))) # # iterate over the hub and shroud layers # for i_l in range(len(self.layerList_[i_hs][0])): # current 3d region layer3d = self.layerList_[i_hs][0][i_l] self.appendAnalyticGeometry( layer3d.clone(), "debug_layer_"+label+str(i_l)+"_"+self.label_ ) # # find the correct surfaces on the region by using the uvw direction # naming of faces: # ortho -> faces orthogonal to streamwise direction (connecting the layers) # periodic -> periodic faces (extend from channel curve radially inside the channel) # channel -> faces on channel side # parallel -> faces parallel to channel # ortho0, ortho1 = self.detectFirstAndSecond(layer3d, 2) periodic0, periodic1 = self.detectFirstAndSecond(layer3d, 1) channel, parallel = self.detectFirstAndSecond(layer3d, 3) # set the labels of the ortho faces ortho0.setLabel("ortho_"+label+str(i_l)) ortho1.setLabel("ortho_"+label+str(i_l+1)) # add the 3d layer as a region rID = m3dGmsh.getModel().addIfRegionToGmshModel(layer3d) # the labels are different for six and five sided regions # this is done becuase they have to be treated differently during meshing # # check in the layer list if the current layer is on a radius of zero if self.layerList_[i_hs][1][i_l] == False: logging.info("%s layer no. %i is six sided" % (label, i_l)) periodic0.setLabel("periodic0_"+label+str(i_l)) periodic1.setLabel("periodic1_"+label+str(i_l)) channel.setLabel("channel_"+label+str(i_l)) parallel.setLabel("parallel_"+label+str(i_l)) # only six sided regions can be meshed completely transfinite # Recursive means that all the edges and faces of the region are also tramsfinite m3dGmsh.getModel().getDtGmshRegionByTag( rID ).meshTransfiniteRecursive() m3dGmsh.getModel().getDtGmshRegionByTag( rID ).meshRecombineRecursive() else: logging.info("%s layer no. %i is five sided" % (label, i_l)) periodic0.setLabel("periodic05s_"+label+str(i_l)) periodic1.setLabel("periodic15s_"+label+str(i_l)) channel.setLabel("channel5s_"+label+str(i_l)) parallel.setLabel("parallel5s_"+label+str(i_l)) # push back the faces aG.push_back(ortho0) # ortho1 is only pushed in the last region if it is six sided # (ortho1 in a five sided region is rotated surface with radius of zero # this results in a degenerated face) if i_l == len(self.layerList_[i_hs][0])-1 and self.layerList_[i_hs][1][i_l] == False: aG.push_back(ortho1) aG.push_back(periodic0) aG.push_back(periodic1) aG.push_back(channel) aG.push_back(parallel) # initialize the region labels ob = bVONameRegions() ob.jInit( jsonPrimitive('{ "_regionLabel" : [] }'), m3dGmsh ) ob.preUpdate() # # initialize the surface labels in gmsh # ob = bVOAnalyticGeometryToFace() ob.jInit( jsonPrimitive( '{' '"analyticGeometry" : [' '{"labels" : "ortho_*"},' '{"labels" : "channel*"},' '{"labels" : "periodic1*"},' '{"labels" : "periodic0*"},' '{"label" : "periodicUnstruct_1"},' '{"label" : "periodicUnstruct_0"},' '{"label" : "interface_unstruct"},' '{"label" : "outlet_unstruct"},' '{"labels" : "parallel*"}' '],' '"_inc" : 10.0,' '"_facesPerEntry" : []' '}' ), None, None, None, aG, None, m3dGmsh ) ob.preUpdate() # # mesh the lines orthogonal to the channel lines # these lines connect the channel and parallel faces # channelToParallelLines = m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical("channel*","parallel*") # channel to parallel length c2pLength = 0 # # iterate over the edges and apply mesh settings # for line in channelToParallelLines: logging.info( "meshing graded Line : ID: %i, number of elements: %i" % (line, self.nLayers_) ) theEdge = m3dGmsh.getModel().getDtGmshEdgeByTag( line ) # mesh the lines with the specified number of elements and set the grading theEdge.meshTransfiniteWNElements( 1, 1, self.nLayers_ ) theEdge.setGrading(1.0, 3) self.appendAnalyticGeometry( m3dGmsh.getModel().getDtGmshEdgeByTag(line).getMap1dTo3d(), "debug_layerEdge_channelToParallelLine_"+"_ID"+str(line)+"_"+self.label_ ) # sum up the channel to parallel lengths of the edges c2pLength = c2pLength + theEdge.length() # calculate the mean value of the edge lengths c2pLength = c2pLength / len(channelToParallelLines) # estimate the mesh size at the points meshSizeAtPoints = c2pLength / self.nLayers_ # list containing the lengths of the mesh lines in circumferential direction lChannel_circ = [0, 0] # # iterate over the hub and shroud layers # the mesh settings for the edges in streamwise and circumferential directions are set # regions with five sides are specially treated in this loop # for i_hs in range(len(self.layerList_)): if i_hs == 0: label = "hub" else: label = "shroud" # iterate over the individual layers in the hub and shroud layer list for i_l in range(len(self.layerList_[i_hs][0])): # find an edge on the channel in streamwise direction # this edge is the common edge between the channel and the periodic # face in this layere region edges = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "channel*"+label+str(i_l)+"*", "periodic0*"+label+str(i_l)+"*" ) # calculate the length and number of elements on this line # this number of elements is used for all streamwise lines in this layer region lChannel_sw = edges[0].getMap1dTo3d().length() nE = int(numpy.ceil(lChannel_sw/self.elementSizeSW_)) logging.info("layer %s%i has a channel length of %.4f, meshing with %i elements" % ( label, i_l, lChannel_sw, nE)) # find all streamwise layer curves for a six sided region if self.layerList_[i_hs][1][i_l] == False: # the curves are found by searching the connecting lines of the orthogonal faces swLines = m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "ortho_"+label+str(i_l)+"*", "ortho_"+label+str(i_l+1)+"*") # find streamwise layercurves for five sided regions # needs special treatment because this region doesnt have a second ortho face elif self.layerList_[i_hs][1][i_l] == True: # lines have to be found manually as the common lines # between channel/parallel face and the periodic faces line0 = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "channel5s_"+label+str(i_l)+"*", "periodic05s_"+label+str(i_l)+"*" ) line1 = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "channel5s_"+label+str(i_l)+"*", "periodic15s_"+label+str(i_l)+"*" ) line2 = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "parallel5s_"+label+str(i_l)+"*", "periodic05s_"+label+str(i_l)+"*" ) line3 = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "parallel5s_"+label+str(i_l)+"*", "periodic15s_"+label+str(i_l)+"*" ) swLines = [line0[0].tag(), line1[0].tag(), line2[0].tag(), line3[0].tag()] # set the orthogonal and periodic faces of the five sided region as transfinite m3dGmsh.getModel().getDtGmshFaceByPhysical("ortho_"+label+str(i_l)+"*").meshTransfinite() m3dGmsh.getModel().getDtGmshFaceByPhysical("ortho_"+label+str(i_l)+"*").meshRecombine() m3dGmsh.getModel().getDtGmshFaceByPhysical("periodic05s_"+label+str(i_l)+"*").meshTransfinite() m3dGmsh.getModel().getDtGmshFaceByPhysical("periodic05s_"+label+str(i_l)+"*").meshRecombine() m3dGmsh.getModel().getDtGmshFaceByPhysical("periodic15s_"+label+str(i_l)+"*").meshTransfinite() m3dGmsh.getModel().getDtGmshFaceByPhysical("periodic15s_"+label+str(i_l)+"*").meshRecombine() # # set the mesh size of the streamwise layer lines of the region # for line in swLines: self.appendAnalyticGeometry( m3dGmsh.getModel().getDtGmshEdgeByTag(line).getMap1dTo3d(), "debug_layerEdge_swLine_"+label+str(i_l)+"_ID"+str(line) +"_"+self.label_ ) logging.info( "meshing streamwise Line : %s%i, ID: %i, number of elements: %i" % (label, i_l, line, nE) ) m3dGmsh.getModel().getDtGmshEdgeByTag(line).meshTransfiniteWNElements(1,1,nE) # find a layer curve in circumferential direction # as the common edge between the channel and the ortho face edges = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "channel*"+label+str(i_l)+"*", "ortho_"+label+str(i_l)+"*" ) # length of circumferential curve l_ortho = numpy.abs(edges[0].getMap1dTo3d().length()) logging.info("Circumferential edge : %s%i, ID: %i, length: %.4f" % (label, i_l, edges[0].tag(), l_ortho)) # find the longest circumferential edge in the hub and shroud layers # based on this length the number of elements in circ direction will be calculated if l_ortho > lChannel_circ[i_hs]: lChannel_circ[i_hs] = l_ortho # find the last layer curve in circumferential direction of the last region # this is only done for six sided regions # (the radius of the last circ line of five sided regions is zero) if i_l == len(self.layerList_[i_hs][0])-1 and self.layerList_[i_hs][1][i_l] == False: edges = self.getCommonEdgesByPhysicalFaces( m3dGmsh, "channel_"+label+str(i_l)+"*", "ortho_"+label+str(i_l+1)+"*" ) l_ortho = numpy.abs(edges[0].getMap1dTo3d().length()) logging.info("Circumferential edge : %s%i, ID: %i, length: %.4f" % (label, i_l+1, edges[0].tag(), l_ortho)) if l_ortho > lChannel_circ[i_hs]: lChannel_circ[i_hs] = l_ortho # calculate the number of elements for the edges in circ direction from the longest edge length nE = int(numpy.ceil(lChannel_circ[i_hs]/self.elementSizeCIRC_)) # find all circ edges on hub or shroud circLines = m3dGmsh.getModel().getDtGmshEdgeTagListByFromToPhysical( "periodic0_"+label+"*", "periodic1_"+label+"*" ) # set the number of elements on the circ edges for hub or shroud # number of elements have to be constant for these edges for transfinte meshing for line in circLines: self.appendAnalyticGeometry( m3dGmsh.getModel().getDtGmshEdgeByTag(line).getMap1dTo3d(), "debug_layerEdge_circLine_"+label+"_ID"+str(line) +"_"+self.label_ ) logging.info( "meshing circumferential Line : %s, ID: %i, number of elements: %i" % (label, line, nE) ) m3dGmsh.getModel().getDtGmshEdgeByTag(line).meshTransfiniteWNElements(1,1,nE) # set an observer which prescribes meshsizes # meshSizeAtPoints is calculatate from the mean length of the channel to parallel # and the number of layers between them ob = bVOSetPrescribedMeshSizeAtPoints() ob.thisown = False ob.jInit( jsonPrimitive()\ .appendReal("_meshSize", meshSizeAtPoints), None, None, None, None, None, m3dGmsh ) #m3dGmsh.attachBVObserver(ob) # initiale the grading function # scaTanhGradingOneD(c, g, gMin, gMax) -> f(x)=c[0]+c[1]*tanh(g*(c[2]+c[3]*x))/tanh(g) theRef = scaTanhGradingOneDCompound( scaTanhGradingOneD( vectorReal([1.0, -1.0, 1.0, -1.0]), 1.0, 0.1, 5.0 ) ) theRef.setLabel( "aF_grading" ) aF.push_back( theRef.clone() ) # set the first element size specified as a class variable ob = bVOSetPrescribedElementSize() ob.thisown = False ob.jInit( jsonPrimitive( '{' '"_type": 3,' '"_firstElementSize": '+str(self.firstElement_)+',' '"_grading" : {' '"analyticFunction" : {"label" : "aF_grading"}' '}' '}' ), None, None, aF, None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) # set mesh rules ob = bVOMeshRule() ob.thisown = False ob.jInit( jsonPrimitive( '{' '"option" : [' '{"name" : "debug", "value" : "true"}' '],' '"_rule1D" : [' '"dtMeshFreeGradingGEdge(*)"' '],' '"_rule2D" : [' '"dtMeshGFace(*)"' '],' '"_rule3D" : [' '"dtMeshGRegion(*)"' '],' '"_only" : [],' '"dtMeshOperator" : [' '{' '"name" : "dtMeshFreeGradingGEdge",' '"label" : "dtMeshFreeGradingGEdge",' '"typeTransfinite" : [3],' '"gradingFunctions" : { "analyticFunction" : [' '{"label" : "aF_grading"}' ']}' '},' '{' '"name" : "dtMeshGFace",' '"label" : "dtMeshGFace"' '},' '{' '"name" : "dtMeshGRegion",' '"label" : "dtMeshGRegion",' '"_minQShapeMetric" : 0.0,' '"_relax" : 0.1,' '"_nPyramidOpenSteps" : 10,' '"_nSmooths" : 3' '}' ']' '}' ), None, None, aF, None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) ## useful debug statement to understand the naming of the layers and vizualize them ## especially for the bVOFaceToPatchRule #for face in m3dGmsh.getModel().getDtGmshFaceListByPhysical("*"): # print(face.getPhysicalString()) # self.appendAnalyticGeometry( # face.getMap2dTo3d(), # "debug_allFaces_"+face.getPhysicalString() # ) #print("length hub : ", str(len(self.layerList_[0][0]))) #print("length shroud : ", str(len(self.layerList_[1][0]))) # # setting bVOFaceToPatchRule, renames all the added faces # this is done to set boundary conditions in the of case later # ob = bVOFaceToPatchRule() ob.thisown = False # if the last hub layer is on radius 0 the unstructured region goes to radius zero # there is no hub layer on the outlet if self.layerList_[0][1][-1] == True: ob.jInit( jsonPrimitive( '{' '"_patchRule" : [' '":*channel*hub*::'+self.label_+'_hub:",' '":*channel*shroud*::'+self.label_+'_shroud:",' '":*ortho_*0_*::'+self.label_+'_inlet:",' '":*interface_unstruct*::'+self.label_+'_inlet:",' # outlet part of last layer '":*ortho_shroud'+str(len(self.layerList_[1][0]))+'*::'+self.label_+'_outlet:",' '":*outlet_unstruct*::'+self.label_+'_outlet:",' '":*periodic0*::'+self.label_+'_periodic0:",' '":*periodic1*::'+self.label_+'_periodic1:",' '":*periodicUnstruct_0*::'+self.label_+'_periodic0:",' '":*periodicUnstruct_1*::'+self.label_+'_periodic1:"' '],' '"_regRule" : [' '":*::'+self.label_+':"' ']' '}' ), m3dGmsh ) # if the radius is not zero in the last hub layer the last ortho segment # of the hub is also part of the outlet bondary else: ob.jInit( jsonPrimitive( '{' '"_patchRule" : [' '":*channel*_hub*::'+self.label_+'_hub:",' '":*channel*_shroud*::'+self.label_+'_shroud:",' '":*ortho_*0_*::'+self.label_+'_inlet:",' '":*interface_unstruct*::'+self.label_+'_inlet:",' '":*ortho_shroud'+str(len(self.layerList_[1][0]))+'*::'+self.label_+'_outlet:",' '":*ortho_hub'+str(len(self.layerList_[0][0]))+'*::'+self.label_+'_outlet:",' '":*outlet_unstruct*::'+self.label_+'_outlet:",' '":*periodic0*::'+self.label_+'_periodic0:",' '":*periodic1*::'+self.label_+'_periodic1:",' '":*periodicUnstruct_0*::'+self.label_+'_periodic0:",' '":*periodicUnstruct_1*::'+self.label_+'_periodic1:"' '],' '"_regRule" : [' '":*::'+self.label_+':"' ']' '}' ), m3dGmsh ) m3dGmsh.attachBVObserver(ob) #m3dGmsh.makeGrid() ob = bVOWriteMSH() ob.thisown = False ob.jInit( jsonPrimitive('{"_filename" : "", "_saveAll" : true}'), None, None, None, None, None, m3dGmsh ) m3dGmsh.attachBVObserver(ob) #ob.postUpdate() ob = bVOOrientCellVolumes() ob.thisown = False ob.jInit( jsonPrimitive('{"_positive" : true}'), m3dGmsh ) m3dGmsh.attachBVObserver(ob) m3dGmsh.thisown = False self.appendBoundedVolume(m3dGmsh) return
[docs] def detectFirstAndSecond(self, 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] def getCommonEdgesByPhysicalFaces(self, m3dGmsh, face0: str, face1: str): """Return the common edges between two faces. This method: - Iterates over the edges on both faces. - Finds faces with the same tag. - Returns them in a list. Parameters ---------- channel: map3dTo3d Volume. face0: str Pyhsical name of first face face1: str Pyhsical name of second face Returns ------- commonEdges: List[dtGmshEdge] List of edges on both faces """ commonEdges = [] edges0 = m3dGmsh.getModel().getDtGmshFaceByPhysical(face0).dtEdges() edges1 = m3dGmsh.getModel().getDtGmshFaceByPhysical(face1).dtEdges() for e0 in edges0: for e1 in edges1: if numpy.abs(e0.tag()) == numpy.abs(e1.tag()): commonEdges.append(e0) return commonEdges