def rytz_axis_construction(d1: Vec3, d2: Vec3) -> Tuple[Vec3, Vec3, float]: """ The Rytz’s axis construction is a basic method of descriptive Geometry to find the axes, the semi-major axis and semi-minor axis, starting from two conjugated half-diameters. Source: `Wikipedia <https://en.m.wikipedia.org/wiki/Rytz%27s_construction>`_ Given conjugated diameter `d1` is the vector from center C to point P and the given conjugated diameter `d2` is the vector from center C to point Q. Center of ellipse is always ``(0, 0, 0)``. This algorithm works for 2D/3D vectors. Args: d1: conjugated semi-major axis as :class:`Vec3` d2: conjugated semi-minor axis as :class:`Vec3` Returns: Tuple of (major axis, minor axis, ratio) """ Q = Vec3(d1) # vector CQ # calculate vector CP', location P' if math.isclose(d1.z, 0, abs_tol=1e-9) and math.isclose( d2.z, 0, abs_tol=1e-9): # Vec3.orthogonal() works only for vectors in the xy-plane! P1 = Vec3(d2).orthogonal(ccw=False) else: extrusion = d1.cross(d2) P1 = extrusion.cross(d2).normalize(d2.magnitude) D = P1.lerp(Q) # vector CD, location D, midpoint of P'Q radius = D.magnitude radius_vector = (Q - P1).normalize(radius) # direction vector P'Q A = D - radius_vector # vector CA, location A B = D + radius_vector # vector CB, location B if A.isclose(NULLVEC) or B.isclose(NULLVEC): raise ArithmeticError('Conjugated axis required, invalid source data.') major_axis_length = (A - Q).magnitude minor_axis_length = (B - Q).magnitude if math.isclose(major_axis_length, 0.) or math.isclose( minor_axis_length, 0.): raise ArithmeticError('Conjugated axis required, invalid source data.') ratio = minor_axis_length / major_axis_length major_axis = B.normalize(major_axis_length) minor_axis = A.normalize(minor_axis_length) return major_axis, minor_axis, ratio
def minor_axis(major_axis: Vec3, extrusion: Vec3, ratio: float) -> Vec3: return extrusion.cross(major_axis).normalize(major_axis.magnitude * ratio)