text: # -*- coding: utf-8 -*-
  import math
  import re
  import sys
  from pyaedt.generic.general_methods import aedt_exception_handler
  from pyaedt.modeler.modeler_constants import CoordinateSystemPlane, CoordinateSystemAxis, SweepDraftType
  
  class GeometryOperators(object):
      """Manages geometry operators."""
  
      @staticmethod
      @aedt_exception_handler
      def List2list(input_list):
          """Convert a C# list object to a Python list.
  
          This function performs a deep conversion.
  
          Parameters
          ----------
          input_list : list
              C# list to convert to a Python list.
  
          Returns
          -------
          list
              Converted Python list.
  
          """
          output_list = []
          for i in input_list:
              if "List" in str(type(i)):
                  output_list.append(GeometryOperators.List2list(list(i)))
              else:
                  output_list.append(i)
          return output_list
  
      @staticmethod
      @aedt_exception_handler
      def parse_dim_arg(string, scale_to_unit=None, variable_manager=None):
          """Convert a number and unit to a float.
  
          Angles are converted in radians.
  
          Parameters
          ----------
          string : str
              String to convert. For example, ``"2mm"``.
          scale_to_unit : str
              Units for the value to convert. For example, ``"mm"``.
          variable_manager : `pyaedt.application.Variables.VariableManager`, optional
              Try to parse formula and returns numeric value.
          Returns
          -------
          float
              Float value for the converted value and units. For example, ``0.002`.
  
          Examples
          --------
          Parse `'"2mm"'`.
  
          >>> from pyaedt.modeler.GeometryOperators import GeometryOperators as go
          >>> go.parse_dim_arg('2mm')
          0.002
  
          Use the optional argument ``scale_to_unit`` to specify the destination unit.
  
          >>> go.parse_dim_arg('2mm', scale_to_unit='mm')
          2.0
  
          """
          scaling = {
              "m": 1.0,
              "meter": 1.0,
              "meters": 1.0,
              "dm": 0.1,
              "cm": 1e-2,
              "mm": 1e-3,
              "um": 1e-6,
              "nm": 1e-9,
              "in": 2.54e-2,
              "mil": 2.54e-5,
              "uin": 2.54e-8,
              "ft": 3.048e-1,
              "s": 1.0,
              "sec": 1.0,
              "ms": 1e-3,
              "us": 1e-6,
              "ns": 1e-9,
              "Hz": 1.0,
              "kHz": 1e3,
              "MHz": 1e6,
              "GHz": 1e9,
              "THz": 1e12,
          }
  
          if type(string) is not str:
              try:
                  return float(string)
              except ValueError:
                  raise TypeError("Input argument is not string nor number")
  
          if scale_to_unit:
              try:
                  sunit = scaling[scale_to_unit]
              except KeyError as e:
                  raise e
          else:
              sunit = 1.0
  
          pattern = r"(?P<number>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s*(?P<unit>[a-zA-Z]*)"
          m = re.search(pattern, string)
          if m:
              if m.group(0) != string:
                  if variable_manager:
                      variable_manager["temp_var"] = string
                      value = variable_manager["temp_var"].numeric_value
                      del variable_manager["temp_var"]
                      return value
                  else:
                      return string
              elif not m.group("unit"):
                  return float(m.group("number"))
              elif m.group("unit") == "deg":
                  return GeometryOperators.deg2rad(float(m.group("number")))
              elif m.group("unit") == "rad":
                  return float(m.group("number"))
              else:
                  try:
                      scaling_factor = scaling[m.group("unit")]
                  except KeyError as e:
                      raise e
                  else:
                      return float(m.group("number")) * scaling_factor / sunit
          else:
              raise TypeError("String is no number")
  
      @staticmethod
      @aedt_exception_handler
      def cs_plane_str(val):
          """Retrieve a string for a coordinate system plane.
  
          Parameters
          ----------
          val :
  
  
          Returns
          -------
          str
             String for the coordinate system plane.
  
          """
          if val == CoordinateSystemPlane.XYPlane:
              return "Z"
          elif val == CoordinateSystemPlane.YZPlane:
              return "X"
          else:
              return "Y"
  
      @staticmethod
      @aedt_exception_handler
      def cs_axis_str(val):
          """Retrieve a string for a coordinate system axis.
  
          Parameters
          ----------
          val :
  
  
          Returns
          -------
          str
              String for the coordinate system axis.
  
          """
          if val == CoordinateSystemAxis.XAxis or val == "X":
              return "X"
          elif val == CoordinateSystemAxis.YAxis or val == "Y":
              return "Y"
          else:
              return "Z"
  
      @staticmethod
      @aedt_exception_handler
      def draft_type_str(val):
          """Retrieve the draft type.
  
          Parameters
          ----------
          val :
  
  
          Returns
          -------
          str
             Type of the draft.
  
          """
          if val == SweepDraftType.ExtendedDraft:
              return "Extended"
          elif val == SweepDraftType.RoundDraft:
              return "Round"
          else:
              return "Natural"
  
      @staticmethod
      @aedt_exception_handler
      def get_mid_point(v1, v2):
          """Evaluate the midpoint between two points.
  
          Parameters
          ----------
          v1 : list
              List of ``[x, y, z]`` coordinates for the first point.
          v2 : list
              List of ``[x, y, z]`` coordinates for the second point.
  
          Returns
          -------
          list
              List of ``[x, y, z]`` coordinates for the midpoint.
  
          """
          x = (v1[0] + v2[0]) / 2.0
          y = (v1[1] + v2[1]) / 2.0
          z = (v1[2] + v2[2]) / 2.0
          return [x, y, z]
  
      @staticmethod
      @aedt_exception_handler
      def get_triangle_area(v1, v2, v3):
          """Evaluate the area of a triangle defined by its three vertices.
  
          Parameters
          ----------
          v1 : list
              List of ``[x, y, z]`` coordinates for the first vertex.
          v2 : list
              List of ``[x, y, z]`` coordinates for the second vertex.
          v3 : list
              List of ``[x, y, z]`` coordinates for the third vertex.
  
          Returns
          -------
          float
              Area of the triangle.
  
          """
          a = ((v1[0] - v2[0]) ** 2 + (v1[1] - v2[1]) ** 2 + (v1[2] - v2[2]) ** 2) ** 0.5
          b = ((v2[0] - v3[0]) ** 2 + (v2[1] - v3[1]) ** 2 + (v2[2] - v3[2]) ** 2) ** 0.5
          c = ((v3[0] - v1[0]) ** 2 + (v3[1] - v1[1]) ** 2 + (v3[2] - v1[2]) ** 2) ** 0.5
          s = 0.5 * (a + b + c)
          area = (s * (s - a) * (s - b) * (s - c)) ** 0.5
          return area
  
      @staticmethod
      @aedt_exception_handler
      def v_cross(a, b):
          """Evaluate the cross product of two geometry vectors.
  
          Parameters
          ----------
          a : list
              List of ``[x, y, z]`` coordinates for the first vector.
          b : list
              List of ``[x, y, z]`` coordinates for the second vector.
  
          Returns
          -------
          list
              List of ``[x, y, z]`` coordinates for the result vector.
          """
  
          c = [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
          return c
  
      @staticmethod
      @aedt_exception_handler
      def _v_dot(a, b):
          """Evaluate the dot product between two geometry vectors.
  
          Parameters
          ----------
          a : list
              List of ``[x, y, z]`` coordinates for the first vector.
          b : list
              List of ``[x, y, z]`` coordinates for the second vector.
  
          Returns
          -------
          float
              Result of the dot product.
  
          """
          c = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
          return c
  
      @staticmethod
      @aedt_exception_handler
      def v_dot(a, b):
          """Evaluate the dot product between two geometry vectors.
  
          Parameters
          ----------
          a : list
              List of ``[x, y, z]`` coordinates for the first vector.
          b : list
              List of ``[x, y, z]`` coordinates for the second vector.
  
          Returns
          -------
          float
              Result of the dot product.
  
          """
          return GeometryOperators._v_dot(a, b)
  
      @staticmethod
      @aedt_exception_handler
      def v_prod(s, v):
          """Evaluate the product between a scalar value and a vector.
  
          Parameters
          ----------
          s : float
              Scalar value.
          v : list
              List of values for the vector in the format ``[v1, v2,..., vn]``.
              The vector can be any length.
  
          Returns
          -------
          list
              List of values for the result vector. This list is the
              same length as the list for the input vector.
  
          """
          r = [s * i for i in v]
          return r
  
      @staticmethod
      @aedt_exception_handler
      def v_sub(a, b):
          """Evaluate two geometry vectors by subtracting them (a-b).
  
          Parameters
          ----------
          a : list
              List of ``[x, y, z]`` coordinates for the first vector.
          b : list
              List of ``[x, y, z]`` coordinates for the second vector.
  
          Returns
          -------
          list
              List of ``[x, y, z]`` coordinates for the result vector.
  
          """
          c = [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
          return c
  
      @staticmethod
      @aedt_exception_handler
      def v_sum(a, b):
          """Evaluate two geometry vectors by adding them (a+b).
  
          Parameters
          ----------
          a : list
              List of ``[x, y, z]`` coordinates for the first vector.
          b : list
              List of ``[x, y, z]`` coordinates for the second vector.
  
          Returns
          -------
          list
              List of ``[x, y, z]`` coordinates for the result vector.
  
          """
          c = [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
          return c
  
      @staticmethod
      @aedt_exception_handler
      def v_norm(a):
          """Evaluate the Euclidean norm of a geometry vector.
  
          Parameters
          ----------
           a : list
              List of ``[x, y, z]`` coordinates for the vector.
  
          Returns
          -------
          float
              Evaluated norm in the same unit as the coordinates for the input vector.
  
          """
          m = (a[0] ** 2 + a[1] ** 2 + a[2] ** 2) ** 0.5
          return m
  
      @staticmethod
      @aedt_exception_handler
      def normalize_vector(v):
          """Normalize a geometry vector.
  
          Parameters
          ----------
          v : list
              List of ``[x, y, z]`` coordinates for vector.
  
          Returns
          -------
          list
              List of ``[x, y, z]`` coordinates for the normalized vector.
  
          """
          # normalize a vector to its norm
          norm = GeometryOperators.v_norm(v)
          vn = [i / norm for i in v]
          return vn
  
      @staticmethod
      @aedt_exception_handler
      def v_points(p1, p2):
          """Return the vector from one point to another point.
  
          Parameters
          ----------
          p1 : list
              List of ``[x1,y1,z1]`` coordinates for the first point.
          p2 : list
              List of ``[x2,y2,z2]`` coordinates for second point.
  
          Returns
          -------
          list
              List of ``[vx, vy, vz]``coordinates for the vector from the first point to the second point.
          """
          return GeometryOperators.v_sub(p2, p1)
  
      @staticmethod
      @aedt_exception_handler
      def points_distance(p1, p2):
          """Evaluate the distance between two points expressed as their Cartesian coordinates.
  
          Parameters
          ----------
          p1 : list
              List of ``[x1,y1,z1]"`` coordinates for the first point.
          p2 : list
              List of ``[x2,y2,z2] coordinates for the second ppint.
  
          Returns
          -------
          float
              Distance between the two points in the same unit as the coordinates for the points.
  
          """
          v = GeometryOperators.v_points(p1, p2)
          d = GeometryOperators.v_norm(v)
          return d
  
      @staticmethod
      @aedt_exception_handler
      def find_point_on_plane(pointlists, direction=0):
          """Find a point on a plane.
  
          Parameters
          ----------
          pointlists : list
              List of points.
          direction : int, optional
               The default is ``0``.
  
          Returns
          -------
          list
  
          """
  
          if direction <= 2:
              point = 1e6
              for p in pointlists:
                  if p[direction] < point:
                      point = p[direction]
          else:
              point = -1e6
              for p in pointlists:
                  if p[direction - 3] > point:
                      point = p[direction - 3]
          return point
  
      @staticmethod
      @aedt_exception_handler
      def distance_vector(p, a, b):
          """Evaluate the vector distance between point ``p`` and a line defined by two points, ``a`` and ``b``.
  
          .. note::
          The formula is  ``d = (a-p)-((a-p)dot p)n``, where ``a`` is a point of the line (either ``a`` or ``b``)
          and ``n`` is the unit vector in the direction of the line.
  
          Parameters
          ----------
          p : list
              List of ``[x, y, z]`` coordinates for the reference point.
          a : list
              List of ``[x, y, z]`` coordinates for the first point of the segment.
          b : list
              List of ``[x, y, z]`` coordinates for the second point of the segment.
  
          Returns
          -------
          list
              List of ``[x, y, z]`` coordinates for the distance vector.
  
          """
          v1 = GeometryOperators.v_points(a, b)
          n = [i / GeometryOperators.v_norm(v1) for i in v1]
          v2 = GeometryOperators.v_sub(a, p)
          s1 = GeometryOperators._v_dot(v2, n)
          v3 = [i * s1 for i in n]
          vd = GeometryOperators.v_sub(v2, v3)
          return vd
  
      @staticmethod
      @aedt_exception_handler
      def is_between_points(p, a, b, tol=1e-6):
          """Check if a point lies on the segment defined by two points.
  
          Parameters
          ----------
          p : list
              List of ``[x, y, z]`` coordinates for the reference point ``p``.
          a : list
              List of ``[x, y, z]`` coordinates for the first point of the segment.
          b : list
              List of ``[x, y, z]`` coordinates for the second point of the segment.
          tol : float
              Linear tolerance. The default value is ``1e-6``.
  
          Returns
          -------
          bool
              ``True`` when the point lies on the segment defined by the two points, ``False`` otherwise.
  
          """
          v1 = GeometryOperators.v_points(a, b)
          v2 = GeometryOperators.v_points(a, p)
          if abs(GeometryOperators.v_norm(GeometryOperators.v_cross(v1, v2))) > tol:
              return False  # not colinear
          t1 = GeometryOperators._v_dot(v1, v2)
          t2 = GeometryOperators._v_dot(v1, v1)
          if t1 < 0 or t1 > t2:
              return False
          else:
              return True
  
      @staticmethod
      @aedt_exception_handler
      def is_parallel(a1, a2, b1, b2, tol=1e-6):
          """Check if a segment defined by two points is parallel to a segment defined by two other points.
  
          Parameters
          ----------
          a1 : list
              List of ``[x, y, z]`` coordinates for the first point of the fiirst segment.
          a2 : list
              List of ``[x, y, z]`` coordinates for the second point of the first segment.
          b1 : list
              List of ``[x, y, z]`` coordinates for the first point of the second segment.
          b2 : list
              List of ``[x, y, z]`` coordinates for the second point of the second segment.
          tol : float
              Linear tolerance. The default value is ``1e-6``.
  
          Returns
          -------
          bool
              ``True`` when successful, ``False`` when failed.
  
          """
          if 1.0 - GeometryOperators.parallel_coeff(a1, a2, b1, b2) < tol * tol:
              return True
          else:
              return False
  
      @staticmethod
      @aedt_exception_handler
      def parallel_coeff(a1, a2, b1, b2):
          """ADD DESCRIPTION.
  
          Parameters
          ----------
          a1 : list
              List of ``[x, y, z]`` coordinates for the first point of the first segment.
          a2 : list
              List of ``[x, y, z]`` coordinates for the second point of the first segment.
          b1 : list
              List of ``[x, y, z]`` coordinates for the first point of the second segment.
          b2 : list
              List of ``[x, y, z]`` coordinates for the second point of the second segment.
  
          Returns
          -------
          float
              _vdot of 4 vertex of 2 segments
          """
          va = GeometryOperators.v_points(a1, a2)
          vb = GeometryOperators.v_points(b1, b2)
          an = GeometryOperators.v_norm(va)
          bn = GeometryOperators.v_norm(vb)
          var = GeometryOperators._v_dot(va, vb) / (an * bn)
          return abs(var)
  
      @staticmethod
      @aedt_exception_handler
      def is_projection_inside(a1, a2, b1, b2):
          """Project a segment onto another segment and check if the projected segment is inside it.
  
          Parameters
          ----------
          a1 : list
              List of ``[x, y, z]`` coordinates for the first point of the projected segment.
          a2 : list
              List of ``[x, y, z]`` coordinates for the second point of the projected segment.
          b1 : list
              List of ``[x, y, z]`` coordinates for the first point of the other segment.
          b2 : list
              List of ``[x, y, z]`` coordinates for the second point of the other segment.
  
          Returns
          -------
          bool
              ``True`` when the projected segment is inside the other segmennt, ``False`` otherwise.
  
          """
          if not GeometryOperators.is_parallel(a1, a2, b1, b2):
              return False
          d = GeometryOperators.distance_vector(a1, b1, b2)
          a1n = GeometryOperators.v_sum(a1, d)
          a2n = GeometryOperators.v_sum(a2, d)
          if not GeometryOperators.is_between_points(a1n, b1, b2):
              return False
          if not GeometryOperators.is_between_points(a2n, b1, b2):
              return False
          return True
  
      @staticmethod
      @aedt_exception_handler
      def arrays_positions_sum(vertlist1, vertlist2):
          """ADD DESCRIPTION.
  
          Parameters
          ----------
          vertlist1 : list
  
          vertlist2 : list
  
          Returns
          -------
          float
  
          """
          s = 0
          for el in vertlist1:
              for el1 in vertlist2:
                  s += GeometryOperators.points_distance(el, el1)
          return s / (len(vertlist1) + len(vertlist2))
  
      @staticmethod
      @aedt_exception_handler
      def v_angle(a, b):
          """Evaluate the angle between two geometry vectors.
  
          Parameters
          ----------
          a : list
              List of ``[x, y, z]`` coordinates for the first vector.
          b : list
              List of ``[x, y, z]`` coordinates for the second vector.
  
          Returns
          -------
          float
              Angle in radians.
  
          """
          d = GeometryOperators.v_dot(a, b)
          an = GeometryOperators.v_norm(a)
          bn = GeometryOperators.v_norm(b)
          theta = math.acos(d / (an * bn))
          return theta
  
      @staticmethod
      @aedt_exception_handler
      def pointing_to_axis(x_pointing, y_pointing):
          """Retrieve the axes from the HFSS X axis and Y pointing axis as per
          the definition of the AEDT interface coordinate system.
  
          Parameters
          ----------
          x_pointing : list
              List of ``[x, y, z]`` coordinates for the X axis.
  
          y_pointing : list
              List of ``[x, y, z]`` coordinates for the Y pointing axis.
  
          Returns
          -------
          tuple
              ``[Xx, Xy, Xz], [Yx, Yy, Yz], [Zx, Zy, Zz]`` of the three axes (normalized).
          """
  
          xp = GeometryOperators.normalize_vector(x_pointing)
  
          tp = GeometryOperators.v_dot(y_pointing, xp)
          ypt = GeometryOperators.v_sub(y_pointing, GeometryOperators.v_prod(tp, xp))
          yp = GeometryOperators.normalize_vector(ypt)
  
          zpt = GeometryOperators.v_cross(xp, yp)
          zp = GeometryOperators.normalize_vector(zpt)
  
          return xp, yp, zp
  
      @staticmethod
      @aedt_exception_handler
      def axis_to_euler_zxz(x, y, z):
          """Finds the Euler angles of a frame defined by X, Y, and Z axes, following the rotation sequence ZXZ.
  
          Provides assumption for the gimbal lock problem.
  
          Parameters
          ----------
          x : list
              List of ``[Xx, Xy, Xz]`` coordinates for the X axis.
          y : list
              List of ``[Yx, Yy, Yz]`` coordinates for the Y axis.
          z : list
              List of ``[Zx, Zy, Zz]`` coordinates for the Z axis.
  
          Returns
          -------
          tuple
              (phi, theta, psi) containing the Euler angles in radians.
  
          """
          x1 = x[0]
          x2 = x[1]
          x3 = x[2]
          y3 = y[2]
          z1 = z[0]
          z2 = z[1]
          z3 = z[2]
          if z == [0, 0, 1]:
              phi = GeometryOperators.atan2(x2, x1)
              theta = 0.0
              psi = 0.0
          elif z == [0, 0, -1]:
              phi = GeometryOperators.atan2(x2, x1)
              theta = math.pi
              psi = 0.0
          else:
              phi = GeometryOperators.atan2(z1, -z2)
              theta = math.acos(z3)
              psi = GeometryOperators.atan2(x3, y3)
          return phi, theta, psi
  
      @staticmethod
      @aedt_exception_handler
      def axis_to_euler_zyz(x, y, z):
          """Finds the Euler angles of a frame defined by X, Y, and Z axes, following rotation sequence ZYZ.
  
          Provides assumption for the gimbal lock problem.
  
          Parameters
          ----------
          x : list
              List of ``[Xx, Xy, Xz]`` coordinates for the X axis.
          y : list
              List of ``[Yx, Yy, Yz]`` coordinates for the Y axis.
          z : list
              List of ``[Zx, Zy, Zz]`` coordinates for the Z axis.
  
          Returns
          -------
          tuple
              (phi, theta, psi) containing the Euler angles in radians.
  
          """
          x1 = x[0]
          x2 = x[1]
          x3 = x[2]
          y3 = y[2]
          z1 = z[0]
          z2 = z[1]
          z3 = z[2]
          if z == [0, 0, 1]:
              phi = GeometryOperators.atan2(x2, x1)
              theta = 0.0
              psi = 0.0
          elif z == [0, 0, -1]:
              phi = GeometryOperators.atan2(x2, x1)
              theta = math.pi
              psi = 0.0
          else:
              phi = GeometryOperators.atan2(z2, z1)
              theta = math.acos(z3)
              psi = GeometryOperators.atan2(y3, -x3)
          return phi, theta, psi
  
      @staticmethod
      @aedt_exception_handler
      def quaternion_to_axis(q):
          """Convert a quaternion to a rotated frame defined by X, Y, and Z axes.
  
          Parameters
          ----------
          q : list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          Returns
          -------
          tuple
              [Xx, Xy, Xz], [Yx, Yy, Yz], [Zx, Zy, Zz] of the three axes (normalized).
  
          """
          q1 = q[0]
          q2 = q[1]
          q3 = q[2]
          q4 = q[3]
  
          m11 = q1 * q1 + q2 * q2 - q3 * q3 - q4 * q4
          m12 = 2.0 * (q2 * q3 - q1 * q4)
          m13 = 2.0 * (q2 * q4 + q1 * q3)
  
          m21 = 2.0 * (q2 * q3 + q1 * q4)
          m22 = q1 * q1 - q2 * q2 + q3 * q3 - q4 * q4
          m23 = 2.0 * (q3 * q4 - q1 * q2)
  
          m31 = 2.0 * (q2 * q4 - q1 * q3)
          m32 = 2.0 * (q3 * q4 + q1 * q2)
          m33 = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
  
          x = GeometryOperators.normalize_vector([m11, m21, m31])
          y = GeometryOperators.normalize_vector([m12, m22, m32])
          z = GeometryOperators.normalize_vector([m13, m23, m33])
  
          return x, y, z
  
      @staticmethod
      @aedt_exception_handler
      def quaternion_to_axis_angle(q):
          """Convert a quaternion to the axis angle rotation formulation.
  
          Parameters
          ----------
          q : list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          Returns
          -------
          tuple
              ([ux, uy, uz], theta) containing the rotation axes expressed as X, Y, Z components of the unit vector ``u``
              and the rotation angle theta expressed in radians.
  
          """
          q1 = q[0]
          q2 = q[1]
          q3 = q[2]
          q4 = q[3]
          n = (q2 * q2 + q3 * q3 + q4 * q4) ** 0.5
          u = [q2 / n, q3 / n, q4 / n]
          theta = 2.0 * GeometryOperators.atan2(n, q1)
          return u, theta
  
      @staticmethod
      @aedt_exception_handler
      def axis_angle_to_quaternion(u, theta):
          """Convert the axis angle rotation formulation to a quaternion.
  
          Parameters
          ----------
          u : list
              List of ``[ux, uy, uz]`` coordinates for the rotation axis.
  
          theta : float
              Angle of rotation in radians.
  
          Returns
          -------
          list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          """
          un = GeometryOperators.normalize_vector(u)
          s = math.sin(theta * 0.5)
          q1 = math.cos(theta * 0.5)
          q2 = un[0] * s
          q3 = un[1] * s
          q4 = un[2] * s
          return [q1, q2, q3, q4]
  
      @staticmethod
      @aedt_exception_handler
      def quaternion_to_euler_zxz(q):
          """Convert a quaternion to Euler angles following rotation sequence ZXZ.
  
          Parameters
          ----------
          q : list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          Returns
          -------
          tuple
              (phi, theta, psi) containing the Euler angles in radians.
  
          """
          q1 = q[0]
          q2 = q[1]
          q3 = q[2]
          q4 = q[3]
          m13 = 2.0 * (q2 * q4 + q1 * q3)
          m23 = 2.0 * (q3 * q4 - q1 * q2)
          m33 = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
          m31 = 2.0 * (q2 * q4 - q1 * q3)
          m32 = 2.0 * (q3 * q4 + q1 * q2)
          phi = GeometryOperators.atan2(m13, -m23)
          theta = GeometryOperators.atan2((1.0 - m33 * m33) ** 0.5, m33)
          psi = GeometryOperators.atan2(m31, m32)
          return phi, theta, psi
  
      @staticmethod
      @aedt_exception_handler
      def euler_zxz_to_quaternion(phi, theta, psi):
          """Convert the Euler angles following rotation sequence ZXZ to a quaternion.
  
          Parameters
          ----------
          phi : float
              Euler angle psi in radians.
          theta : float
              Euler angle theta in radians.
          psi : float
              Euler angle phi in radians.
  
          Returns
          -------
          list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          """
          t1 = phi
          t2 = theta
          t3 = psi
          c = math.cos(t2 * 0.5)
          s = math.sin(t2 * 0.5)
          q1 = c * math.cos((t1 + t3) * 0.5)
          q2 = s * math.cos((t1 - t3) * 0.5)
          q3 = s * math.sin((t1 - t3) * 0.5)
          q4 = c * math.sin((t1 + t3) * 0.5)
          return [q1, q2, q3, q4]
  
      @staticmethod
      @aedt_exception_handler
      def quaternion_to_euler_zyz(q):
          """Convert a quaternion to Euler angles following rotation sequence ZYZ.
  
          Parameters
          ----------
          q : list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          Returns
          -------
          tuple
              (phi, theta, psi) containing the Euler angles in radians.
  
          """
          q1 = q[0]
          q2 = q[1]
          q3 = q[2]
          q4 = q[3]
          m13 = 2.0 * (q2 * q4 + q1 * q3)
          m23 = 2.0 * (q3 * q4 - q1 * q2)
          m33 = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
          m31 = 2.0 * (q2 * q4 - q1 * q3)
          m32 = 2.0 * (q3 * q4 + q1 * q2)
          phi = GeometryOperators.atan2(m23, m13)
          theta = GeometryOperators.atan2((1.0 - m33 * m33) ** 0.5, m33)
          psi = GeometryOperators.atan2(m32, -m31)
          return phi, theta, psi
  
      @staticmethod
      @aedt_exception_handler
      def euler_zyz_to_quaternion(phi, theta, psi):
          """Convert the Euler angles following rotation sequence ZYZ to a quaternion.
  
          Parameters
          ----------
          phi : float
              Euler angle psi in radians.
          theta : float
              Euler angle theta in radians.
          psi : float
              Euler angle phi in radians.
  
          Returns
          -------
          list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          """
          t1 = phi
          t2 = theta
          t3 = psi
          c = math.cos(t2 * 0.5)
          s = math.sin(t2 * 0.5)
          q1 = c * math.cos((t1 + t3) * 0.5)
          q2 = -s * math.sin((t1 - t3) * 0.5)
          q3 = s * math.cos((t1 - t3) * 0.5)
          q4 = c * math.sin((t1 + t3) * 0.5)
          return [q1, q2, q3, q4]
  
      @staticmethod
      @aedt_exception_handler
      def deg2rad(angle):
          """Convert the angle from degrees to radians.
  
          Parameters
          ----------
          angle : float
              Angle in degrees.
  
          Returns
          -------
          float
              Angle in radians.
  
          """
          pi = math.pi
          return angle / 180.0 * pi
  
      @staticmethod
      @aedt_exception_handler
      def rad2deg(angle):
          """Convert the angle from radians to degrees.
  
          Parameters
          ----------
          angle : float
              Angle in radians.
  
          Returns
          -------
          float
              Angle in degrees.
  
          """
          pi = math.pi
          return angle * 180.0 / pi
  
      @staticmethod
      @aedt_exception_handler
      def atan2(y, x):
          """Implementation of atan2 that does not suffer from the following issues:
          math.atan2(0.0, 0.0) = 0.0
          math.atan2(-0.0, 0.0) = -0.0
          math.atan2(0.0, -0.0) = 3.141592653589793
          math.atan2(-0.0, -0.0) = -3.141592653589793
          and returns always 0.0
  
          Parameters
          ----------
          y : float
              Y-axis value for atan2.
  
          x : float
              X-axis value for atan2.
  
          Returns
          -------
          float
              atan2(y, x)
  
          """
          eps = 7.0 / 3.0 - 4.0 / 3.0 - 1.0
          if abs(y) < eps:
              y = 0.0
          if abs(x) < eps:
              x = 0.0
          return math.atan2(y, x)
  
      @staticmethod
      @aedt_exception_handler
      def q_prod(p, q):
          """Evaluate the product of two quaternions, ``p`` and ``q``, defined as:
  
              * p = p0 + p' = p0 + ip1 + jp2 + kp3
  
              * q = q0 + q' = q0 + iq1 + jq2 + kq3
  
              r = pq = p0q0 - p' • q' + p0q' + q0p' + p' x q'
  
          Parameters
          ----------
          p : list
              List of ``[p1, p2, p3, p4]`` coordinates for quaternion ``p``.
  
          q : list
              List of ``[p1, p2, p3, p4]`` coordinates for quaternion ``q``.
  
          Returns
          -------
          list
              List of [r1, r2, r3, r4] coordinates for the result quaternion.
  
          """
          p0 = p[0]
          pv = p[1:4]
          q0 = q[0]
          qv = q[1:4]
  
          r0 = p0 * q0 - GeometryOperators.v_dot(pv, qv)
  
          t1 = GeometryOperators.v_prod(p0, qv)
          t2 = GeometryOperators.v_prod(q0, pv)
          t3 = GeometryOperators.v_cross(pv, qv)
          rv = GeometryOperators.v_sum(t1, GeometryOperators.v_sum(t2, t3))
  
          return [r0, rv[0], rv[1], rv[2]]
  
      @staticmethod
      @aedt_exception_handler
      def q_rotation(v, q):
          """Evaluate the rotation of a vector defined by a quaternion.
  
          Evaluated as:
  
          ``q = q0 + q' = q0 + iq1 + jq2 + kq3``
  
          ``w = qvq* = (q0^2 - |q'|^2)v + 2(q' • v)q' + 2q0(q' x v)``
  
          Parameters
          ----------
          v : list
              List of ``[v1, v2, v3]`` coordinates for the vector.
  
          q : list
              List of ``[q1, q2, q3, q4]``coordinates for the quaternion.
  
          Returns
          -------
          list
              List of ``[w1, w2, w3]`` coordinates for the result vector ``w``.
  
          """
          q0 = q[0]
          qv = q[1:4]
  
          c1 = q0 * q0 - (qv[0] * qv[0] + qv[1] * qv[1] + qv[2] * qv[2])
          t1 = GeometryOperators.v_prod(c1, v)
  
          c2 = 2.0 * GeometryOperators.v_dot(qv, v)
          t2 = GeometryOperators.v_prod(c2, qv)
  
          t3 = GeometryOperators.v_cross(qv, v)
          t4 = GeometryOperators.v_prod(2.0 * q0, t3)
  
          w = GeometryOperators.v_sum(t1, GeometryOperators.v_sum(t2, t4))
  
          return w
  
      @staticmethod
      @aedt_exception_handler
      def q_rotation_inv(v, q):
          """Evaluate the inverse rotation of a vector that is defined by a quaternion.
  
          It can also be the rotation of the coordinate frame with respect to the vector.
  
              q = q0 + q' = q0 + iq1 + jq2 + kq3
              q* = q0 - q' = q0 - iq1 - jq2 - kq3
              w = q*vq
  
          Parameters
          ----------
          v : list
              List of ``[v1, v2, v3]`` coordinates for the vector.
  
          q : list
              List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
  
          Returns
          -------
          list
              List of ``[w1, w2, w3]`` coordinates for the vector.
  
          """
          q1 = [q[0], -q[1], -q[2], -q[3]]
          return GeometryOperators.q_rotation(v, q1)
  
      @staticmethod
      @aedt_exception_handler
      def get_polygon_centroid(pts):
          """Evaluate the centroid of a polygon defined by its points.
  
          Parameters
          ----------
          pts : list
              List of points, with each point defined by its ``[x,y,z]`` coordinates.
  
          Returns
          -------
          list
              List of [x,y,z] coordinates for the centroid of the polygon.
  
          """
          sx = sy = sz = sl = sl2 = 0
          for i in range(len(pts)):  # counts from 0 to len(points)-1
              x0, y0, z0 = pts[i - 1]  # in Python points[-1] is last element of points
              x1, y1, z1 = pts[i]
              L = ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5
              sx += (x0 + x1) / 2 * L
              sy += (y0 + y1) / 2 * L
              L2 = ((z1 - z0) ** 2 + (x1 - x0) ** 2) ** 0.5
              sz += (z0 + z1) / 2 * L2
              sl += L
              sl2 += L2
          xc = sx / sl if sl != 0.0 else x1
          yc = sy / sl if sl != 0.0 else y1
          zc = sz / sl2 if sl2 != 0.0 else z1
  
          return [xc, yc, zc]
  
      @staticmethod
      @aedt_exception_handler
      def cs_xy_pointing_expression(yaw, pitch, roll):
          """Return x_pointing and y_pointing vectors as expressions from
          the yaw, ptich, and roll input (as strings).
  
          Parameters
          ----------
          yaw : str, required
              String expression for the yaw angle (rotation about Z-axis)
          pitch : str
              String expression for the pitch angle (rotation about Y-axis)
          roll : str
              String expression for the roll angle (rotation about X-axis)
  
          Returns
          -------
          [x_pointing, y_pointing] vector expressions.
          """
          # X-Pointing
          xx = "cos(" + yaw + ")*cos(" + pitch + ")"
          xy = "sin(" + yaw + ")*cos(" + pitch + ")"
          xz = "sin(" + pitch + ")"
  
          # Y-Pointing
          yx = "sin(" + roll + ")*sin(" + pitch + ")*cos(" + yaw + ") - "
          yx += "sin(" + yaw + ")*cos(" + roll + ")"
  
          yy = "sin(" + roll + ")*sin(" + yaw + ")*sin(" + pitch + ") + "
          yy += "cos(" + roll + ")*cos(" + yaw + ")"
  
          yz = "sin(" + roll + " + pi)*cos(" + pitch + ")"  # use pi to avoid negative sign.
  
          # x, y pointing vectors for CS
          x_pointing = [xx, xy, xz]
          y_pointing = [yx, yy, yz]
  
          return [x_pointing, y_pointing]
  
      @staticmethod
      @aedt_exception_handler
      def get_numeric(s):
          """Convert a string to a numeric value. Discard the suffix."""
          if type(s) == str:
              if s == "Global":
                  return 0.0
              else:
                  return float("".join(c for c in s if c.isdigit() or c == "."))
          elif s is None:
              return 0.0
          else:
              return float(s)
  
      @staticmethod
      @aedt_exception_handler
      def is_small(s):
          """
          Return True if the number represented by s is zero (i.e very small).
  
          Parameters
          ----------
          s, numeric or str
              Variable value.
  
          Returns
          -------
  
          """
          n = GeometryOperators.get_numeric(s)
          return True if math.fabs(n) < 2.0 * abs(sys.float_info.epsilon) else False
  
      @staticmethod
      @aedt_exception_handler
      def numeric_cs(cs_in):
          """
          Return a list of [x,y,z] numeric values
          given a coordinate system as input:
  
          cs_in could be a list of strings, ["x", "y", "z"]
          or "Global"
          """
          if type(cs_in) is str:
              if cs_in == "Global":
                  return [0.0, 0.0, 0.0]
              else:
                  return None
          elif type(cs_in) is list:
              if len(cs_in) == 3:
                  return [GeometryOperators.get_numeric(s) if type(s) is str else s for s in cs_in]
              else:
                  return [0, 0, 0]
  