Source code for dtOOPythonApp.builder.analyticGeometry_piecewiseMeridionalRotContour

# ------------------------------------------------------------------------------
# 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,
    analyticCurve,
    bSplineCurve_pointConstructOCC,
    analyticSurface,
    dtPoint3,
    dtVector3,
    vectorDtPoint3,
    rotatingMap2dTo3d,
    partRotatingMap2dTo3d,
    trimmedCurve_uBounds,
    curveCurveDist,
    gslMinFloatAttr,
    vectorReal,
    vectorHandlingConstDtCurve,
    bSplineSurface_bSplineCurveFillConstructOCC,
    bSplineCurve_curveConnectConstructOCC
)

from typing import List, Tuple
from collections import defaultdict
import logging

[docs] class analyticGeometry_piecewiseMeridionalRotContour(dtBundleBuilder): """Create regular channels and special hub and shroud curves. This class: - Creates interface curves in the meridional contour. - Splits the hub and shroud curves at the intersection points with the interfaces. - Creates regular channels between the interfaces. - Creates lists of boundary curves for a layered flow channel downstream of the last interface. - Returns the volumes of the regular channels and the boundary curve lists. Attributes ---------- label_ : str Label. rotVector_ : dtVector3 Rotation vector. normalAxis_ : dtVector3 Vector normal to the 2D domain. interfaces_ : List[analyticGeometry] List of interface curves. hubSplits_ : List[List[Tuple[float, int]]] List of splits on hub curves. Each entry represents: - ``hubSplits_[i][n]``: n-th split on the i-th hub curve. - ``hubSplits_[i][n][0]``: Relative position where the split is applied. - ``hubSplits_[i][n][1]``: ID of the interface creating the split. shroudSplits_ : List[List[Tuple[float, int]]] List of splits on shroud curves. Each entry represents: - ``shroudSplits_[i][n]``: n-th split on the i-th shroud curve. - ``shroudSplits_[i][n][0]``: Relative position where the split is applied. - ``shroudSplits_[i][n][1]``: ID of the interface creating the split. hubCurves_ : List[analyticGeometry] Split hub curves. shroudCurves_ : List[analyticGeometry] Split shroud curves. inOutCurves_ : List[analyticGeometry] List of inlet and outlet curves of the complete domain. regChannels_ : List[analyticGeometry] List of channel surfaces. speHub_ : List[analyticGeometry] List of hub curves of the layered region. speShroud_ : List[analyticGeometry] List of shroud curves of the layered region. inOutLayerReg_ : List[analyticGeometry] List of inlet and outlet curves of the layered region. Returns ------- None Examples -------- >>> import dtOOPythonSWIG as dtOO Define list of hub curves: >>> hubCurves = [ ... dtOO.analyticCurve( ... dtOO.bSplineCurve_pointConstructOCC( ... dtOO.vectorDtPoint3() ... << dtOO.dtPoint3(+0.50, +0.00, 1.00) ... << dtOO.dtPoint3(+0.00, +0.00, 0.50), ... 1 ... ).result() ... ), ... dtOO.analyticCurve( ... dtOO.bSplineCurve_pointConstructOCC( ... dtOO.vectorDtPoint3() ... << dtOO.dtPoint3(+0.00, +0.00, 0.50) ... << dtOO.dtPoint3(+0.00, +0.00, 0.00), ... 1 ... ).result() ... ) ... ] Define list of shroud curves: >>> shroudCurves = [ ... dtOO.analyticCurve( ... dtOO.bSplineCurve_pointConstructOCC( ... dtOO.vectorDtPoint3() ... << dtOO.dtPoint3(+1.00, +0.00, 1.00) ... << dtOO.dtPoint3(+1.00, +0.00, 0.00), ... 1 ... ).result() ... ) ... ] Initialize builder: >>> builder = analyticGeometry_piecewiseMeridionalRotContour( ... label = "channel", ... hubCurves = hubCurves, ... shroudCurves = shroudCurves, ... interface_hub = [[0, 0.7]], ... interface_shroud = [[0, 0.3]], ... interface_curvature = [[0.5, 0.2, -1]], ... ) Build volume: >>> builder.build() Return the six sided domain and check type: >>> builder.getRegChannel(0, 12).virtualClassName() 'partRotatingMap2dTo3d' The main method of this class is the constructor. The inputs are the hub and shroud curves (``hubCurves`` and ``shroudCurves``), as well as the lists defining the interface parameters (``interface_hub``, ``interface_shroud`` and ``interface_curvature``). The normal axis ``normalAxis_`` of the flow channel's cross-section is returned by the method :meth:`calculateNormalAxis`. It is used to determine the direction of the interface curves. The interface curves are created with the method :meth:`createInterface`. The method returns the curves in the list ``interfaces_``. With the method :meth:`detectIntersect`, it is checked whether the interface curves intersect the hub or shroud curves; if so, an exception is raised. Special inlet and outlet curves are created as straight lines at the inlet and outlet of the meridional contour. The list ``inOutCurves_`` contains these curves. The hub and shroud curves are split at the intersection points corresponding to the start or end points of the interface curves using :meth:`createSplits`. The lists ``hubSplits_`` and ``shroudSplits_`` track which curves are split by which interface and at which percentual position along their span. The method :meth:`createSplits` returns the split curves in the lists ``hubCurves_`` and ``shroudCurves_``. The newly split curves are managed with ``cti`` (curve-to-interface) lists. These lists are initially returned by :meth:`createSplits` and track which interface curve is associated with which hub and shroud curve. The lists are modified by :meth:`propagate_interface_ids_next` to indicate which hub and shroud curves belong to which regular channel. The regular channels are created between two neighbouring interfaces in the flow direction of the channel contour. The first regular channel is created between the inlet and the first interface. The following regular channels are created between neighbouring interfaces. The surfaces of the regular channels are created from the interface curves and the hub and shroud curves extending between them. These curves are identified with the method :meth:`findChannelCurves`. The surfaces are created in the constructor of this class and stored in the list ``regChannels_``. The hub and shroud curves that are not part of the regular channels are stored as special curves in the lists ``speHub_`` and ``speShroud_``. These curves later form the basis of a layered flow channel. The inlet and outlet curves for this layered flow channel are stored in the list ``inOutLayerReg_`` and are formed by the last interface curve and the outlet of the flow domain. The :meth:`build` method is used for debugging the class. If the debug option is enabled in the class constructor, the created geometries are returned to `ParaView`, where they can be plotted with the names assigned in the :meth:`build` method and the label ``label_``. The method :meth:`getRegChannel` returns the volume of a specified regular channel. The volume is created by rotating the face of a regular channel around the rotational axis ``rotVector_`` with a specified angle. The getter method :meth:`getLayerRegionCurves` returns the lists ``speHub_``, ``speShroud_``, and ``inOutLayerReg_``. """
[docs] def __init__( self, label: str, hubCurves: List[analyticGeometry], shroudCurves: List[analyticGeometry], interface_hub: List[Tuple[int, float]] = [], # [curve, percent] interface_shroud: List[Tuple[int, float]] = [], interface_curvature: List[Tuple[float, float, int]] = [], # [curvature offset, percent, direction] rotVector: dtVector3 = dtVector3(0, 0, 1), ) -> None: """Constructor. This method: - Creates the interface curves. - Splits the hub and shroud curves at the interfaces. - Creates inlet and outlet lines. - Creates regular channel surfaces. - Creates lists with curves for layered region. Parameters ---------- label : str Label. hubCurves : List[analyticGeometry] List of hub curves. shroudCurves : List[analyticGeometry] List of shroud curves. interface_hub : List[Tuple[int, float]] Positions of the interfaces on the hub curves. Each entry represents: - ``interface_hub[i]``: Interface number - ``interface_hub[i][0]``: Curve number where the interface is located - ``interface_hub[i][1]``: Percentage along the curve (0 to 1) interface_shroud : List[Tuple[int, float]] Positions of the interfaces on the shroud curves. Each entry represents: - ``interface_shroud[i]``: Interface number - ``interface_shroud[i][0]``: Curve number where the interface is located - ``interface_shroud[i][1]``: Percentage along the curve (0 to 1) interface_curvature : List[Tuple[float, float, int]] Curvature of the interface curve from hub to shroud. Each entry represents: - ``interface_curvature[i]``: Interface number - ``interface_curvature[i][0]``: Curvature offset point [%] from hub to shroud - ``interface_curvature[i][1]``: Curvature as a percentage of the connection line length - ``interface_curvature[i][2]``: Curvature direction rotVector: dtVector3 Rotation vector. Returns ------- None The constructor of this class is used to partition the hub and shroud curves into regular channel segments and special hub and shroud curves. **Input Curves** The input lists named ``hubCurves`` and ``shroudCurves`` define the two-dimensional cross-section of the channel. The curves must be created such that their direction is consistent with the downstream direction of the flow machine. Furthermore, the ordering of the curves in the lists must also follow the downstream direction of the machine. Here downstream direction refers to the flow direction of a machine in turbine mode. The counter direction is referred to as the upstream direction. The following figure shows the hub and shroud curves: .. _hsCurves0: .. figure:: meridionalFigs/hsCurve_noInterface.png :width: 50% :align: center Hub and shroud curves of the meridional channel. Numbering corresponds to the indices in the ``hubCurves`` and ``shroudCurves`` lists. **Interface Curves** The method :meth:`calculateNormalAxis` is used to compute the normal axis of the cross-section, stored in ``normalAxis_``. By passing ``hubCurves``, ``shroudCurves``, ``interface_hub``, ``interface_shroud``, and ``interface_curvature``, together with the computed normal axis, to :meth:`createInterface`, the interface curves are created. The method returns a list of interface curves, which is stored in ``interfaces_``. The index of each interface in this list also serves as its interface ID. **Splitting of Hub and Shroud Curves** The hub and shroud curves are split at their intersection points with the interface curves. For this purpose, the lists ``hubSplits_`` and ``shroudSplits_`` are created, where each entry is initialized as an empty list. The number of entries corresponds to the number of hub and shroud curves. By iterating over the interfaces, each split position together with the corresponding interface ID is stored as a tuple in the respective curve entry at the location where the split occurs. The resulting lists ``hubSplits_`` and ``shroudSplits_`` are of type ``List[List[Tuple[float, int]]]``. The values in this nested list represent: - ``hubSplits_[i][n]``: n-th split on the i-th hub curve. - ``hubSplits_[i][n][0]``: Percentual position along the curve span where the split occurs. - ``hubSplits_[i][n][1]``: ID of the interface that creates the split. The splits for the hub and the shroud curves are applied using the method :meth:`createSplits`. The hub and shroud curves are split independently by calling the method once for the hub curves and once for the shroud curves. The method takes the split data lists ``hubSplits_`` or ``shroudSplits_`` and the corresponding curve lists ``hubCurves`` or ``shroudCurves`` as input. Additionally, a label is passed to the method. The method returns the split hub and shroud curve lists as well as ``cti`` (curve-to-interface) lists. If no split is performed on a curve, or if the split position lies at 0 or 100 percent of the curve span, no new split is created and the original curve is appended as a copy. The resulting curve lists are stored as ``hubCurves_`` and ``shroudCurves_``. The following figure shows the meridional contour with interface, inlet, and outlet curves. The curves ``hubCurves[1]`` and ``shroudCurves[2]`` (see :numref:`hsCurves0`) are split into two curves each by the interface curve ``interfaces_[1]``. The resulting curves are stored as ``hubCurves_[1]`` and ``hubCurves_[2]`` as well as ``shroudCurves_[2]`` and ``shroudCurves_[3]`` in the instantiated curve lists. .. _hsCurves01: .. figure:: meridionalFigs/hsCurves.png :width: 50% :align: center Meridional channel with interface (red) and inlet and outlet curves (orange). Numbering of the hub and shroud curves (black) corresponds to the indices in the ``hubCurves_`` and ``shroudCurves_`` lists. **Curve-to-Interface Lists** The ``cti`` lists (curve-to-interface) track which hub and shroud curves are upstream of which interface. The lists contain one entry per hub or shroud curve. If a curve endpoint lies on an interface curve, the corresponding entry stores the interface ID; otherwise the entry is ``None``. For the example shown in :numref:`hsCurves01`, the resulting ``cti`` lists are: - ``hub_cti = [0, 1, None, None, None]`` - ``shroud_cti = [0, None, 1, None, None]`` The ``cti`` lists are passed to the method :meth:`propagate_interface_ids_next`, where the entries are matched to the regular channels to which the curves belong. The returned values overwrite the original ``cti`` lists. The resulting ``shroud_cti`` list is: - ``shroud_cti = [0, 1, 1, None, None]`` **Inlet and Outlet Curves of the Meridional Contour** In ``inOutCurves_``, the inlet and outlet curves of the meridional contour are stored. The inlet curve is created as a straight line between the start points of the first curves in the hub and shroud curve lists. The outlet curve is created in the same way, using the end points of the last hub and shroud curves. **Regular Channels** The number of regular channels corresponds to the number of defined interfaces. The first regular channel is created between the inlet curve and the first interface. Subsequent regular channels are created between adjacent interfaces in flow direction. The regular channel surfaces are created by iterating over the number of interfaces. For each regular channel, the hub and shroud curves belonging to that channel are determined using the method :meth:`findChannelCurves`. This method is called once for the hub curves and once for the shroud curves. The inputs are the instantiated curve lists ``hubCurves_`` or ``shroudCurves_``, the ``cti`` lists ``hub_cti`` or ``shroud_cti``, and the ID of the regular channel, as well as a label. The curves belonging to a channel are identified by matching the channel ID against the ``cti`` list entries and are combined into a single curve. The upstream interface, or in the case of the first regular channel the inlet of the meridional channel, forms the inlet boundary of each regular channel. The downstream interface forms the outlet boundary. The surfaces are constructed using a ``bSplineSurface_bSplineCurveFillConstructOCC`` object from the inlet, outlet, and the combined hub and shroud curves of the regular channel. To ensure consistent orientation of the resulting surfaces and volumes, the curves are passed to the construct object in the following order: 1. combined hub curve 2. inlet of the regular channel 3. combined shroud curve 4. outlet of the regular channel This results in a regular channel volume whose uvw-directions after rotation are: - u : circumferential direction - v : meridional direction - w : hub-to-shroud direction The regular channel surfaces are stored in the list ``regChannels_``. The following figure shows the surfaces of the regular channels. .. _regChannels: .. figure:: meridionalFigs/regChannels.png :width: 50% :align: center Regular channels (yellow, green) between the interfaces. Special hub and shroud curves are not part of the regular channels. **Layer Region Curves** The curves downstream of the last interface are not part of the regular channels. Their value in the propagated ``cti`` list is ``None``. By iterating over the ``cti`` list and checking for ``None`` values, the special hub and shroud curves are identified and stored in the lists ``speHub_`` and ``speShroud_``. These special curves are used for the layered flow channel. The inlet of the layered flow channel is formed by the last interface curve, and the outlet is formed by the outlet of the meridional channel. These curves are stored in the list ``inOutLayerReg_``. The following figure shows the special curves and the inlet and outlet of the layered flow channel contour. .. _speCurvesClass: .. figure:: meridionalFigs/speCurvesClass.png :width: 55% :align: center Special hub and shroud curves with numbering according to their positions in the ``speHub_`` and ``speShroud_`` lists. The inlet and outlet curves of the layered flow channel are stored in ``inOutLayerReg_``. """ logging.info("Initializing %s ..." % (label)) super(analyticGeometry_piecewiseMeridionalRotContour, self).__init__() self.label_ = label self.rotVector_ = dtLinearAlgebra.normalize(rotVector) # # Extract a bounding box from all start and end points of hub and # shroud curves; necessary for calculating the normal direction # self.normalAxis_, center, bb = self.calculateNormalAxis( hubCurves + shroudCurves ) logging.info("### Creating Interfaces") # # Generate interface curves # self.interfaces_ = self.createInterface(hubCurves, shroudCurves, interface_hub, interface_shroud, interface_curvature, self.normalAxis_) logging.info("### Preparing Splits") # # makes split lists with number of empty lists #equal to the number of curves in hub and shroud # self.hubSplits_ = [[] for _ in range(len(hubCurves))] self.shroudSplits_ = [[] for _ in range(len(shroudCurves))] # # The hub and shroud points of the interfaces are added to the lists of splits # they get the ID of the interface i as a descriptor # trough this the correct interfaces can be tracked later # the .sort function sorts the splits by their percentage along the curve # Format: # self.hubSplits_ = List[List[Tuple[percentage, ID]]] # for i in range(len(interface_hub)): self.hubSplits_[interface_hub[i][0]].append((interface_hub[i][1],i)) self.hubSplits_[i].sort() self.shroudSplits_[interface_shroud[i][0]].append((interface_shroud[i][1],i)) self.shroudSplits_[i].sort() logging.info("### Creating Hub Splits") # # Create splits of hub and shroud curves # hub_cti and shroud_cti (curve_to_interface) contains a datastructure list # Here is mapped which of the newly spilt curves are located upstream of an interface # a curve located upstream of an interface is marked with the interface ID the others with None # # the cti lists will be updated to map all the curves to the regular channels # every cut curve will be appointed the ID of its respective reg channel defined by its interface # None means that the curve is part of a layer channel # self.hubCurves_, hub_cti = self.createSplits(self.hubSplits_, hubCurves, "Hub") hub_cti = self.propagate_interface_ids_next(hub_cti, 'Hub') logging.info("### Creating Shroud Splits") self.shroudCurves_, shroud_cti = self.createSplits(self.shroudSplits_, shroudCurves, "Shroud") shroud_cti = self.propagate_interface_ids_next(shroud_cti, 'Shroud') # # Add inlet and outlet curves # self.inOutCurves_ = [] # The inlet curve is needed as a boundary for the first regular channel self.inOutCurves_.append( analyticCurve( bSplineCurve_pointConstructOCC( self.hubCurves_[0].getPointPercent(0), self.shroudCurves_[0].getPointPercent(0), ).result() ) ) # The outlet curve will be needed for layer generation self.inOutCurves_.append( analyticCurve( bSplineCurve_pointConstructOCC( self.hubCurves_[-1].getPointPercent(1), self.shroudCurves_[-1].getPointPercent(1), ).result() ) ) # Returning if no interfaces are defined if not self.interfaces_: logging.error("NO INTERFACES DEFINED") return logging.info("### Creating Regular Channels") # # Generating regular channels from the interfaces # here all the channels are defined trough the hub and shroud curves and the interface # the last interface builds the last regular channel # the regChannels are generated by creating a surface from 4 curves # these curves are the upsteam and downstream boundaries # and the combined hub and shroud curves of the channel # self.regChannels_ = [] # # interating through the interfaces # (interface_hub and ..._shroud have to be the same size) # for ii in range(len(interface_hub)): logging.info("# Regular Channel[%i]" % ii) vhc = vectorHandlingConstDtCurve() # # adding the boundary curves in the succession: # hub -> inlet -> shroud -> outlet # this succession is important so the coordinate system will be as follows: # u - circumferential # v -- meridional # w --- hub to shroud # # finding the hub curves which belong to the interface regCurve = self.findChannelCurves(self.hubCurves_, hub_cti, ii, 'Hub') vhc.push_back(analyticCurve.MustDownCast(regCurve[0]).ptrDtCurve()) # setting the inlet boundary for the regular channel if ii == 0: # the first regChannel gets the inlet as a boundary curve vhc.push_back(analyticCurve.MustDownCast(self.inOutCurves_[0]).ptrDtCurve()) else: # the other regChannels get the previous interface as boundary vhc.push_back(analyticCurve.MustDownCast(self.interfaces_[ii-1]).ptrDtCurve()) # finding the shroud curves which belong to the interface regCurve = self.findChannelCurves(self.shroudCurves_, shroud_cti, ii, 'Shroud') vhc.push_back(analyticCurve.MustDownCast(regCurve[0]).ptrDtCurve()) # the outlet boundary is always the current interface vhc.push_back(analyticCurve.MustDownCast(self.interfaces_[ii]).ptrDtCurve()) # # generates the regular Channel by filling the four curves in vhc # self.regChannels_.append( analyticSurface( bSplineSurface_bSplineCurveFillConstructOCC(vhc).result() ) ) del vhc logging.info("%d regular channels created." % len(self.regChannels_)) logging.info("### Preparing Layer Curves") # # checking in the cti which curves will get a layer and appending them to speHub, # self.speHub_ = [] for i in range(len(hub_cti)): # it the cti for the curve is none a layer will be created if hub_cti[i] == None: self.speHub_.append(self.hubCurves_[i]) self.speShroud_ = [] for i in range(len(shroud_cti)): if shroud_cti[i] == None: self.speShroud_.append(self.shroudCurves_[i]) # # inlet and outlet curves of the layer region # self.inOutLayerReg_ = [self.interfaces_[-1], self.inOutCurves_[-1]]
# # creates an interface curve between two specified points on the hub and the shroud curve #
[docs] def createInterface(self, hubCurves, shroudCurves, interface_hub, interface_shroud, curve, normalAxis): """Create interface curve between hub and shroud. This method: - Creates a linear mean line between the hub and shroud point of the interface. - Creates the interface curve based on the mean line. - Checks if the interface curve intersects with any hub or shroud curves. Parameters ---------- hubCurves : List[analyticGeometry] List of hub curves. shroudCurves : List[analyticGeometry] List of shroud curves. interface_hub : List[Tuple[int, float]] Positions of the interfaces on the hub curves. Each entry represents: - ``interface_hub[i]``: Interface number. - ``interface_hub[i][0]``: Curve number where the interface is located. - ``interface_hub[i][1]``: Percentage along the curve (0 to 1). interface_shroud : List[Tuple[int, float]] Positions of the interfaces on the shroud curves. Each entry represents: - ``interface_shroud[i]``: Interface number. - ``interface_shroud[i][0]``: Curve number where the interface is located. - ``interface_shroud[i][1]``: Percentage along the curve (0 to 1). curve : List[Tuple[float, float, int]] Curvature of the interface curve from hub to shroud. Each entry represents: - ``curve[i]``: Interface number. - ``curve[i][0]``: Curvature offset point [%] from hub to shroud. - ``curve[i][1]``: Curvature as a percentage of the connection line length. - ``curve[i][2]``: Curvature direction. normalAxis: dtVector3 Normal axis Returns ------- interfaces: List[analyticGeometry] List of interface curves. The interfaces are created between points on the hub and shroud curves (``pointHub`` and ``pointShroud``), which are specified using the input lists ``interface_hub`` and ``interface_shroud``. The lists are unpacked as follows: - ``interface_hub[i]``: Start point of the i-th interface curve on the hub. - ``interface_shroud[i]``: End point of the i-th interface curve on the shroud. The lower-level list entries define the hub and shroud curves and the procentual positions along those curves at which the interface start and end points are located: - ``interface_hub[i][0]``: Index of the hub curve on which the start point lies. - ``interface_hub[i][1]``: Percentage along the hub curve where the start point is located. By iterating over the interface lists, the interface curves are constructed. Between the hub and shroud points, a straight line ``MP_linear`` is created, spanning from the hub point to the shroud point. Based on this line and the input list ``curve``, the curvature of the interface is defined. The list ``curve`` is structured as follows. The highest level corresponds to the interface index: - ``curve[i]``: Parameters of the i-th interface. The lower level defines the curvature properties of the interface: - ``curve[i][0]``: Control point offset as a percentage of the length of ``MP_linear``. - ``curve[i][1]``: Control point base position as a percentage along ``MP_linear``. - ``curve[i][2]``: Direction of the control point offset. The following figure illustrates the creation of the interface curves, with emphasis on the curvature definition of the second interface curve. .. _interfaceCalc0: .. figure:: meridionalFigs/interfaceCalcMethod.png :width: 80% :align: center Creation of the interface curves (red) between the hub and shroud curves (black), using the linear mean-plane curve ``MP_linear`` (green). :numref:`map-fig-var_interface0` gives the mapping between the mathematical symbols in :numref:`interfaceCalc0` and the naming of the variables in this method. .. _map-fig-var_interface0: .. csv-table:: Mapping between mathematical symbols and variable names. :header: "Symbol", "Label" :widths: 10, 20 :escape: # :align: center :math:`a`, ``curve[i][0]`` :math:`b`, ``curve[i][1]`` :math:`c`, ``curve[i][2]`` ":math:`MP,lin`", ``MP_linear`` The curvature is defined using a single control point ``pointCurve`` (:math:`\\mathbf{P_{C}}`). This control point is computed by applying an offset from a point (:math:`\\mathbf{P_{MP}}`) located along the span of ``MP_linear``. The position along the span is given as a percentage in ``curve[i][1]`` (:math:`b`). The offset is applied in the normal direction of ``MP_linear`` (:math:`\\mathbf{v}`) within the two-dimensional plane of the meridional contour. This direction is computed as the normalized cross product of the tangential direction of ``MP_linear`` (:math:`\\mathbf{t}`) and the normal axis of the meridional contour ``normalAxis`` (:math:`\\mathbf{n_{global}}`): .. math:: \\mathbf{v} = \\frac{\\mathbf{t} \\times \\mathbf{n_{global}}}{\\|\\mathbf{t} \\times \\mathbf{n_{global}}\\|} The offset magnitude is defined as a percentage of the length of ``MP_linear`` (:math:`l_{MP,lin}`), specified by ``curve[i][0]`` (:math:`a`). The direction of curvature is controlled by ``curve[i][2]`` (:math:`c`), which takes the values ``1`` or ``-1``. The control point ``pointCurve`` is then defined as: .. math:: \\mathbf{P_{C}} = \\mathbf{P_{MP}} + \\mathbf{v} \\cdot l_{MP,lin} \\cdot a \\cdot c The interface curve is created from the start and end points on the hub and shroud (``pointHub`` and ``pointShroud``) together with the control point ``pointCurve``. It is stored in the list ``interfaces``, along with the other interface curves created within this loop. The method :meth:`detectIntersect` is used to detect possible intersections between the interface curves and the hub and shroud curves. The inputs to this method are the list of interface curves and a list of curves against which intersections are checked. The returned lists ``intersects_hub`` and ``intersects_shroud`` contain information about which interface curves intersect which hub or shroud curves. If no intersections are detected, the lists are empty. If intersections are detected, the corresponding intersecting curves are reported via logging statements. The list of interface curves is returned by the method. """ # # check that the interface arreays have the same amount of entries # if (len(interface_hub) != len(interface_shroud)) or (len(interface_hub) != len(curve)): raise ValueError("interface arrays need the same no. of entries.") interfaces = [] # # iterate through all the interfaces that are created # for ii in range(len(interface_hub)): # # find the points at the specified percentage # of the specified hub and shroud curves # pointHub = analyticCurve.MustDownCast( hubCurves[interface_hub[ii][0]]).getPointPercent( interface_hub[ii][1]) pointShroud = analyticCurve.MustDownCast( shroudCurves[interface_shroud[ii][0]]).getPointPercent( interface_shroud[ii][1]) logging.debug("Interface [%d] at hub[%d] [%f]" % ( ii, interface_hub[ii][0], interface_hub[ii][1]) ) logging.debug("Interface [%d] at shroud[%d] [%f]" % ( ii, interface_shroud[ii][0], interface_shroud[ii][1]) ) # # create a straigth line between the two points # MP_linear = analyticCurve( bSplineCurve_pointConstructOCC( vectorDtPoint3() << pointHub << pointShroud, 1 ).result() ) direction = curve[ii][2] # # calculate the curvature point by making an offset in normal # direction of the specified percentage from MP_linear # # the normal direction is calculated from the crossproduct # of the direction of MP_linear and the normalAxis # # the offset is a specified percentage of the length of MP_linear # times +-1 for direction # pointCurve = (analyticCurve.MustDownCast(MP_linear).getPointPercent(curve[ii][1]) + dtLinearAlgebra.normalize( dtLinearAlgebra.crossProduct( MP_linear.firstDerUPercent(curve[ii][1]), normalAxis ) ) * MP_linear.length() * curve[ii][0] * direction) # # create the interface as a bSpline curve from the three points # interface = analyticCurve( bSplineCurve_pointConstructOCC( vectorDtPoint3() << pointHub << pointCurve << pointShroud, 2 ).result() ) logging.debug("Interface [%d] : curve offset[%f], percent[%f], direction[%d]" % ( ii, curve[ii][0], curve[ii][1], curve[ii][2]) ) interfaces.append(interface.clone()) logging.debug("Copy interface [%d]" % (ii)) # # check if the generated interfaces are intersecting with any hub or shroud curves # intersects_hub = self.detectIntersect(interfaces, hubCurves) intersects_shroud = self.detectIntersect(interfaces, shroudCurves) # # logging or warning if intersections occur # if (not intersects_hub and not intersects_shroud): logging.debug("Interfaces created without any illegal intersections") else: for i_hub in range(len(intersects_hub)): logging.warning( "Illegal intersect between interface no. %i and hub curve no. %i", intersects_hub[i_hub][0], intersects_hub[i_hub][1] ) for i_shroud in range(len(intersects_shroud)): logging.warning( "Illegal intersect between interface no. %i and shroud curve no. %i", intersects_shroud[i_shroud][0], intersects_shroud[i_shroud][1] ) return interfaces
# # searches for intersections between two lists of curves #
[docs] def detectIntersect(self, interfaces, curves): """Detect intersections between curves from two lists. This method: - Iterates over the interface list. - Cuts each interface curve at 5% and 95% of its span. - Iterates over the second curve list. - Checks for intersections between the cut interface and each curve. - Creates and returns a list managing intersecting curve pairs. Parameters ---------- interfaces : List[analyticGeometry] List of interface curves. curves : List[analyticGeometry] List of curves to be checked for intersections. Returns ------- intersectList : List[Tuple[int, int, bool]] List of detected intersections. Each entry represents: - ``intersectList[i]``: curve pair entry - ``intersectList[i][0]``: ID of interface curve - ``intersectList[i][1]``: ID of curve from second list - ``intersectList[i][2]``: True if intersection occurs By comparing curves in the two input lists, possible intersections are detected and stored in ``intersectList``. The method iterates over all interface curves in ``interfaces``. Each interface is trimmed at 5% and 95% of its parameter range to avoid detecting the intersections at the interface endpoints where they connect to the hub or shroud curves. The resulting trimmed curve is stored as ``cut_interface``. For each ``cut_interface``, all curves in the input list ``curves`` are checked for intersections. This is performed using the object ``gmf`` of type ``dtOO.gslMinFloatAttr``. The call ``gmf.perform()`` returns ``True`` if an intersection is detected, which is stored in the variable ``interbool``. If ``interbool`` is ``True``, the IDs of the interface and curve, together with the boolean flag, are appended to the intersection list. The resulting ``intersectList`` is unpacked as follows: - ``intersectList[i]``: detected intersection between two curves - ``intersectList[i][0]``: ID of interface curve - ``intersectList[i][1]``: ID of curve in second list - ``intersectList[i][2]``: ``True`` if intersection is detected The intersection list is returned by the method. """ intersect_list = [] # # iterate over first curve list # for ii in range(len(interfaces)): # # trimm the interface curve at 5 and 95 percent. # this is done so the intersect ath the start and end points are not detected. # (this would only occur at very high angles at the start and end points) interface = analyticCurve.MustDownCast(interfaces[ii]).ptrDtCurve() cut_interface = analyticCurve( trimmedCurve_uBounds( interface, interface.u_uPercent(0.05), interface.u_uPercent(0.95), ).result() ) # # iterate over the list of second curves # for ic in range(len(curves)): # # create a minimization object to search for the intersect point between the two curves # implemented from the dtOO documentation: Test\Transfinite Mesh # gmf = gslMinFloatAttr( curveCurveDist(cut_interface.ptrConstDtCurve(), curves[ic].ptrConstDtCurve()), vectorReal([0.5, 0.5,]), vectorReal([0.001, 0.001,]), 1.e-8, 100 ) # returns True if a intersect is detected interbool = gmf.perform() del gmf # cache the curves which intersect for return if interbool == True: intersect_list.append([ii, ic, interbool]) # # return the intersect list # return intersect_list
[docs] def propagate_interface_ids_next(self, curve_to_interface,lab): """Map curves to IDs of regular channels. This method: - Takes the ``cti`` (`curve-to-interface`) lists created in :meth:`createSplits` - Propagates interface IDs to upstream curve segments - Returns the propagated lists Parameters ---------- curve_to_interface : List Curve-to-interface list containing interface IDs or ``None``. lab : str Label. Returns ------- result : List Propagated curve-to-regular-channel list. This method modifies the ``cti`` (`curve_to_interface`) list. The list is initially returned by :meth:`createSplits`. In its original form, it only marks curves that are directly upstream of an interface. In this method, the interface IDs are propagated upstream so that all curve segments belonging to the same regular channel carry the same interface ID. This allows the curves to be directly associated with their corresponding regular channels. As an example, :meth:`createSplits` may return a ``cti`` list such as: - ``shroud_cti = [0, None, 1, None, None, None, None]`` After propagation, this method modifies it to: - ``shroud_cti = [0, 1, 1, None, None, None, None]`` In this case, entries 1 and 2 can now be identified as belonging to regular channel 1. The method takes the ``curve_to_interface`` list and propagates the integer interface IDs to all upstream curves that currently contain ``None`` values. All interface IDs present in ``curve_to_interface`` are first collected in the list ``iface_indices``. If no interface IDs are found, an empty ``result`` list is returned. The index of the first interface is stored in ``first_idx``. Subsequent interface IDs are propagated forward to all curve entries between consecutive interface indices. This process is controlled using the variables ``start`` and ``end`` within a loop. The resulting list contains, for each curve, either the corresponding regular channel ID or ``None``. The updated list is returned at the end of the method. """ logging.info("propagating %s_cti" % (lab)) result = curve_to_interface.copy() n = len(result) # find indices of segments with interface IDs iface_indices = [i for i, x in enumerate(result) if x is not None] if not iface_indices: return result # nothing to propagate # assign first interface to segments before first one first_idx = iface_indices[0] for i in range(0, first_idx): result[i] = result[first_idx] # assign next interface to segments between interface IDs for idx in range(len(iface_indices) - 1): start = iface_indices[idx] end = iface_indices[idx + 1] for i in range(start + 1, end): result[i] = result[end] groups = defaultdict(list) logging.info("Interface and Layers on %s curves" % lab) logging.info("Interface/Layer : Curves") for i, val in enumerate(result): groups[val].append(i) for val, indices in groups.items(): logging.info(f"{val} : {' '.join(map(str, indices))}") # segments after the last interface remain None return result
[docs] def findChannelCurves(self, curves, cti, ii, lab ): """Find and combine curves belonging to a regular channel. This method: - Iterates over a list of curves. - Identifies curves that belong to the regular channel. - Combines the curves. - Returns the combined curve in a list. Parameters ---------- curves : List[analyticGeometry] List of curves. cti : List Curve-to-interface list mapping split curves to regular channels. Entries can be: - int: ID of the regular channel the curve belongs to - None: curve belongs to a layered region ii : int ID of the regular channel. lab : str Label. Returns ------- curve_list : List[analyticGeometry] List containing the combined curve. The ID of the regular channel is passed to the method via the input ``ii``. The list ``curves`` contains the curves that form either the hub or the shroud of the channel surface. The list ``cti`` contains one entry for each curve in ``curves``. The entries are the IDs of the regular channels the corresponding curves belong to (or ``None`` if the curve is not part of a regular channel). By iterating over ``curves`` and checking whether the corresponding entry in ``cti`` matches ``ii``, the relevant curves are identified. The first curve belonging to the regular channel is assigned to ``regCurve``. In subsequent iterations, all additional matching curves are merged into ``regCurve`` using the `dtOO` class `bSplineCurve_curveConnectConstructOCC`. The combined curve of the regular channel is returned in ``curve_list``. """ curve_list = [] first = 0 # # iterate over the curves # for jj in range(len(curves)): # # Check if the regular channel ID cti[jj] of the current curve # matches the current regular channel ii. # If it matches the curve will be combined with the other matching curves. # if cti[jj] == ii: if first == 0: # the first curve is allocated to regCurve regCurve = curves[jj] first = 1 else: # the subsequent curves are be combined with regCurve regCurve = analyticCurve( bSplineCurve_curveConnectConstructOCC( analyticCurve.MustDownCast( regCurve ).ptrDtCurve(), analyticCurve.MustDownCast( curves[jj] ).ptrDtCurve() ).result() ) # # The combined channel curve is returned in a list with a single entrie. # curve_list.append(regCurve.clone()) logging.info("Combined regular channel curves of %s" % lab) return curve_list
# creates the splits of the channel curve # splits are made by interfaces with interface_hub and interface_shroud # this function splits the specified curves at the specified percentages # it is possible to make multiple splits on one curve
[docs] @staticmethod def createSplits(splits, inCurves, lab): """Split the curves at the defined positions. This method: - Iterates over split definitions. - Performs splits on the specified curves at given positions. - Creates a curve-to-interface list. Parameters ---------- splits : List[List[Tuple[float, int]]] List defining splits for each curve. Each entry represents: - ``splits[i][n]`` : n-th split on the i-th curve - ``splits[i][n][0]`` : parameter (percentage) where the split is applied - ``splits[i][n][1]`` : ID of the interface that defines the split inCurves : List[analyticGeometry] Input curves to be split. lab : str Label. Returns ------- outCurves : List[analyticGeometry] List of split curves. curve_to_interface : List List containing the interface ID for each curve endpoint. Entries can be: - int: ID of the interface located at the curve endpoint - None: no interface is located at the curve endpoint The curves that are split by this method are provided in the input list ``inCurves``. The nested list ``splits`` defines the split positions on these curves. The first-level index ``splits[i]`` corresponds to the i-th curve in ``inCurves``. The second-level index ``splits[i][n]`` corresponds to the n-th split applied to that curve. Each split is defined by a tuple containing: - the position along the curve (as a percentage of its parameter range), - the ID of the interface that defines the split. The full structure of ``splits`` is therefore ``List[List[Tuple[float, int]]]``, where ``splits[i][n][0]`` defines the split position and ``splits[i][n][1]`` defines the corresponding interface ID. The following figure shows the activity diagram of this method. .. _createSplits: .. figure:: meridionalFigs/createSplits.png :width: 110% :align: center Activity diagram of the :meth:`createSplits` method. At the start of the method, empty lists are created for the split curves ``outCurves`` and the curve-to-interface list ``curve_to_interface``. The latter is used to track which interface each curve in ``outCurves`` belongs to. The method generates the split curves by iterating over the first level of the list ``splits``. The curve processed in each iteration is referred to as ``curve``. If splits occur on this curve, the lists ``split_pos`` and ``interface_ids`` are created. These lists aggregate the values from the second level of ``splits`` in order to support multiple splits per curve. ``split_pos`` contains the percentual split positions along the curve span. The values ``0.0`` and ``1.0`` are prepended or appended respectively to ensure the full curve range is covered. ``interface_ids`` stores the IDs of the interfaces associated with each split. The curve splitting is performed by iterating over ``split_pos``. During this loop, three cases can occur: 1. **Boundary split at 0 or 100 percent** This case applies when a split position is exactly ``0.0`` or ``1.0``. In this situation, ``split_pos`` contains two adjacent identical values (i.e. ``split_pos[n] == split_pos[n+1]``). The corresponding interface ID is appended to ``curve_to_interface``, but no geometric split is performed. 2. **Single split at full curve length** This case occurs when the only effective split is at ``1.0``, resulting in neighbouring entries ``0.0`` and ``1.0`` in ``split_pos``. The full curve is appended to ``outCurves`` without modification. The corresponding entry in ``curve_to_interface`` is handled in the subsequent iteration, where case 1 applies. 3. **Regular split case** If neither Case 1 nor Case 2 applies, a standard split is performed. The curve is split using the `dtOO` class `trimmedCurve_uBounds`, creating a new curve segment between two neighbouring values in ``split_pos``. The interface ID is appended to ``curve_to_interface`` if the resulting segment lies upstream of the interface; otherwise, ``None`` is appended for downstream segments. If no split is defined for a curve, it is appended unchanged to ``outCurves``, and ``None`` is appended to ``curve_to_interface``. Finally, the lists ``outCurves`` and ``curve_to_interface`` are returned. """ # list with the split curves outCurves = [] # mapping of all the curves and their corresponding interface IDs curve_to_interface = [] # # iterating over the curves # (the length of splits has to be the same as the number or curves) # for cc in range(len(splits)): curve = analyticCurve.MustDownCast(inCurves[cc]).ptrDtCurve() # # if a split is made on this curve (splits[cc] contains an entrie) # if splits[cc]: # # makes a list of all the occuring splits in this curve # appends 0 and 1 at the start and end for the segments # split_pos = [0.0] + [s[0] for s in splits[cc]] + [1.0] # # makes a list of the IDs the new curves will have # s[1] will contain the interface number ot the interface or None # interface_ids = [s[1] for s in splits[cc]] # # iterating over the splits on this curve # for n in range(len(split_pos)-1): # # exception for when a interface is located on 0 or 100 percent of a curve # doesnt make a split just corrects the cti # if split_pos[n] == split_pos[n+1]: # sets the id of this domain iface_id = interface_ids[n] if n < len(interface_ids) else interface_ids[-1] curve_to_interface[-1] = iface_id # # exception for if the whole curve has a split or interface at 100 percent # doesnt make a split but appends outCurves and curve_to_interface # elif (split_pos[n] == 0 and split_pos[n+1] == 1): outCurves.append(inCurves[cc].clone()) curve_to_interface.append(None) # # else the splits are created and the interface IDs added # else: # trimms the curves at the split positions ac = analyticCurve( trimmedCurve_uBounds( curve, curve.u_uPercent(split_pos[n]), curve.u_uPercent(split_pos[n+1]), ).result() ) outCurves.append(ac.clone()) # Determines interface ownership # assigns the interface ID of the *upper bound* of the segment iface_id = interface_ids[n] if n < len(interface_ids) else None curve_to_interface.append(iface_id) logging.debug( "Split %s[%d] [%5f, %5f]" % (lab, cc, split_pos[n], split_pos[n+1]) ) # # if the curve is not split the whole curve will be copied # else: outCurves.append(inCurves[cc].clone()) logging.debug("Copy %s[%d]" % (lab, cc)) # adds none to curve to interface curve_to_interface.append(None) # as string identifier "segm" will be set logging.info("Finished splitting %s curves. %i curves created" % (lab, len(outCurves))) # returns the cut curves and the cti return outCurves, curve_to_interface
# # 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
# builder function # here is specified which debug geometries will be plotted
[docs] def build(self) -> None: """Plot the instantiated geometries in paraview if debug is enabeled. Parameters ---------- None Returns ------- None This method appends the geometries to the ``dtBundle`` objects for debugging. """ logging.info("Building %s ..." % (self.label_)) if self.debug(): try: ii = 0 for interface in self.interfaces_: self.appendAnalyticGeometry( interface.clone(), "debug_interface_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass try: ii = 0 for aCurve in self.shroudCurves_: self.appendAnalyticGeometry( aCurve.clone(), "debug_shroudCurve_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass try: ii = 0 for aCurve in self.hubCurves_: self.appendAnalyticGeometry( aCurve.clone(), "debug_hubCurve_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass try: ii = 0 for aCurve in self.speHub_: self.appendAnalyticGeometry( aCurve.clone(), "debug_speHub_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass try: ii = 0 for aCurve in self.speShroud_: self.appendAnalyticGeometry( aCurve.clone(), "debug_speShroud_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass try: ii = 0 for aCurve in self.inOutCurves_: self.appendAnalyticGeometry( aCurve.clone(), "debug_inOutCurve_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass try: ii = 0 # defines which channels will be rotatetd for the debug geometries for channel in ( self.regChannels_ ): #off = float(ii) * 0.10 # rotational offset of each channel off = 0.05 # 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_channel_" + str(ii) + "_" + self.label_, ) ii = ii + 1 except: pass return
# # returns the rotated segment of the channel number #
[docs] def getRegChannel(self, pos: int, nSlices: int ) -> analyticGeometry: """Return a regular channel. This method: - Returns a rotated segment of a regular channel surface. Parameters ---------- pos: int Channel Id. nSlices: int Number of periodic slices. Returns ------- : analyticGeometry Regular channel volume. This getter method is used to return a regular channel specified by the channel ID with ``pos``. The rotation angle can be set with ``nSlices`` which determines how many partitions the flow domain will have. If the number of slices is set to one, a whole 360 degree volume of the flow channel is returned. It is created with the `dtOO` class `rotatingMap2dT03d`. If a number greater than one is handed to the method, the class `partRotatingMap2dTo3d` is used, where the angle is calculated as :math:`360°/n_{Slices}`. The following figure shows the regular channel volumes resulting from the rotation of the regular channel faces. .. _regChannels3d: .. figure:: meridionalFigs/regChannels3d.png :width: 50% :align: center Regular channel volumes created by rotating the first (yellow) and second (green) regular channel faces. """ logging.info("Request regChannel[ %d ]" % pos) # whole rotational domain if nSlices == 1: return rotatingMap2dTo3d( self.rotVector_, self.regChannels_[pos], ).clone() # segment else: return partRotatingMap2dTo3d( self.rotVector_, self.regChannels_[pos], 0.00, (1/nSlices), ).clone()
[docs] def getLayerRegionCurves(self) -> Tuple[List[analyticGeometry]]: """Returns the bounding curves for the layered region. Parameters ---------- None Returns ------- speHub_: List[analyticGeometry] Hub curves of the layered region speShroud_: List[analyticGeometry] Shroud curves of the layered region inOutLayerReg_: List[analyticGeometry] inlet and outlet curves of the layered region Returns the bounding curves of the layered region shown in :numref:`speCurvesClass`. """ return self.speHub_, self.speShroud_, self.inOutLayerReg_