Source code for EasyFEA.geoms._circle

# Copyright (C) 2021-2025 Université Gustave Eiffel.
# This file is part of the EasyFEA project.
# EasyFEA is distributed under the terms of the GNU General Public License v3, see LICENSE.txt and CREDITS.md for more information.

"""Module containing the Circle and CircleArc classes."""

import numpy as np
from typing import Union, Optional

from ._utils import (
    Point,
    AsCoords,
    AsPoint,
    Normalize,
    Jacobian_Matrix,
    Angle_Between,
    Circle_Triangle,
    Circle_Coords,
)
from ._geom import _Geom
from ..utilities import _params, _types


[docs] class Circle(_Geom): """Circle class.""" __NCircle = 0 def __init__( self, center: Point.PointALike, diam: float, meshSize: float = 0.0, isHollow: bool = True, isOpen: bool = False, n: _types.Coords = (0, 0, 1), ): """Creates a circle according to its center, diameter and the normal vector. Parameters ---------- center : Point | Coords center of circle diam : float diameter meshSize : float, optional mesh size that will be used to create the mesh >= 0, by default 0.0 isHollow : bool, optional circle is hollow/empty, by default True isOpen : bool, optional circle can be opened (openCrack), by default False n : Coords, optional normal direction to the circle, by default (0,0,1) """ _params._CheckIsPositive(diam) center = AsPoint(center) r = diam / 2 # creates points associated with the circle self.center = center self.pt1 = center + [r, 0, 0] self.pt2 = center + [0, r, 0] self.pt3 = center + [-r, 0, 0] self.pt4 = center + [0, -r, 0] Circle.__NCircle += 1 name = f"Circle{Circle.__NCircle}" _Geom.__init__( self, [self.center, self.pt1, self.pt2, self.pt3, self.pt4], meshSize, name, isHollow, isOpen, ) # rotate if necessary zAxis = np.array([0, 0, 1]) n = Normalize(AsCoords(n)) rotAxis = np.cross(n, zAxis) # theta = AngleBetween_a_b(zAxis, n) # then we rotate along i if np.linalg.norm(rotAxis) == 0: # n and zAxis are collinear i = Normalize((self.pt1 - center).coord) # i = p1 - center else: i = rotAxis mat = Jacobian_Matrix(i, n) coord = np.einsum("ij,nj->ni", mat, self.coord - center.coord) + center.coord for p, point in enumerate(self.points): point.coord = coord[p] @property def diam(self) -> float: """circle's diameter""" p1 = self.pt1.coord pC = self.center.coord return np.linalg.norm(p1 - pC).astype(float) * 2 @property def n(self) -> _types.Coords: """axis normal to the circle""" i = Normalize((self.pt1 - self.center).coord) j = Normalize((self.pt2 - self.center).coord) n: _types.Coords = Normalize(np.cross(i, j)) return n
[docs] def Get_coord_for_plot(self) -> tuple[_types.FloatArray, _types.FloatArray]: angle = np.linspace(0, np.pi * 2, 40) pC = self.center R = self.diam / 2 points = self.coord lines = np.zeros((angle.size, 3)) lines[:, 0] = np.cos(angle) * R lines[:, 1] = np.sin(angle) * R # construct jacobian matrix i = (self.pt1 - self.center).coord n = self.n mat = Jacobian_Matrix(i, n) # change base lines = np.einsum("ij,nj->ni", mat, lines) + pC.coord return lines, points[1:]
@property def length(self) -> float: """circle perimeter""" return np.pi * self.diam
[docs] def Get_Contour(self): """Creates the contour object associated with the circle""" center = self.center meshSize = self.meshSize isHollow = self.isHollow isOpen = self.isOpen # creates circle arcs associated with the circle circleArc1 = CircleArc( self.pt1, self.pt2, center=center, meshSize=meshSize, isOpen=isOpen ) circleArc2 = CircleArc( self.pt2, self.pt3, center=center, meshSize=meshSize, isOpen=isOpen ) circleArc3 = CircleArc( self.pt3, self.pt4, center=center, meshSize=meshSize, isOpen=isOpen ) circleArc4 = CircleArc( self.pt4, self.pt1, center=center, meshSize=meshSize, isOpen=isOpen ) from ._contour import Contour return Contour( [circleArc1, circleArc2, circleArc3, circleArc4], isHollow, isOpen )
[docs] class CircleArc(_Geom): """CircleArc class.""" __NCircleArc = 0 def __init__( self, pt1: Point.PointALike, pt2: Point.PointALike, center: Union[Point, None] = None, R: Optional[_types.Number] = None, P: Optional[_types.Coords] = None, meshSize: _types.Number = 0.0, n: _types.Coords = (0, 0, 1), isOpen: bool = False, coef: int = 1, ): """Creates a circular arc using several methods:\n - 1: with 2 points, a radius R and a normal vector.\n - 2: with 2 points and a center.\n - 3: with 2 points and a point P belonging to the circle.\n The methods are chosen in the following order 3 2 1.\n This means that if you enter P, the other methods will not be used. Parameters ---------- pt1 : Point | Coords starting point pt2: Point | Coords ending point R: _types.Number, optional radius of the arc circle, by default None center: Point, optional center of circular arc, by default None P: _types.Coords, optional a point belonging to the circle, by default None meshSize : _types.Number, optional size to be used for mesh construction, by default 0.0 n: Coords, optional normal vector to the arc circle, by default (0,0,1) isOpen : bool, optional arc can be opened, by default False coef: int, optional Change direction, by default 1 or -1 """ pt1 = AsPoint(pt1) pt2 = AsPoint(pt2) # check that pt1 and pt2 dont share the same coordinates assert not pt1.Check(pt2), "pt1 and pt2 are on the same coordinates" if center is not None: center = AsPoint(center) assert not pt1.Check(center), "pt1 and center are on the same coordinates" elif P is not None: center = Point(*Circle_Triangle(pt1, pt2, P)) elif R is not None: coord = np.array([pt1.coord, pt2.coord]) center = Point(*Circle_Coords(coord, R, n)) else: raise Exception("must give P, center or R") r1 = np.linalg.norm((pt1 - center).coord) r2 = np.linalg.norm((pt2 - center).coord) assert ( r1 - r2 ) ** 2 / r2**2 <= 1e-12, "The given center doesn't have the right coordinates. If the center coordinate is difficult to identify, you can give:\n - the radius R with the vector normal to the circle n\n - another point belonging to the circle." self.center = center """Point at the center of the arc.""" self.pt1 = pt1 """Starting point of the arc.""" self.pt2 = pt2 """Ending point of the arc.""" # Here we'll create an intermediate point, because in gmsh, circular arcs are limited to an pi angle. i1 = (pt1 - center).coord i2 = (pt2 - center).coord # construction of the passage matrix k = np.array([0, 0, 1]) if np.linalg.norm(np.cross(i1, i2)) <= 1e-12: vect = Normalize(i2 - i1) i = np.cross(k, vect) else: i = Normalize((i1 + i2) / 2) k = Normalize(np.cross(i1, i2)) j = np.cross(k, i) mat = np.array([i, j, k]).T # midpoint coordinates _params._CheckIsInIntervaloo(coef, -1, 1) pt3 = center.coord + mat @ [coef * r1, 0, 0] self.pt3 = Point(*pt3) """Midpoint of the circular arc.""" self.coef = coef CircleArc.__NCircleArc += 1 name = f"CircleArc{CircleArc.__NCircleArc}" _Geom.__init__( self, [pt1, center, self.pt3, pt2], meshSize, name, False, isOpen ) @property def n(self) -> _types.Coords: """axis normal to the circle arc""" i = Normalize((self.pt1 - self.center).coord) if np.any(np.isclose(self.angle, [0, np.pi])): j = Normalize((self.pt3 - self.center).coord) else: j = Normalize((self.pt2 - self.center).coord) n = Normalize(np.cross(i, j)) return n @property def angle(self): """circular arc angle [rad]""" i = (self.pt1 - self.center).coord j = (self.pt2 - self.center).coord return Angle_Between(i, j) @property def r(self): """circular arc radius""" return np.linalg.norm((self.pt1 - self.center).coord) @property def length(self) -> float: """circular arc length""" return np.abs(self.angle * self.r)
[docs] def Get_coord_for_plot(self) -> tuple[_types.FloatArray, _types.FloatArray]: points = self.coord pC = self.center r = self.r # plot arc circle in 2D space angles = np.linspace(0, np.abs(self.angle), 11) lines = np.zeros((angles.size, 3)) lines[:, 0] = np.cos(angles) * r lines[:, 1] = np.sin(angles) * r # get the jabobian matrix i = (self.pt1 - self.center).coord n = self.n mat = Jacobian_Matrix(i, n) # transform coordinates lines = np.einsum("ij,nj->ni", mat, lines) + pC.coord return lines, points[[0, -1]]