Source code for nekmeshpy.quadmesh.ports

"""A cross-section together with the two facts a bare section cannot state about itself:
**which way it faces** and **where its axis is**."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass

import numpy as np

from .._typing import Point, Vec3
from .quadmesh import QuadMesh
from .query import plane_normal

#: How far ``normal`` may stray from unit length before ``Port`` refuses it.
NORMAL_TOL = 1e-12


[docs] @dataclass(frozen=True, eq=False) class Port: """An open end of a meshed component: its cross-section, the outward direction, the axis point, and the nominal radius.""" #: The cross-section itself. section: QuadMesh #: Unit vector pointing **out** of the component, along which a connector leaves. normal: Vec3 #: The axis point the section was built about -- deliberately *not* the centroid, #: which an O-grid's grading shifts slightly off it. center: Point #: Nominal radius, for checking that two ports being joined are the same size. radius: float def __post_init__(self) -> None: n = np.asarray(self.normal, dtype=float).reshape(-1) c = np.asarray(self.center, dtype=float).reshape(-1) if n.shape != (3,): raise ValueError("Port: normal must be a (3,) vector, got %s" % (np.shape(self.normal),)) if c.shape != (3,): raise ValueError("Port: center must be a (3,) point, got %s" % (np.shape(self.center),)) off = abs(float(n @ n) - 1.0) if off > NORMAL_TOL: raise ValueError( "Port: normal %s is not a unit vector (|n|^2 is %.3g off 1). It is used " "verbatim as a sweep direction, so a non-unit one rescales the " "connector rather than just naming a side." % (np.array2string(n), off)) if not float(self.radius) > 0.0: raise ValueError("Port: radius must be positive, got %g" % self.radius) object.__setattr__(self, "normal", n) object.__setattr__(self, "center", c) object.__setattr__(self, "radius", float(self.radius))
[docs] def reversed(self) -> Port: """The same section faced the other way -- for the end of a component that is about to be continued *into* rather than out of.""" return Port(self.section, -self.normal, self.center, self.radius)
[docs] def faces(self, other: Port) -> bool: """Whether the two ports point at each other, rather than the same way or apart. The check :func:`hexmesh.bridge <nekmeshpy.hexmesh.lift.bridge>` cannot make from geometry alone.""" return float(self.normal @ other.normal) < 0.0
def __repr__(self) -> str: return ("<Port r=%.4g at %s facing %s, %d quads>" % (self.radius, np.array2string(self.center, precision=4), np.array2string(self.normal, precision=4), self.section.quads.shape[0]))
[docs] def port(section: QuadMesh, *, outward: Vec3 | Sequence[float], center: Point | Sequence[float] | None = None, radius: float | None = None) -> Port: """A :class:`Port` from a section plus the side that faces out.""" n = plane_normal(section, hint=outward, check=False) c: Point = (np.asarray(section.points, dtype=float).mean(axis=0) if center is None else np.asarray(center, dtype=float).reshape(-1)) r = (float(np.linalg.norm(np.asarray(section.points, dtype=float) - c, axis=1).max()) if radius is None else float(radius)) return Port(section, n, c, r)
__all__ = ["NORMAL_TOL", "Port", "port"]