# ------------------------------------------------------------------------------
# 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 (
dtLinearAlgebra,
analyticGeometry,
jsonPrimitive,
rotate,
analyticCurve,
bSplineCurve_pointConstructOCC,
analyticSurface,
dtPoint2,
dtPoint3,
dtVector3,
vectorDtPoint3,
partRotatingMap2dTo3d,
trimmedCurve_uBounds,
geomCurve_curveReverseConstructOCC,
multipleBoundedVolume,
infinityMap3dTo3d,
vectorHandlingConstDtCurve,
vectorHandlingAnalyticGeometry,
rectangularTrimmedSurface_curveRotateConstructOCC,
multipleBoundedSurface,
bSplineSurface_bSplineCurveFillConstructOCC,
bSplineCurve_curveConnectConstructOCC
)
from typing import List, Tuple
import logging
import numpy
[docs]
class analyticGeometry_layerRegion(dtBundleBuilder):
"""Create flow channel as five or six sided layer regions on the walls
and a multiple bounded volume inside the flow domain.
This class:
- Takes hub, shroud, inlet and outlet curves of the layered region.
- Creates layer boundary curves on the hub and shroud walls.
- Creates layer faces from the boundary curves.
- Creates layer volumes by rotating the boundaries.
- Creates a multiple bounded volume in the flow channel connecting to the layers.
Attributes
----------
label_: str
Label.
rotVector_: dtVector3
Rotation vector.
origin_: dtPoint3
Origin.
normalAxis_: dtVector3
Normal direction of the 2d region curves.
speCenter_: dtPoint3
Center point of the bounding box of the region.
speBb_: pairDtPoint3
Bounding box of the whole region.
hubLayerCurves_: List[analyticGeometry]
Boundary curves of the hub wall.
hubRadZero_: List[Bool]
List containing information about which hub curves are on a radius of zero.
hubUnstructBounds_: List[analyticGeometry]
Boundary curves of the multiple bounded volume at the hub.
shroudLayerCurves_: List[List[analyticGeometry]]
Boundary curves of the shroud Layer regions.
shroudRadZero_: List[Bool]
List containing information about which shroud curves are on a radius of zero.
shroudUnstructBounds_: List[analyticGeometry]
Boundary curves of the multiple bounded volume at the shroud.
interfaceUnstructBound_: analyticGeometry
Inlet boundary curve of the multiple bounded volume.
outletUnstructBound_: analyticGeometry
Outlet boundary curve of the multiple bounded volume.
unstructVH_: vectorHandlingAnalyticGeometry
vector handler containing all boundary curves of the multiple bounded volume.
hubLayers_: List[analyticGeometry]
List containing the faces of the six sided hub layers.
shroudLayers_: List[analyticGeometry]
List containing the faces of the six sided shroud layers.
Examples
--------
>>> import dtOOPythonSWIG as dtOO
Define inlet, outlet, hub and shroud curves of the layer.
>>> inlet = dtOO.analyticCurve(
... dtOO.bSplineCurve_pointConstructOCC(
... dtOO.vectorDtPoint3()
... << dtOO.dtPoint3(0.50, +0.00, 0.80)
... << dtOO.dtPoint3(0.75, +0.00, 0.80)
... << dtOO.dtPoint3(1.00, +0.00, 1.00),
... 2
... ).result()
... )
>>> outlet = dtOO.analyticCurve(
... dtOO.bSplineCurve_pointConstructOCC(
... dtOO.vectorDtPoint3()
... << dtOO.dtPoint3(0.00, +0.00, 0.00)
... << dtOO.dtPoint3(1.10, +0.00, 0.00),
... 1
... ).result()
... )
>>> hub0 = dtOO.analyticCurve(
... dtOO.bSplineCurve_pointConstructOCC(
... dtOO.vectorDtPoint3()
... << dtOO.dtPoint3(0.50, +0.00, 0.80)
... << dtOO.dtPoint3(0.25, +0.00, 0.60)
... << dtOO.dtPoint3(0.00, +0.00, 0.40),
... 2
... ).result()
... )
>>> hub1 = dtOO.analyticCurve(
... dtOO.bSplineCurve_pointConstructOCC(
... dtOO.vectorDtPoint3()
... << dtOO.dtPoint3(0.00, +0.00, 0.40)
... << dtOO.dtPoint3(0.00, +0.00, 0.00),
... 1
... ).result()
... )
>>> shroud0 = dtOO.analyticCurve(
... dtOO.bSplineCurve_pointConstructOCC(
... dtOO.vectorDtPoint3()
... << dtOO.dtPoint3(1.00, +0.00, 1.00)
... << dtOO.dtPoint3(1.10, +0.00, 0.00),
... 1
... ).result()
... )
Create the input lists with the boundary curves.
>>> speHub = [hub0, hub1]
>>> speShroud = [shroud0]
>>> inOutCurves = [inlet, outlet]
Initialize the builder.
>>> from dtOOPythonApp.builder import analyticGeometry_layerRegion
>>> builder = analyticGeometry_layerRegion(
... label = "test",
... speHub = speHub,
... speShroud = speShroud,
... inOutCurves = inOutCurves,
... layer_thickness = 0.2,
... layer_supports = [0.33, 0.66]
... ).enableDebug()
>>> builder.build()
Build the multiple bounded volume of the layer region.
>>> unstrReg, surf = builder.getUnstructuredRegion(10)
Check the virtual class name.
>>> unstrReg.virtualClassName()
'multipleBoundedVolume'
This class is used to create the geometry of a flow channel consisting of five- or six-
sided layer volumes on the hub and shroud walls, as well as a multiple bounded volume
that expands inside the flow domain and connects to the layers.
In this documentation, the region formed by the multiple bounded volume is referred to
as the *unstructured region*.
The main method of this class is the constructor, from which the remaining methods are
called. The rotational vector and the point of origin are initialized as
``rotVector_`` and ``origin_``.
The method :meth:`calculateNormalAxis` is used to calculate the normal axis
``normalAxis_`` on the flow domain cross section from the hub and shroud
curves. Furthermore, the method returns the cross section bounding box
``speBb_`` and its center point ``speCenter_``.
The bounding curves of the layers are created using the method
:meth:`createLayerBounds`. This method is called twice to create the bounds of the hub
and shroud wall layer faces. The return values of the two calls are stored in the lists
``hubLayerCurves_``, ``hubRadZero_``, and ``hubUnstructBounds_`` for the hub layer,
and ``shroudLayerCurves_``, ``shroudRadZero_``, and ``shroudUnstructBounds_`` for the
shroud layer.
The lists ``hubLayerCurves_`` and ``shroudLayerCurves_`` contain the bounding curves
of the corresponding layer faces. Within :meth:`createLayerBounds`, the method
:meth:`layerCurve` is called.
The lists ``hubRadZero_`` and ``shroudRadZero_`` contain Boolean values corresponding
to the number of layers created on the hub or shroud. These Boolean values indicate
which layers are located on a radius of zero. The method :meth:`rz_xyz` is used to
perform this check.
The lists ``hubUnstructBounds_`` and ``shroudUnstructBounds_`` contain the layer
boundary curves that extend into the flow domain and form the interfaces between the
unstructured multiple bounded volume and the layers.
The inlet and outlet curves of the flow domain cross section are shared between the
hub and shroud layer regions and the unstructured region. By splitting these curves,
the inlet boundary of the unstructured region is assigned to the variable
``interfaceUnstructBound_`` and the outlet boundary to ``outletUnstructBound_``.
The vector handler ``unstructVH_`` acts as a container for all hub and shroud boundary
curves of the unstructured region. The curves in ``hubUnstructBounds_`` and
``shroudUnstructBounds_``, as well as the curves
``interfaceUnstructBound_`` and ``outletUnstructBound_``, are stored in this vector
handler.
From the bounding curves of the hub and shroud layers, layer faces are created and
stored in ``hubLayers_`` and ``shroudLayers_``.
The layer volumes are created by rotating the layer faces around the rotation axis and
are returned by the method :meth:`getLayerList`. The method returns a list with the
following structure:
.. 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 unstructured region is created in the method
:meth:`getUnstructuredRegion`. This method returns the multiple bounded volume of the
unstructured region together with a list containing its boundary surfaces.
The :meth:`build` method is used to visualize the created geometries in `ParaView`.
"""
[docs]
def __init__(
self,
label: str,
speHub: List[analyticGeometry],
speShroud: List[analyticGeometry],
inOutCurves: List[analyticGeometry],
layer_thickness: float = 0,
layer_supports: List[float] = [],
rotVector: dtVector3 = dtVector3(0, 0, 1),
origin: dtPoint3 = dtPoint3(0, 0, 0),
) -> None:
"""Constructor.
This method:
- Creates layer boundary curves.
- Creates layer faces.
- Creates boundary curves of the unstructured region.
Parameters
----------
label: str
Label.
speHub: List[analyticGeometry]
Hub curves of the flow domain.
speShroud: List[analyticGeometry]
Shroud curves of the flow domain.
inOutCurves: List[analyticGeometry]
Inlet and outlet curves of the flow domain.
layer_thickness: float
Thickness of mesh layers in unstructured domain.
layer_supports: List[float]
Number and position of support points on channel curves for layer creation.
rotVector: dtVector3
Rotation vector.
origin: dtPoint3
Origin.
Returns
-------
None
The boundary curves of the channel are passed to the constructor through the lists
``speHub``, ``speShroud``, and ``inOutCurves``. These lists contain the curves of
the hub and shroud walls, as well as the inlet and outlet curves of the flow
channel cross section.
The following figure shows the cross section and the corresponding curves.
.. _speCurves0:
.. figure:: meridionalFigs/speCurves.png
:width: 50%
:align: center
Special hub (``speHub``) and special shroud (``speShroud``) curves (black),
together with the inlet (red) and outlet (orange) curves of the draft tube
cone.
The rotation vector and the origin are stored in the variables
``rotVector_`` and ``origin_``.
The hub and shroud curves are passed to the method
:meth:`calculateNormalAxis`. This method creates and returns the bounding box
``speBb_`` surrounding the curves. Furthermore, the center point
``speCenter_`` and the normal axis ``normalAxis_`` of the bounding box are
calculated and returned.
The boundary curves of the layers are created using the method
:meth:`createLayerBounds`. This method is called once for the hub layers and
once for the shroud layers.
As input, the wall curve lists ``speHub`` or ``speShroud`` and the inlet and
outlet curves ``inOutCurves`` are passed to the method. The layer generation
is defined by the layer thickness ``layer_thickness`` (:math:`t_{Layer}`) and
the list of support point positions along the spans of the wall curves
``layer_supports``. The label parameter is used to assign physical names to the
generated geometries for visualization.
The return values are the layer boundary curves ``hubLayerCurves_`` and
``shroudLayerCurves_``, the lists encoding whether a layer is located on a
radius of zero (``hubRadZero_`` and ``shroudRadZero_``), and the connecting
curves to the unstructured region (``hubUnstructBounds_`` and
``shroudUnstructBounds_``).
Each wall curve that is not located on a radius of zero generates one layer
face. Each layer face consists of four boundary curves. The first boundary
curve corresponds to the respective hub or shroud wall curve.
The second and fourth boundary curves extend from the start and end points
of the wall curves into the flow channel. For the first and last layer faces
at the hub and shroud, these curves correspond either to the inlet or outlet
curves or to a wall curve located on a radius of zero. At the intersection
points of two wall curves, boundary curves extending into the flow channel
are constructed such that they follow the mean normal direction of both
curves.
The third boundary curve connects the second and fourth boundary curves inside
the flow channel. It is constructed from the end points of the second and
fourth boundary curves together with additional support points. These support
points are calculated by translating points on the hub and shroud curves in the
normal direction of the curves. The positions of these points along the hub and
shroud curves are defined by the list ``layer_supports``.
The lists ``hubLayerCurves_`` and ``shroudLayerCurves_`` have the structure
``List[List[analyticGeometry]]``. In this structure, each first level list
contains the four boundary curves of one layer face stored in the corresponding
second level list.
The arrangement of the boundary curves in the second level list is as follows:
#. ``hubLayerCurves_[i][0]``: Wall curve of the *i*-th special hub curve.
#. ``hubLayerCurves_[i][1]``: Downstream curve extending from the wall into
the flow channel or, for the last layer, along the outlet or a wall curve
located on a radius of zero.
#. ``hubLayerCurves_[i][2]``: Curve extending inside the flow channel.
#. ``hubLayerCurves_[i][3]``: Upstream curve extending from the wall into the
flow channel or, for the first layer, along the inlet interface.
This ordering results in the following parameter directions of the layer
volumes after rotation:
- ``u``: Circumferential direction
- ``v``: Streamwise direction
- ``w``: Direction from the wall toward the flow domain
The following figure shows the boundary curves of the layers in black
and the layer faces in blue.
As an example of the numbering convention of the boundary curves, the indices of
the second level list in ``hubLayerCurves_[0]`` are shown.
.. _layer2d_numbering:
.. figure:: meridionalFigs/layers2d_numbering.png
:width: 40%
:align: center
Two dimensional layer faces (blue) in the draft tube cone.
The lists containing Boolean values, ``hubRadZero_`` and ``shroudRadZero_``,
contain a value of ``True`` for layers located on a radius of zero. These
layers form the last layer face on the corresponding wall. Their second
boundary curve is formed by a section of the subsequent boundary curve located
on the radius of zero. In the figures above, this applies to the curve in
``speHub[1]``.
The curves in ``hubUnstructBounds_`` and ``shroudUnstructBounds_`` are copies
of the third boundary curves of the layer faces. These curves are used to
construct the multiple bounded volume of the unstructured region.
The inlet of the flow domain created in this class corresponds to the curve in
``inOutCurves[0]``. A section of this curve is used as the inlet boundary of
the unstructured region.
The curve is trimmed according to the layer thicknesses
``layer_thickness`` using the `dtOO` class ``trimmedCurve_uBounds``. The
resulting trimmed curve is stored in ``interfaceUnstructBound_``.
To trim the outlet curve in ``inOutCurves[1]``, it must first be determined
whether a layer exists on the last hub curve. No layer is created on the last
hub curve if the hub extends to a radius of zero. In this case,
``hubRadZero_[-1] == True`` applies, and the first trim parameter is set to
zero. Otherwise, the trim parameters on both the hub and shroud sides are
determined using ``layer_thickness``. The resulting trimmed curve is stored in
``outletUnstructBound_``.
The object ``unstructVH_`` of the `dtOO` class
``vectorHandlingAnalyticGeometry`` is instantiated to contain all boundary
curves describing the contour of the unstructured region. The curves in
``hubUnstructBounds_`` and ``shroudUnstructBounds_`` are added to this object,
while ``interfaceUnstructBound_`` is prepended and
``outletUnstructBound_`` is appended.
The faces of the hub and shroud layers are created by iterating over the lists
``hubLayerCurves_`` and ``shroudLayerCurves_``. During each iteration, the
second level boundary curves are inserted into a vector handler object named
``layer_vhc``.
The layer faces are then generated using the `dtOO` class
``bSplineSurface_bSplineCurveFillConstructOCC`` with ``layer_vhc`` as input.
The resulting layer faces are stored in the lists ``hubLayers_`` and
``shroudLayers_``.
"""
logging.info("Initializing %s ..." % (label))
super(analyticGeometry_layerRegion, self).__init__()
self.label_ = label
self.rotVector_ = dtLinearAlgebra.normalize(rotVector)
self.origin_ = origin
#
# calculating the centerpoint of the special curves
# will be later used in order to make sure the layer boundaries are pointing inside the domain
# the bounding box of the special curves self.speBb_ will be used to generate
# the multi bounded volume of the unstructured region
#
self.normalAxis_, self.speCenter_, self.speBb_ = self.calculateNormalAxis(speHub + speShroud)
logging.info("### Creating Hub Layer Curves")
#
# Creating the boundary curves for the layers
# each layer is defined by four curves those returned through self.hubLayerCurves_
# the hubLayerCurves_ list has the following format:
# self.hubLayerCurve = [
# [channel curve,
# streamwise boundary 1,
# parallel to channel curve,
# streamwise boundary 0],
# ...
# ]
#
# self.hubRadZero_ contains booleans which will show true for layers which have a radius of zero
# this is used for meshing later
#
# self.hubUnstructBounds_ contains all the hub boundaries of the unstructured region
# these will be the curves parallel to the channel curves or the channel curves if they are on
# a radius of zero
#
self.hubLayerCurves_, self.hubRadZero_, self.hubUnstructBounds_ = self.createLayerBounds(
speHub,
inOutCurves,
layer_thickness,
layer_supports,
'Hub'
)
logging.info("### Creating Shroud Layer Curves")
self.shroudLayerCurves_, self.shroudRadZero_, self.shroudUnstructBounds_ = self.createLayerBounds(
speShroud,
inOutCurves,
layer_thickness,
layer_supports,
'Shroud'
)
#
# trimming the inlet curve between the layerthicknesses
# this curve will be used as a boundary for the unstructured region
#
interf = analyticCurve.MustDownCast(inOutCurves[0]).ptrDtCurve()
self.interfaceUnstructBound_ = analyticCurve(
trimmedCurve_uBounds(
interf,
interf.u_l(layer_thickness),
interf.u_l(interf.length() - layer_thickness)
).result()
)
#
# trimming the outlet curve
#
# deciding the trimm position of the outlet curve at the hub
outlet_trim = layer_thickness
# if the outlet curve hub curve at the outlet has a radius of zero
# the following condtion will be fulfilled:
if self.hubRadZero_[-1] == True:
# sets the trim length to zero
outlet_trim = 0
# trimming the outlet curve for the boundary of the unstructured domain
outl = analyticCurve.MustDownCast(inOutCurves[-1]).ptrDtCurve()
self.outletUnstructBound_ = analyticCurve(
trimmedCurve_uBounds(
outl,
outl.u_l(outlet_trim),
outl.u_l(outl.length() - layer_thickness)
).result()
)
# adding the boundary curves of the unstructured regions to a VH
self.unstructVH_ = vectorHandlingAnalyticGeometry()
# adding the interface curve first
self.unstructVH_.push_back(self.interfaceUnstructBound_.clone())
# adding the region bounds (the curves named parallel in the createLayerBounds function)
for bound in self.hubUnstructBounds_ + self.shroudUnstructBounds_:
self.unstructVH_.push_back(bound.clone())
# adding the outlet boundary last
self.unstructVH_.push_back(self.outletUnstructBound_.clone())
logging.info("### Creating Hub Layer Faces")
# creatig the layer faces from the curves
self.hubLayers_ = []
for i in range(len(self.hubLayerCurves_)):
layer_vhc = vectorHandlingConstDtCurve()
# pushing the individual curves in a vhc
for n in range(len(self.hubLayerCurves_[i])):
layer_vhc.push_back(analyticCurve.MustDownCast(self.hubLayerCurves_[i][n]).ptrDtCurve())
# creating the faces by filling the curves
self.hubLayers_.append(
analyticSurface(
bSplineSurface_bSplineCurveFillConstructOCC(layer_vhc).result()
)
)
del layer_vhc
logging.info("### Creating Shroud Layer Faces")
self.shroudLayers_ = []
for i in range(len(self.shroudLayerCurves_)):
layer_vhc = vectorHandlingConstDtCurve()
for n in range(len(self.shroudLayerCurves_[i])):
layer_vhc.push_back(analyticCurve.MustDownCast(self.shroudLayerCurves_[i][n]).ptrDtCurve())
self.shroudLayers_.append(
analyticSurface(
bSplineSurface_bSplineCurveFillConstructOCC(layer_vhc).result()
)
)
del layer_vhc
[docs]
def createLayerBounds(self, layerCurve, inOutCurves, thickness, supports, lab):
"""Create boundary curves of the layer faces.
This method:
- Iterates over the wall curves and checks if they are on a radius of zero.
- Iterates over curves and combines them if they have a steady transition.
- Iterates over curves and creates boundary curves of the layer faces.
- Returns the lists with layer bounds and boundary curves for the
unstructured region as well as a list telling which layers are on a radius
of zero.
Parameters
----------
layerCurve: List[analyticGeometry]
List of wall curves.
inOutCurves: List[analyticGeometry]
List with inlet and outlet curves.
thickness: float
Layer thickness.
supports: List[float]
List with positions of support points in percent along the parametrized span of
the wall curve.
lab: string
label
Returns
-------
returnBounds: List[List[analyticGeometry]]
Boundary curves of each layer on the wall curves.
on_rad_zero: List[Bool]
Information for each layer, telling if the radius is on zero.
layerParallel: List[analyticGeometry]
Inner boundary curves of the layers, used for the multiple bounded volume.
This method takes a list of wall curves ``layerCurve``
(either hub or shroud) together with the inlet and outlet curves
``inOutCurves`` as input.
Furthermore, the layer thickness ``layer_thickness`` and the list of
support point positions ``supports`` are passed to the method.
``supports`` defines both the number of support points and their
positions on each wall curve, for the creation of the third bounding
curves, in the method :meth:`layerCurve`.
The label ``lab`` is used to assign names to the generated geometries.
The following figure shows the hub curves of the draft tube example
stored in ``layerCurve`` (black) together with their numbering in the
list. ``layerCurve[2]`` and the inlet curve ``inOutCurves[0]`` (red)
are trimmed.
.. _createLayerBounds0:
.. figure:: meridionalFigs/createLayerBounds0.png
:width: 70%
:align: center
Hub curves of the `draft tube cone` example in ``layerCurve`` (black)
and the inlet curve in ``boundsGlob[0]`` (red).
In a first loop checks are performed, which of the curves in ``layerCurve``
get a layer region. This is only the case for curves which are not located
on a radius of zero.
The following figure shows the activity diagram of the first loop.
.. _createLayerBounds_activity0:
.. figure:: meridionalFigs/createLayerBounds_activity0.png
:width: 70%
:align: center
Activity diagram of the first loop in :meth:`createLayerBounds`.
**Initialize Lists**
A copy of ``inOutCurves`` is created and assigned to ``boundsGlob``.
This list contains the global boundary curves that form part of the
boundary curves of the first and last layer faces.
The layer faces are constructed from four boundary curves.
The following empty lists are prepared to store the layer boundary
curves. The numbering *first*, *second*, *third*, and *fourth*
corresponds to the definition given in the constructor documentation
:meth:`__init__`.
- ``layerStreamOrtho``:
List containing the second and fourth boundary curves, which extend
from the wall into the flow domain.
- ``layerParallel``:
List containing the third boundary curves, which extend approximately
parallel to the wall.
- ``returnBounds``:
List containing all boundary curves of the layer faces.
- ``on_rad_zero``:
List tracking which curves in ``layerCurve`` are located on a radius
of zero.
**Iterate over Curves**
In the first loop, a check is performed to determine which curves in
``layerCurve`` are located on a radius of zero. This check is carried
out using the method :meth:`rz_xyz`, which takes a point as input and
performs a coordinate transformation from Cartesian coordinates into a
cylindrical coordinate system defined by the origin ``origin_`` and the
rotation axis ``rotAxis_``.
The method returns the radius and axial position of the point in
cylindrical coordinates.
The check is performed on the radii of the start and end points of each
curve using the method ``inXYZTolerance`` of the class
``analyticGeometry``. This method returns ``True`` if the radius lies
within the tolerance of zero.
Two cases are handeled.
**The curve is on a radius of zero:**
::
onRotAxis_0 and onRotAxis_1
If the check returns ``True`` for both points, the curve is considered
to lie on a radius of zero. In this case, the outlet curve in
``boundsGlob[-1]`` is replaced by the current curve. This replacement
is necessary because the curve forms the second boundary curve of the last
layer face on the corresponding wall.
The last entry of the list ``on_rad_zero`` is then set to ``True``.
A curve extending from the layer thickness position to the end of the
curve located on the rotation axis is created and assigned to the
variable ``unstructOnRotAxis``. This curve is required as a boundary
curve of the unstructured region.
The curve located on the radius of zero is then removed from the list
of layer curves, and the loop is terminated.
**The curve is not on a radius of zero:**
::
else
If the curve is not located on a radius of zero, the list
``on_rad_zero`` is extended with a value of ``False``.
The following figure shows the hub curves together with their assigned
Boolean values in ``on_rad_zero`` and the global layer boundaries stored
in ``boundsGlob``.
.. _createLayerBounds1:
.. figure:: meridionalFigs/createLayerBounds1.png
:width: 70%
:align: center
Hub curves of the `draft tube cone` example together with their
assigned Boolean values in the ``on_rad_zero`` list. The curves that
are part of the global layer boundaries are shown in red.
In a second loop, curves with a continuous transition at their
connection points are combined within a specified tolerance range.
The following figure shows the activity diagram of this loop.
.. _createLayerBounds_activity1:
.. figure:: meridionalFigs/createLayerBounds_activity1.png
:width: 70%
:align: center
Activity diagram of the second loop in :meth:`createLayerBounds`.
**Itreate over Curves**
In the loop, the tangent vectors, at the transition points, of two
consecutive curves ``v0`` and ``v1`` are calculated. This
transition point corresponds to the end point of ``v0`` and the start
point of ``v1``.
Using the angle between the tangent vectors ``v0_firstDer`` and
``v1_firstDer``, a continuity condition ``is_steady`` is formulated.
This condition evaluates to ``True`` if the angular deviation between
the two curves is less than or equal to two degrees.
If the condition evaluates to ``True``, the curves are combined and
stored in the variable ``current_curve``, which is then used in the next
iteration. Furthermore, the corresponding flag in the ``on_rad_zero``
list is removed.
If the condition evaluates to ``False``, the current curve is appended
to the list ``speCurve`` and the consecutive curve is assigned to
``current_curve``.
After the loop has finished, all curves for which layer faces are
generated are stored in ``speCurve``.
The boundary curves for the layers are generated in the third loop,
which iterates over ``speCurve``. The following figure shows the
activity diagram of this loop.
.. _createLayerBounds_activity2:
.. figure:: meridionalFigs/createLayerBounds_activity2.png
:width: 100%
:align: center
Activity diagram of the third loop in
:meth:`createLayerBounds`. Orange action blocks correspond to the
creation of the second and fourth boundary curves, while blue action
blocks correspond to the creation of the third boundary curves.
Three different conditions may apply for each curve within the loop.
#. **The first layer face along the wall**
::
i == 0
The fourth boundary curve of the first layer face is formed by the
inlet curve stored in ``boundsGlob[0]``. This boundary curve is
created by trimming the inlet curve from its shared point with the
layer curve ``speCurve[i]`` to the layer thickness specified by
``layer_thickness``.
The connection point must correspond to the start of the parameter
span (0 %) of the global boundary curve. Depending on whether hub or
shroud wall layers are created, this is not always the case.
To ensure that both curves share the same start point, the distance
between their start points is computed and checked against the
geometric tolerance. The distance is calculated using the method
``distance`` of the `dtOO` class ``dtLinearAlgebra``. The tolerance
check is performed using
``analyticGeometry.inXYZTolerance``.
If the distance exceeds the tolerance, the global boundary curve is
reversed.
The resulting trimmed curve is appended to the list
``layerStreamOrtho``.
#. **The last layer face along the wall**
::
i == len(speCurve)
The second boundary curve of the last layer face is formed by the
second global boundary ``boundsGlob[-1]``. This boundary can either
be on the the outlet curve of the layered region or on a curve located
on the radius of zero.
The layer boundary is created similarly to case 1. In this case, the
distance between the end point of the wall curve
``speCurve[i-1]`` and the start point of the global boundary curve
is used to orient the global boundary curve correctly.
The third layer boundary curve, extending approximately parallel to
the wall, is created using the method :meth:`layerCurve`. The
resulting curve is appended to the list ``layerParallel``.
The list ``newLayer`` is created. This list contains the four
boundary curves of the last layer face.
#. **Regular layer face**
::
else
In this case, the wall curve in each iteration is
``speCurve[i-1]``. For each wall curve, the second and third layer
boundary curves are created. The second boundary curve is generated
directly within the loop, while the third boundary curve is created
using the method :meth:`layerCurve`. The fourth boundary curve is
taken from the second boundary curve generated in the previous
iteration.
**Get Cuves v0 and v1**
The second layer boundary curve is constructed so it extends
in the mean normal direction :math:`\mathbf{v_{mean}}` between the
curves ``speCurve[i-1]`` and ``speCurve[i]`` at their shared point
:math:`P_0`. The curves are assigned to the variables ``v0`` and
``v1``.
**Caluclate the direction of the second boundary curve**
The mean normal direction is calculated as follows:
.. math::
\\mathbf{v_{mean}} = \\frac{\\mathbf{n_0} + \\mathbf{n_1}}{\\|\\mathbf{n_0} + \\mathbf{n_1}\\|}
The normal directions of the curves
(:math:`\mathbf{n_0}` and :math:`\mathbf{n_1}`) at the shared point
are calculated as the cross products of the normal axis of the
channel cross section ``normalAxis_``
(:math:`\mathbf{n_{global}}`) and the tangential directions of the
curves at the shared point
(:math:`\mathbf{t_0}` and :math:`\mathbf{t_1}`).
.. math::
\\mathbf{n} = \\frac{\\mathbf{t} \\times \\mathbf{n_{global}}}{\\|\\mathbf{t} \\times \\mathbf{n_{global}}\\|}
The resulting vector :math:`\mathbf{v_{mean}}` is stored in the
variable ``layerVec``.
The following figure illustrates the generation of the second layer
boundary curves.
.. _createLayerBounds2:
.. figure:: meridionalFigs/createLayerBounds2.png
:width: 70%
:align: center
Creation of the second and fourth layer boundary curves (blue) extending from the wall curves into the flow domain.
**Calculate a Vector Pointing Into the Center**
The vector ``insideVec`` is created by subtracting the global center
point ``speCenter_`` from the shared point of the two wall curves. This
results in a vector pointing from the shared point toward the interior
of the flow domain.
**Calculate the Normal Vector, Check the Direction and Calculate the Length**
The normal vector ``normalVec`` of curve ``v0`` at the shared
point is defined with the length specified with ``thickness``
(:math:`t_{Layer}`).
By calculating the dot product of the vectors ``normalVec`` and
``insideVec``, it can be determined whether ``normalVec`` points toward
the interior or exterior of the flow domain. If the dot product is
negative, the direction of ``normalVec`` is reversed by multiplying it
with ``-1``.
The length of the layer boundary curve is calculated from the length of
``normalVec`` and the angle between ``normalVec`` and ``layerVec``
(see :numref:`createLayerBounds2`).
**Create the Layer Boundary**
The layer boundary curve is constructed between the points
:math:`P_0` and :math:`P_1`. The point :math:`P_1` is calculated from
the point :math:`P_0` by adding the direction vector ``layerVec``
(:math:`\mathbf{v_{mean}}`) multiplied by the required layer thickness.
.. math::
P_1 = P_0 + \\mathbf{v_{mean}} * t_{Layer} / cos(\lambda)
The resulting curve is stored in the list ``layerStreamOrtho``.
**Create third Boundary Curve**
The third boundary curve is generated using the method
:meth:`layerCurve`. The method takes the list
``layerStreamOrtho``, the current wall curve ``v0``, and the current
iteration index ``i`` as input arguments.
The generation of the curve is controlled by the layer thickness
``thickness`` and the support point positions defined in ``supports``.
The returned curve ``ext`` is stored in the list
``layerParallel``.
The creation of the third layer boundary curve is illustrated in the
following figure.
.. _createLayerBounds3:
.. figure:: meridionalFigs/createLayerBounds3.png
:width: 75%
:align: center
Creation of the third layer boundary curve (blue) extending into the
flow domain.
**Append List of Boundary Curves**
The boundary curves of the current layer face are stored in the list
``newLayer``. In this list, the boundary curves are ordered according
to the numbering convention used throughout this documentation, with the
wall curve ``v0`` forming the first entry.
The second entry contains the curve in ``layerStreamOrtho`` generated in
the current iteration. The fourth entry contains the curve in
``layerStreamOrtho`` generated in the previous iteration. The third
entry contains the curve stored in ``layerParallel``.
The following figure shows all boundary curves surrounding the layer
faces together with their orientations. For the first layer face, the
numbering of the boundary curves is shown explicitly.
.. _createLayerBounds4:
.. figure:: meridionalFigs/createLayerBounds4.png
:width: 70%
:align: center
All layer boundary curves surrounding the layer face (blue).
The list ``newLayer`` is appended to the list ``returnBounds``.
After the loop has concluded, the curve ``unstructOnRotAxis`` is
appended to the list ``layerParallel`` if it exists.
The method returns the lists ``returnBounds``, ``on_rad_zero``, and
``layerParallel``.
"""
#
# preparing lists
#
# global boundaries of the flow domain
boundsGlob = inOutCurves.copy()
# second and fourth layer boundaries between the special curves
# will extend from the channel curves inside the domain, orthogonally to streamwise direction
layerStreamOrtho = []
# third layer boundaries
# layerboundaries which will be parallel to channel curves
layerParallel = []
# list for return of the boundaries (will be explained later)
returnBounds = []
# list which tracks if the layer will end on a radius of zero
# will be used for mesh generation later
on_rad_zero = []
unstructOnRotAxis = None
#
# first loop
# iterating over all curves and deciding which curves get Layers
#
for i in range(len(layerCurve)):
#
# checking if the curve has a radius of zero
#
rz_0 = self.rz_xyz(layerCurve[i].getPointPercent(0.0))
rz_1 = self.rz_xyz(layerCurve[i].getPointPercent(1.0))
onRotAxis_0 = analyticGeometry.inXYZTolerance(rz_0[0])
onRotAxis_1 = analyticGeometry.inXYZTolerance(rz_1[0])
logging.debug(
"speHub: |<- %5.2f --- %5.2f ->|" % (rz_0[0], rz_1[0])
)
#
# if the radius is zero the curve will be assigned as the second global boundary
# the layers will end here in streamwise direction
#
if onRotAxis_0 and onRotAxis_1:
# overwrites the outlet curve as global boundary
boundsGlob[-1] = layerCurve[i]
logging.info("Set %s curve no. %i as the global layer boundary" % (lab, i))
on_rad_zero[-1] = True
# saving the segment of the curve from the thickness to the maximum
# will later be a curve for the segment of the unstructured mesh
lc = analyticCurve.MustDownCast(layerCurve[i]).ptrDtCurve()
unstructOnRotAxis = analyticCurve(
trimmedCurve_uBounds(
lc, lc.u_l(thickness), lc.getUMax()
).result()
)
del layerCurve[i]
break
#
# else False is appended to on_rad_zero
#
else:
on_rad_zero.append(False)
#
# second loop
# iterating over curves and deciding between which curves a layerStreamOrtho curve
# should be created and which curves should be combined
#
speCurve = []
current_curve = analyticCurve.MustDownCast(layerCurve[0])
# iterating over the curves
# if defined curve segments have a steady transition and are not created as a split
# the curves will be combined and no layer boundary is defined between them
for i in range(len(layerCurve) - 1):
v0 = analyticCurve.MustDownCast(layerCurve[i])
v1 = analyticCurve.MustDownCast(layerCurve[i + 1])
v0_firstDer = dtLinearAlgebra.normalize(v0.firstDerUPercent(1))
v1_firstDer = dtLinearAlgebra.normalize(v1.firstDerUPercent(0))
angle = dtLinearAlgebra.angle(v0_firstDer, v1_firstDer)
# condition for combining the curves:
is_steady = (angle <= 2*(numpy.pi/180))
# if the condition is fulfilled the curves will be combined
# no splits will be made here
if is_steady:
current_curve = analyticCurve(
bSplineCurve_curveConnectConstructOCC(
current_curve.ptrDtCurve(),
v1.ptrDtCurve()
).result()
)
del on_rad_zero[i]
logging.info("Combining curves %i and %i to make one layer" % (i, i+1))
# else the curves will be appended
else:
speCurve.append(current_curve)
current_curve = v1
# appending the last curve outside of the loop
# speCurve will be used in the rest of this method
speCurve.append(current_curve)
logging.info("Creating %i Layers on %s" % (len(speCurve), lab))
#
# third loop
# iterate over all curves and create the bounding curves
#
for i in range(len(speCurve)+1):
# the first layer boundary is built from the first global boundary
# (the last interface in streamwise direction)
if i == 0:
# check if the direction of the interface is correct
# if its not correct, the curve will be reversed for trimming
base = analyticCurve.MustDownCast(speCurve[i]).ptrDtCurve()
dc = analyticCurve.MustDownCast(boundsGlob[0]).ptrDtCurve()
distance = dtLinearAlgebra.distance(
base.pointPercent(0.0), dc.pointPercent(0.0)
)
if not analyticGeometry.inXYZTolerance(distance):
logging.debug("Reverse direction. distance = %f" % (distance))
dc = geomCurve_curveReverseConstructOCC(dc, True).result()
direction = -1.0
# trimms the global boundary at the thickness of the layer
layerStreamOrtho.append(
analyticCurve(
trimmedCurve_uBounds(
dc, dc.getUMin(), dc.u_l(thickness)
).result()
)
)
logging.info("Created orthogonal layer boundary no. %i" % i)
# the last layer boundary is built from the second global boundary
elif i == len(speCurve):
base = analyticCurve.MustDownCast(speCurve[i-1]).ptrDtCurve()
dc = analyticCurve.MustDownCast(boundsGlob[-1]).ptrDtCurve()
distance = dtLinearAlgebra.distance(
base.pointPercent(1.0), dc.pointPercent(0.0)
)
if not analyticGeometry.inXYZTolerance(distance):
logging.debug("Reverse direction. distance = %f" % (distance))
dc = geomCurve_curveReverseConstructOCC(dc, True).result()
direction = -1.0
layerStreamOrtho.append(
analyticCurve(
trimmedCurve_uBounds(
dc, dc.getUMin(), dc.u_l(thickness)
).result()
)
)
logging.info("Created orthogonal layer boundary no. %i" % i)
# calculates the parallel curve to the channel curve
v0 = analyticCurve.MustDownCast(speCurve[i-1])
ext = self.layerCurve(
layerStreamOrtho,
i,
v0,
thickness,
supports,
lab
)
layerParallel.append(ext)
logging.info("Created parallel layer boundary on curve no. %i" % (i-1))
# appends the boundary curves for this boundary to the return list
newLayer = [
v0.clone(),
layerStreamOrtho[i].clone(),
layerParallel[i-1].clone(),
layerStreamOrtho[i-1].clone()
]
returnBounds.append(newLayer)
# for the curves inbetween the global boundrries the layers curves othogonal to the
# streamwise direction have to be calculated
else:
# get the two curves which are in sequence to each other
v0 = analyticCurve.MustDownCast(speCurve[i-1])
v1 = analyticCurve.MustDownCast(speCurve[i])
# calculates the resulting vector of the gradients at the intersect point
# WARNING: this will retun the wrong vector if both curves have the same gradient
#layerVec = dtLinearAlgebra.normalize(
# dtLinearAlgebra.normalize(v0.firstDerUPercent(1.0))
# + dtLinearAlgebra.normalize(v1.firstDerUPercent(0.0))*(-1)
#)
# calculates the direction of the layer curve as the sum of the normals
# of the curves at the intersect point
layerVec = dtLinearAlgebra.normalize(
dtLinearAlgebra.normalize(
dtLinearAlgebra.crossProduct(
v0.firstDerUPercent(1), self.normalAxis_
)
)
+ dtLinearAlgebra.normalize(
dtLinearAlgebra.crossProduct(
v1.firstDerUPercent(0), self.normalAxis_
)
)
)
# this vector connects the start point of the layer curve
# with the center of the bounding box of the special channel
# it will be used to check if the layer curve points inside the domain
insideVec = dtVector3(
analyticCurve.MustDownCast(v0).getPointPercent(1) - self.speCenter_
)
# calculating a normalvector on the origin point of the layer
# this vector will have the length of the layer thickness
normalVec = dtLinearAlgebra.normalize(
dtLinearAlgebra.crossProduct(
v0.firstDerUPercent(1), self.normalAxis_
)
) * thickness
# comparing the vectors
# if the dot product is smaller than zero the normalvector
# points out of the domain and has to be reversed (times -1)
direction = 1
if dtLinearAlgebra.dotProduct(normalVec, insideVec) < 0:
direction = -1
normalVec = normalVec*direction
# extracting the length of the layer boundary
# so the thickness of the layer is consistent with the normal vector
angle = dtLinearAlgebra.angle(layerVec, normalVec)
length = dtLinearAlgebra.length(normalVec)/numpy.cos(angle)
# creating the layer boundary curve
# the lenth of the curve is set so the thickens is consistent at the
# normal at 100 percent of the curve
layerStreamOrtho.append(
analyticCurve(
bSplineCurve_pointConstructOCC(
vectorDtPoint3()
<< analyticCurve.MustDownCast(v0).getPointPercent(1)
<< analyticCurve.MustDownCast(v0).getPointPercent(1)+(layerVec * length),
1
).result()
)
)
logging.info("Created orthogonal layer boundary no. %i" % i)
# creating the layer curve parallel to the channel curve
ext = self.layerCurve(layerStreamOrtho, i, v0, thickness, supports, lab)
layerParallel.append(ext)
logging.info("Created parallel layer boundary on curve no. %i" % i)
# appending the curves for return
# the curves of one layer are appended in clockwise direction
newLayer = [
v0.clone(), # channel segment
layerStreamOrtho[i].clone(), # ortho0
layerParallel[i-1].clone(), # parallel to channel
layerStreamOrtho[i-1].clone() # ortho1
]
returnBounds.append(newLayer)
logging.info("Finished creating %s layer curves" % lab)
# appending the curve on radius 0 if it exists
# the parallel boundaries and the radius 0 curve are needed as boundaries for the
# unstructured mesh
if unstructOnRotAxis != None:
layerParallel.append(unstructOnRotAxis)
return returnBounds, on_rad_zero, layerParallel
# creates the layer curve parallel to the channel curves
# the curve is created from the two points at 100 percent of the matching
# curves orthogonally to the streamwise direction and offsets from the points
# of the channel curve at the support points
[docs]
def layerCurve(self, layerStreamOrtho, i, curve, thickness, supports, lab):
"""Create third boundary curve extending paralell to the wall.
This method:
- Creates third layer bounding curve from the end points of the second and
third bounding curves and support points.
Parameters
----------
layerStreamOrtho: List[analyticGeometry]
Boundary curves of the layer faces orthogonal to the wall curve.
i: int
Iterator, iD of the wall curve.
curve: analyticGeometry
Wall curve
thickness: float
Layer thickness
supports: List[float]
List with positions of support points in percent along wall curve
lab: string
label
Returns
-------
ext: analyticGeometry
Created layer curve inside the layer region
The inputs to this method are the list of second and fourth layer
boundary curves ``layerStreamOrtho``, the wall curve ``curve`` for
which the third boundary curve is created, and the iteration index
``i`` of this curve within the loop in
:meth:`createLayerBounds`.
The generation of the boundary curve is controlled by the layer
thickness ``thickness`` and the number and positions of support points
specified in ``supports``.
The following figure illustrates the creation of the third layer
boundary curve.
.. _layerCurve:
.. figure:: meridionalFigs/layerCurve.png
:width: 60%
:align: center
Creation of the third layer boundary curve (blue) using the method
:meth:`layerCurve`.
**Get second and fourth Boundary Curves**
The iteration index ``i`` is used to retrieve the second and fourth boundary
curves of the current layer from ``layerStreamOrtho``. These curves are
assigned to the variables ``bound0`` and ``bound1``.
**Calculate Offset Direction**
Using the method :meth:`calculateNormalAxis`, the center point of the
bounding box surrounding the two boundary curves and the wall curve is
calculated and stored in ``layerCenter``.
This point is used to define the vector ``insideVec``, which points from
the point located at 50 % of the parameter range of ``curve`` toward the
interior of the layer face.
The vector ``normalVec`` is created to point in the normal direction of
the wall curve at 50 % of its parameter span. It is calculated as the
cross product of the tangent vector of the curve at this point and the
global normal axis ``normalAxis_``.
Using the dot product of ``normalVec`` and ``insideVec``, the offset
direction from the wall curve is determined through the variable
``direction``, which is assigned either ``1`` or ``-1``.
**Create Boundary Curve**
A container object of the `dtOO` class ``vectorDtPoint3`` is created.
The end point of the fourth layer boundary curve ``bound0`` is appended
to this container.
By iterating over ``supports``, the support points :math:`P_s` are
generated. Each entry in ``supports`` contains a floating point value
defining a relative parameter position along the wall curve.
For each support value, a base point :math:`P_0` is calculated on the
wall curve.
The support point is then created by offsetting :math:`P_0` in the
normal direction of the wall curve at :math:`P_0`.
The normal direction is defined as the cross product between the tangent
direction :math:`\mathbf{t}` at :math:`P_0` and the global normal axis
``normalAxis_`` (:math:`\mathbf{n_{global}}`).
The offset length is prescribed by ``layer_thickness``
(:math:`t_{Layer}`). The value of ``direction`` (:math:`k`) ensures
that the support points are generated inside the flow domain.
The following equation describes the calculation of a support point:
.. math::
P_s = P_0 + \\frac{\\mathbf{t} \\times \\mathbf{n_{global}}}{\\|\\mathbf{t} \\times \\mathbf{n_{global}}\\|} * t_{Layer} * k
The generated support points are appended to the point container.
After all support points have been generated, the end point of the
second layer boundary curve ``bound1`` is appended to the container.
The third layer boundary curve is then created as a
``bSplineCurve_pointConstructOCC`` object using the points stored in the
container.
The method returns the generated curve.
"""
logging.info("creating layer curve no. %i" % i)
# finding the correct curves for the layer
# bound is the curve orthogonal to the streamwise direction
bound0 = analyticCurve.MustDownCast(layerStreamOrtho[i-1]).ptrDtCurve()
bound1 = analyticCurve.MustDownCast(layerStreamOrtho[i]).ptrDtCurve()
curve0 = analyticCurve.MustDownCast(curve).ptrDtCurve()
# setting the direction of the offset by checking the direction of the normalvector
# as insideVector the centerpoint of the layer is used
layer_list = [layerStreamOrtho[i-1], layerStreamOrtho[i], curve]
layerBox, layerCenter, unusedBb = self.calculateNormalAxis(layer_list)
insideVec = dtVector3(
curve.getPointPercent(0.5) - layerCenter
)
normalVec = dtLinearAlgebra.crossProduct(
curve0.firstDerPercent(0.5), self.normalAxis_
)
direction = 1
if dtLinearAlgebra.dotProduct(normalVec, insideVec) < 0:
logging.info("changing offset direction")
direction = -1
# Create point container;
# first and last points are set as the 100 percent points of the orthogonal curves
# the points inbetween are created from an offset of the support points of the channel curves
container = vectorDtPoint3()
# first point
container = container << bound0.pointPercent(1.0)
# support points
for support in supports:
container = container << (
curve0.pointPercent(support)
+ dtLinearAlgebra.normalize(
dtLinearAlgebra.crossProduct(
curve0.firstDerPercent(support), self.normalAxis_
)
)
* thickness
* direction
)
## debug statement to plot the vectors from the channel wall to the parallels
#self.appendAnalyticGeometry(
# analyticCurve(
# bSplineCurve_pointConstructOCC(
# vectorDtPoint3()
# << curve0.pointPercent(support)
# << (curve0.pointPercent(support)
# + dtLinearAlgebra.normalize(
# dtLinearAlgebra.crossProduct(
# curve0.firstDerPercent(support),
# self.normalAxis_
# )
# )
# * thickness * direction
# ),
# 1
# ).result()
# ),
# "debug_supportVector_"+lab+str(i)+"_"+str(support)
#)
# last point
container = container << bound1.pointPercent(1.0)
# creating the curve from the point container
ext = analyticCurve(
bSplineCurve_pointConstructOCC(container, 2).result()
)
return ext
[docs]
def rz_xyz(self, pp: dtPoint3) -> dtPoint2:
"""Transform a point in cartesian coordinates into cylindric coordinates.
This method:
- Transforms a point in the xyz-coordinates into cylindric coordinates.
- Returns radius and z-position of the point.
Parameters
----------
pp: dtPoint3
Point in xyz-coordinates.
Returns
-------
: dtPoint2
dtPoint2 object containing the radius and z-position of pp
The point ``pp`` in carthesian coordinates is reparametrized in cylindirc
coordinates based on the origin point ``origin_`` and the rotational vector
``rotVector_``.
Returns the radius ``rr`` and the z-coordinate ``zz`` in a `dtPoint2`
object.
"""
vv = pp - self.origin_
zz = dtLinearAlgebra.dotProduct(vv, self.rotVector_)
rr = dtLinearAlgebra.length(vv - self.rotVector_ * zz)
return dtPoint2(rr, zz)
#
# calculate the normal axis of the whole domain
#
[docs]
@staticmethod
def calculateNormalAxis(curves):
"""Calculate the bounding box and normal axis of curves in one plane.
This method:
- Creates a bounding box around the curves.
- Calculates the normal axis on the bounding box.
- Calculates the center of the bounding box.
- Returns normal axis, center and bounding box.
Parameters
----------
curves: List[analyticGeometry]
List of curves.
Returns
-------
normalAxis: dtVector3
Normal axis of the bounding box.
bbCenter: dtPoint3
Center point of the bounding box.
bb: pairDtPoint3
Bounding box points.
"""
vv = vectorDtPoint3()
for curve in curves:
vv = vv << curve.getPointPercent(0.0) << curve.getPointPercent(1.0)
# calculates a bounding box of all the start and endpoints of the curves
bb = dtLinearAlgebra.boundingBox(vv)
dist = bb[0] - bb[1]
logging.info("Create bounding box from %d points." % vv.size())
logging.debug(
" bb[0] : %5.2f %5.2f %5.2f" % (bb[0][0], bb[0][1], bb[0][2])
)
logging.debug(
" bb[1] : %5.2f %5.2f %5.2f" % (bb[1][0], bb[1][1], bb[1][2])
)
logging.info(
" dist : %5.2f %5.2f %5.2f" % (dist[0], dist[1], dist[2])
)
# calculates the normal axis
normalAxis = dtVector3(
float(analyticGeometry.inXYZTolerance(dist[0])),
float(analyticGeometry.inXYZTolerance(dist[1])),
float(analyticGeometry.inXYZTolerance(dist[2])),
)
logging.info(
" -> normalAxis = (%5.2f %5.2f %5.2f)"
% (normalAxis[0], normalAxis[1], normalAxis[2]) # normal axis to bounding box
)
# calculationg center point of bounding box
bbCenter = dtPoint3(
(bb[0][0]+bb[1][0])*0.5,
(bb[0][1]+bb[1][1])*0.5,
(bb[0][2]+bb[1][2])*0.5,
)
logging.info("Centerpoint : [%f, %f, %f]" % (bbCenter[0], bbCenter[1],bbCenter[2]))
return normalAxis, bbCenter, bb
[docs]
def build(self) -> None:
"""Plot the instantiated geometries in paraview if debug is enabeled.
Parameters
----------
None
Returns
-------
None
This method allows the instantiated geometries to be plotted in `ParaView`, by
adding them to the analytic geometry container.
If the code is run in paraview, the geometries can be found with the
`FindAndShow` method.
"""
logging.info("Building %s ..." % (self.label_))
if self.debug():
try:
ii = 0
for layer in self.hubLayers_:
self.appendAnalyticGeometry(
layer.clone(),
"debug_hubLayer_" + str(ii) + "_" + self.label_,
)
ii = ii + 1
except:
pass
try:
ii = 0
for layer in self.shroudLayers_:
self.appendAnalyticGeometry(
layer.clone(),
"debug_shroudLayer_" + str(ii) + "_" + self.label_,
)
ii = ii + 1
except:
pass
try:
ii = 0
for aCurve in self.unstructVH_:
self.appendAnalyticGeometry(
aCurve.clone(),
"debug_unstructBound_" + str(ii) + "_" + self.label_,
)
ii = ii+1
except:
pass
try:
for k in range(len(self.hubLayerCurves_)):
for c in range(len(self.hubLayerCurves_[k])) :
self.appendAnalyticGeometry(
self.hubLayerCurves_[k][c].clone(),
"debug_hubLayerCurve_" + str(k) + str(c) + "_" + self.label_,
)
except:
pass
try:
for k in range(len(self.shroudLayerCurves_)):
for c in range(len(self.shroudLayerCurves_[k])) :
self.appendAnalyticGeometry(
self.shroudLayerCurves_[k][c].clone(),
"debug_shroudLayerCurve_" + str(k) + str(c) + "_" + self.label_,
)
except:
pass
try:
ii = 0
# defines which channels will be rotatetd for the debug geometries
for channel in (
self.hubLayers_
+ self.shroudLayers_
):
off = float(ii) * 0.10 # rotational offset of each channel
self.appendAnalyticGeometry(
partRotatingMap2dTo3d(
self.rotVector_,
channel,
off - 0.05, # min segment in percent
off + 0.05, # max segment in percent
).clone(),
"debug_layer_" + str(ii) + "_" + self.label_,
)
ii = ii + 1
except:
pass
return
# returns a list containing the hub and shroud layers as well as information
# on what kind of layer it is
[docs]
def getLayerList(
self,
nSlices: int
) -> List[
List[
List[analyticGeometry] | List[bool]
]
]:
"""Create the layer volumes and return the layer data.
This method:
- Rotates the hub and shroud layer faces to create volumes.
- Returns the generated layer data in structured lists.
Parameters
----------
nSlices : int
Number of rotationally periodic slices.
Returns
-------
layerList : List[List[List[analyticGeometry] | List[bool]]]
List containing the generated layer volumes and information about
whether a layer is located on a radius of zero.
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 layer faces stored in ``hubLayers_`` and ``shroudLayers_`` are
rotated around ``rotVector_``. The rotation angle is defined by the
number of slices as:
.. math::
{360^\circ}/{n_{Slices}}
The generated layer volumes are stored in the lists ``hubLayer3d`` and
``shroudLayer3d``.
The following figure shows the resulting volumes of the draft tube cone.
.. _layers3d0:
.. figure:: meridionalFigs/layers3d.png
:width: 50%
:align: center
Volumes of the draft tube cone.
Layer faces (blue), inlet (red), and outlet (orange).
The hub and shroud layer volumes together with the lists
``hubRadZero_`` and ``shroudRadZero_`` are returned in the following
format:
.. 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
"""
logging.info("Request Layer Volumes: %i Slices" % nSlices)
#
# Create the layer volumes
# partRotatingMap2dTo3d takes the rotation angle in %
#
hubLayer3d = []
for layer in self.hubLayers_:
hubLayer3d.append(
partRotatingMap2dTo3d(
self.rotVector_,
layer,
0.00,
(1/nSlices),
).clone()
)
shroudLayer3d = []
for layer in self.shroudLayers_:
shroudLayer3d.append(
partRotatingMap2dTo3d(
self.rotVector_,
layer,
0.00,
(1/nSlices),
).clone()
)
#
# return the layer data in the following nested list:
# layers = [[hub layer lists],[shroud layer list]]
# with:
# [hub layer lists] = [[3d layer domain], [bool list radius zero]]
#
layerList = [
[hubLayer3d, self.hubRadZero_],
[shroudLayer3d, self.shroudRadZero_]
]
return layerList
# returns a multiple bounded volume of the unstructured region
[docs]
def getUnstructuredRegion(self, nSlices: int) -> Tuple[analyticGeometry, List[analyticGeometry]]:
"""Create and return the unstructured region together with its
bounding surfaces.
This method:
- Creates the boundary surfaces of the unstructured region that
connect to the layer volumes.
- Creates the periodic boundary surfaces of the unstructured region
as multiple bounded surfaces.
- Creates the unstructured region as a multiple bounded volume.
- Returns the multiple bounded volume together with its boundary
surfaces.
Parameters
----------
nSlices : int
Number of rotationally periodic slices.
Returns
-------
multBoundedVol : multipleBoundedVolume
Unstructured region located between the hub and shroud layers.
boundSurf : vectorHandlingAnalyticGeometry
Vector handler containing the boundary surfaces of the
multiple bounded volume.
The unstructured region is created as an object of the `dtOO` class
``multipleBoundedVolume``. The multiple bounded volume is defined by
its boundary surfaces.
**Create Boundary Surfaces through the Rotation of Curves**
The inlet and outlet surfaces, together with the surfaces connecting
the multiple bounded volume to the layer volumes, are created from the
curves stored in the vector handler ``unstructVH_``.
The surfaces are generated using the `dtOO` class
``rectangularTrimmedSurface_curveRotateConstructOCC``. This class
creates surfaces by rotating curves around the rotation vector
``rotVector_`` over the angle ``rotAngle`` defined as:
.. math::
{360^\circ}/{n_{Slices}}
Conditional statements are used to assign labels to the generated
surfaces. Due to the ordering of curves in ``unstructVH_``, the first
and last curves define the inlet and outlet surfaces of the
unstructured region. These surfaces receive the labels
``interface_unstruct`` and ``outlet_unstruct``.
The remaining surfaces connect the unstructured region to the layer
regions and are labeled as ``para`` followed by their position in the
vector handler.
Using the method ``degenerated`` of the `dtOO` class
``analyticSurface``, a check is performed to determine whether any of
the rotated surfaces are degenerated. Degenerated surfaces occur when
the corresponding curve lies on a radius of zero.
Only non degenerated surfaces are appended to the
``vectorHandlingAnalyticGeometry`` object ``boundSurf``.
The following figure shows the boundary surfaces created from the
curves stored in ``unstructVH_``.
.. _mbvBounds0:
.. figure:: meridionalFigs/boundingSurfs.png
:width: 50%
:align: center
Boundary surfaces of the multiple bounded volume with inlet (red),
outlet (orange), and layer connection surfaces (blue). The periodic
multiple bounded surfaces are not shown.
**Create Periodic Faces as Multiple Boundes Surfaces**
The periodic surfaces of the unstructured region slice are created as
multiple bounded surfaces. These surfaces are generated using the
`dtOO` class ``multipleBoundedSurface``, which takes a set of boundary
curves together with a surrounding bounding box as input.
The bounding box is created from the minimum and maximum vertices
stored in ``speBb_``. Based on these vertices, a bounding box
``m2d`` is generated for the first multiple bounded surface, extending
0.1 units beyond ``speBb_`` in all directions.
The first periodic surface ``mbs1`` is created from ``m2d`` and the
boundary curves stored in ``unstructVH_`` using the class
``multipleBoundedSurface``. The surface is assigned the label
``periodicUnstruct_0`` and appended to ``boundSurf``.
To create the second periodic surface, a rotational transformation is
applied to both the bounding box and the boundary curves.
For this purpose, a `dtTransformer` object is initialized. The
transformation configuration is defined in the ``jsonPrimitive`` object
``cfg``, where the rotation vector, origin, and rotation angle are set
to ``rotVector_``, ``origin_``, and ``rotAngle``, respectively.
The resulting rotation object is stored in ``rot``.
By applying ``rot`` to ``m2d``, the rotated bounding box ``m2d_rot``
is created. By iterating over ``unstructVH_`` and applying the same
rotational transformation, the rotated boundary curves are generated and
stored in ``unstructVH_rot``.
The second multiple bounded surface is created analogously to the
first one using ``m2d_rot`` and ``unstructVH_rot``. This surface is
assigned the label ``periodicUnstruct_1`` and appended to
``boundSurf``.
**Create Unstructured Region as Multiple Bounded Volume**
The unstructured region itself is finally created using the class
``multipleBoundedVolume``. Its bounding volume is defined as an
``infinityMap3dTo3d`` object, while the vector handler ``boundSurf``
is provided as the collection of boundary surfaces.
**Returns**
The method returns both the multiple bounded volume and the vector
handler ``boundSurf`` containing all generated boundary surfaces.
"""
logging.info("Request Volume of unstructured Region: %i Slices" % nSlices)
# rotation angle defined by the number of slices
rotAngle = 2*numpy.pi/nSlices
vv = vectorDtPoint3()
boundSurf = vectorHandlingAnalyticGeometry()
# iterating over the boundary curves of the unstructured region
# extracts the start and end points of all curves for a bounding box
# creates surfaces of the boundary curves by rotationg them
ib = 0
for bound in self.unstructVH_:
# rotating the boundary curves, pushing them in boundSurf
bs = analyticSurface(
rectangularTrimmedSurface_curveRotateConstructOCC(
analyticCurve.MustDownCast(bound).ptrDtCurve(),
self.origin_,
self.rotVector_,
rotAngle
).result()
)
# setting labels for the interface and the outlet surface
if ib == 0:
bs.setLabel("interface_unstruct")
elif ib == (len(self.unstructVH_)-1):
bs.setLabel("outlet_unstruct")
else:
bs.setLabel("para"+str(ib))
if not bs.degenerated():
boundSurf.push_back(bs.clone())
else:
logging.info("Degenerated MBV boundary: %s " % bs.getLabel())
ib = ib + 1
# building a bounding box surface for the 2d unstructured region
# using the bounding box of the special hub and shroud curves
unstructBB = self.speBb_
BB_dist = unstructBB[0] - unstructBB[1]
# creating a rectangular surface which extends over the bounding box by 0.1
P0 = dtPoint3(unstructBB[0][0] - 0.1, unstructBB[0][1], unstructBB[0][2] - 0.1)
P1 = dtPoint3(P0[0] + BB_dist[0]+0.2, 0.00, P0[2])
P2 = dtPoint3(P1[0], 0.00, P1[2] + BB_dist[2]+0.2)
P3 = dtPoint3(P0[0], 0.00, P2[2])
m2d = analyticSurface(
bSplineSurface_bSplineCurveFillConstructOCC(
bSplineCurve_pointConstructOCC(P0, P1).result(),
bSplineCurve_pointConstructOCC(P1, P2 ).result(),
bSplineCurve_pointConstructOCC(P2, P3).result(),
bSplineCurve_pointConstructOCC(P3, P0).result()
).result()
)
# creating the first multi bounded surface and pushing it into the VH
mbs1 = multipleBoundedSurface(m2d, self.unstructVH_)
mbs1.setLabel("periodicUnstruct_0")
boundSurf.push_back( mbs1.clone())
# initializing a rotation dtTransformer
cfg = jsonPrimitive()
cfg.appendDtPoint3("_origin", self.origin_)
cfg.appendDtVector3("_rotVector", self.rotVector_)
cfg.appendReal("_angle", rotAngle)
# creating transformer object
rot = rotate(cfg)
# rotating the 2d bounding box
m2d_rot = (rot.applyAnalyticGeometry(m2d))
# rotating the boundary curves of the unstructured region
unstructVH_rot = vectorHandlingAnalyticGeometry()
for curve in self.unstructVH_:
unstructVH_rot.push_back(rot.applyAnalyticGeometry(curve))
# creating the rotated mbs and pushing it
mbs2 = multipleBoundedSurface(m2d_rot, unstructVH_rot)
mbs2.setLabel("periodicUnstruct_1")
boundSurf.push_back( mbs2.clone())
# generating the multiple bounded volume
multBoundedVol = multipleBoundedVolume(infinityMap3dTo3d(), boundSurf)
# adding the multi bounded surfaces to the vhs
#boundSurf.push_back(mbs1.clone())
#boundSurf.push_back(mbs2.clone())
# returning the multi bounded volume and its boundary surfaces
return multBoundedVol.clone(), boundSurf