#
##
##  SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.4  (Thu Nov 16 18:07:39 CET 2023)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program 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
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
#
"""Some functions operating on 2D structures.
This is a plugin for pyFormex.
(C) 2002 Benedict Verhegghe
See the Section2D example for an example of its use.
"""
import numpy as np
from pyformex.plugins import sectionize
## initialization should be with a closed PolyLine, after checking it is plane
[docs]class PlaneSection():
    """A class describing a general 2D section.
    The 2D section is the area inside a closed curve in the (x,y) plane.
    The curve is decribed by a finite number of points and by straight
    segments connecting them.
    """
    def __init__(self, F):
        """Initialize a plane section.
        Initialization can be done either by a list of points or a set of line
        segments.
        By Points:
          Each point is connected to the following one, and (unless they are
          very close) the last one back to the first. Traversing the resulting
          path should rotate positively around the z axis to yield a positive
          surface.
        By Segments:
          It is the responsibilty of the user to ensure that the segments
          form a closed curve. If not, the calculated section data will be
          rather meaningless.
        """
        if F.nplex() == 1:
            self.F = sectionize.connectPoints(F, close=True)
        elif F.nplex() == 2:
            self.F = F
        else:
            raise ValueError("Expected a plex-1 or plex-2 Formex")
    def sectionChar(self):
        return sectionChar(self.F) 
[docs]def sectionChar(F):
    """Compute characteristics of plane sections.
    The plane sections are described by their circumference, consisting of a
    sequence of straight segments.
    The segment end point data are gathered in a plex-2 Formex.
    The segments should form a closed curve.
    The z-value of the coordinates does not have to be specified,
    and will be ignored if it is.
    The resulting path through the points should rotate positively around the
    z axis to yield a positive surface.
    The return value is a dict with the following characteristics:
    - `L`   : circumference,
    - `A`   : enclosed surface,
    - `Sx`  : first area moment around global x-axis
    - `Sy`  : first area moment around global y-axis
    - `Ixx` : second area moment around global x-axis
    - `Iyy` : second area moment around global y-axis
    - `Ixy` : product moment of area around global x,y-axes
    """
    if F.nplex() != 2:
        raise ValueError("Expected a plex-2 Formex!")
    #pf.debug("The circumference has %d segments" % F.nelems())
    x = F.x
    y = F.y
    x0 = x[:, 0]
    y0 = y[:, 0]
    x1 = x[:, 1]
    y1 = y[:, 1]
    a = (x0*y1 - x1*y0) / 2
    return {
        'L': np.sqrt((x1-x0)**2 + (y1-y0)**2).sum(),
        'A': a.sum(),
        'Sx': (a*(y0+y1)).sum()/3,
        'Sy': (a*(x0+x1)).sum()/3,
        'Ixx': (a*(y0*y0+y0*y1+y1*y1)).sum()/6,
        'Iyy': (a*(x0*x0+x0*x1+x1*x1)).sum()/6,
        'Ixy': -(a*(x0*y0+x1*y1+(x0*y1+x1*y0)/2)).sum()/6,
        } 
[docs]def extendedSectionChar(S):
    """Computes extended section characteristics for the given section.
    S is a dict with section basic section characteristics as returned by
    sectionChar().
    This function computes and returns a dict with the following:
    - `xG`, `yG` : coordinates of the center of gravity G of the plane section
    - `IGxx`, `IGyy`, `IGxy` : second area moments and product around axes
      through G and  parallel with the global x,y-axes
    - `alpha` : angle(in radians) between the global x,y axes and the principal
      axes (X,Y) of the section (X and Y always pass through G)
    - `IXX`, `IYY` : principal second area moments around X,Y respectively. (The
      second area product is always zero.)
    """
    xG =  S['Sy']/S['A']
    yG =  S['Sx']/S['A']
    IGxx = S['Ixx'] - S['A'] * yG**2
    IGyy = S['Iyy'] - S['A'] * xG**2
    IGxy = S['Ixy'] + S['A'] * xG*yG
    alpha, IXX, IYY = princTensor2D(IGxx, IGyy, IGxy)
    return {
        'xG': xG,
        'yG': yG,
        'IGxx': IGxx,
        'IGyy': IGyy,
        'IGxy': IGxy,
        'alpha': alpha,
        'IXX': IXX,
        'IYY': IYY,
        } 
[docs]def princTensor2D(Ixx, Iyy, Ixy):
    """Compute the principal values and directions of a 2D tensor.
    Returns a tuple with three values:
    - `alpha` : angle (in radians) from x-axis to principal X-axis
    - `IXX,IYY` : principal values of the tensor
    """
    C = (Ixx+Iyy) * 0.5
    D = (Ixx-Iyy) * 0.5
    R = np.sqrt(D**2 + Ixy**2)
    IXX = C+R
    IYY = C-R
    alpha = np.arctan2(Ixy, D) * 0.5
    return alpha, IXX, IYY 
# End