Sheared font - no Macros

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Sheared font - no Macros

Post by edwilliams16 »

In the course of experimentation, I discovered it is possible to shear an object with a proper sequence of scalings and rotations. As far as I know, this would normally require a suitable macro to manipulate the object's transformation matrix.

The shear angle is controlled by the Dynamic data's Scale quantity ( one gives you 45 degrees, zero nothing.) The expressions are complicated, but you can copy them.
Screenshot 2023-01-23 at 5.51.26 PM.png
Screenshot 2023-01-23 at 5.51.26 PM.png (16.2 KiB) Viewed 602 times
Screenshot 2023-01-23 at 5.51.45 PM.png
Screenshot 2023-01-23 at 5.51.45 PM.png (14.85 KiB) Viewed 602 times
The Matrix API doesn't appear to provide a way of decomposing a matrix into a deformation (scale and shear) and Placement (base and rotation), but some web searching found a routine which I adapted.

Code: Select all

#BSD License https://github.com/matthew-brett/transforms3d/blob/main/LICENSE

import numpy as np
import math

def striu2mat(striu):
    ''' Construct shear matrix from upper triangular vector
    Parameters
    ----------
    striu : array, shape (N,)
       vector giving triangle above diagonal of shear matrix.
    Returns
    -------
    SM : array, shape (N, N)
       shear matrix
    Examples
    --------
    >>> S = [0.1, 0.2, 0.3]
    >>> striu2mat(S)
    array([[1. , 0.1, 0.2],
           [0. , 1. , 0.3],
           [0. , 0. , 1. ]])
    >>> striu2mat([1])
    array([[1., 1.],
           [0., 1.]])
    >>> striu2mat([1, 2])
    Traceback (most recent call last):
       ...
    ValueError: 2 is a strange number of shear elements
    Notes
    -----
    Shear lengths are triangular numbers.
    See http://en.wikipedia.org/wiki/Triangular_number
    '''
    n = len(striu)
    # cached case
    if n in _shearers:
        N, inds = _shearers[n]
    else: # General case
        N = ((-1+math.sqrt(8*n+1))/2.0)+1 # n+1 th root
        if N != math.floor(N):
            raise ValueError('%d is a strange number of shear elements' %
                             n)
        N = int(N)
        inds = np.triu(np.ones((N,N)), 1).astype(bool)
    M = np.eye(N)
    M[inds] = striu
    return M

def decompose44(A44):
    ''' Decompose 4x4 homogenous affine matrix into parts.
    The parts are translations, rotations, zooms, shears.
    This is the same as :func:`decompose` but specialized for 4x4 affines.
    Decomposes `A44` into ``T, R, Z, S``, such that::
       Smat = np.array([[1, S[0], S[1]],
                        [0,    1, S[2]],
                        [0,    0,    1]])
       RZS = np.dot(R, np.dot(np.diag(Z), Smat))
       A44 = np.eye(4)
       A44[:3,:3] = RZS
       A44[:-1,-1] = T
    The order of transformations is therefore shears, followed by
    zooms, followed by rotations, followed by translations.
    This routine only works for shape (4,4) matrices
    Parameters
    ----------
    A44 : array shape (4,4)
    Returns
    -------
    T : array, shape (3,)
       Translation vector
    R : array shape (3,3)
        rotation matrix
    Z : array, shape (3,)
       Zoom vector.  May have one negative zoom to prevent need for negative
       determinant R matrix above
    S : array, shape (3,)
       Shear vector, such that shears fill upper triangle above
       diagonal to form shear matrix (type ``striu``).
    Examples
    --------
    >>> T = [20, 30, 40] # translations
    >>> R = [[0, -1, 0], [1, 0, 0], [0, 0, 1]] # rotation matrix
    >>> Z = [2.0, 3.0, 4.0] # zooms
    >>> S = [0.2, 0.1, 0.3] # shears
    >>> # Now we make an affine matrix
    >>> A = np.eye(4)
    >>> Smat = np.array([[1, S[0], S[1]],
    ...                  [0,    1, S[2]],
    ...                  [0,    0,    1]])
    >>> RZS = np.dot(R, np.dot(np.diag(Z), Smat))
    >>> A[:3,:3] = RZS
    >>> A[:-1,-1] = T # set translations
    >>> Tdash, Rdash, Zdash, Sdash = decompose44(A)
    >>> np.allclose(T, Tdash)
    True
    >>> np.allclose(R, Rdash)
    True
    >>> np.allclose(Z, Zdash)
    True
    >>> np.allclose(S, Sdash)
    True
    Notes
    -----
    The implementation inspired by:
    *Decomposing a matrix into simple transformations* by Spencer
    W. Thomas, pp 320-323 in *Graphics Gems II*, James Arvo (editor),
    Academic Press, 1991, ISBN: 0120644819.
    The upper left 3x3 of the affine consists of a matrix we'll call
    RZS::
       RZS = R * Z *S
    where R is a rotation matrix, Z is a diagonal matrix of scalings::
       Z = diag([sx, sy, sz])
    and S is a shear matrix of form::
       S = [[1, sxy, sxz],
            [0,   1, syz],
            [0,   0,   1]])
    Running all this through sympy (see 'derivations' folder) gives
    ``RZS`` as ::
       [R00*sx, R01*sy + R00*sx*sxy, R02*sz + R00*sx*sxz + R01*sy*syz]
       [R10*sx, R11*sy + R10*sx*sxy, R12*sz + R10*sx*sxz + R11*sy*syz]
       [R20*sx, R21*sy + R20*sx*sxy, R22*sz + R20*sx*sxz + R21*sy*syz]
    ``R`` is defined as being a rotation matrix, so the dot products between
    the columns of ``R`` are zero, and the norm of each column is 1.  Thus
    the dot product::
       R[:,0].T * RZS[:,1]
    that results in::
       [R00*R01*sy + R10*R11*sy + R20*R21*sy + sx*sxy*R00**2 + sx*sxy*R10**2 + sx*sxy*R20**2]
    simplifies to ``sy*0 + sx*sxy*1`` == ``sx*sxy``.  Therefore::
       R[:,1] * sy = RZS[:,1] - R[:,0] * (R[:,0].T * RZS[:,1])
    allowing us to get ``sy`` with the norm, and sxy with ``R[:,0].T *
    RZS[:,1] / sx``.
    Similarly ``R[:,0].T * RZS[:,2]`` simplifies to ``sx*sxz``, and
    ``R[:,1].T * RZS[:,2]`` to ``sy*syz`` giving us the remaining
    unknowns.
    '''
    A44 = np.asarray(A44)
    T = A44[:-1,-1]
    RZS = A44[:-1,:-1]
    # compute scales and shears
    M0, M1, M2 = np.array(RZS).T
    # extract x scale and normalize
    sx = math.sqrt(np.sum(M0**2))
    M0 /= sx
    # orthogonalize M1 with respect to M0
    sx_sxy = np.dot(M0, M1)
    M1 -= sx_sxy * M0
    # extract y scale and normalize
    sy = math.sqrt(np.sum(M1**2))
    M1 /= sy
    sxy = sx_sxy / sx
    # orthogonalize M2 with respect to M0 and M1
    sx_sxz = np.dot(M0, M2)
    sy_syz = np.dot(M1, M2)
    M2 -= (sx_sxz * M0 + sy_syz * M1)
    # extract z scale and normalize
    sz = math.sqrt(np.sum(M2**2))
    M2 /= sz
    sxz = sx_sxz / sx
    syz = sy_syz / sy
    # Reconstruct rotation matrix, ensure positive determinant
    Rmat = np.array([M0, M1, M2]).T
    if np.linalg.det(Rmat) < 0:
        sx *= -1
        Rmat[:,0] *= -1
    return T, Rmat, np.array([sx, sy, sz]), np.array([sxy, sxz, syz])

def decomposeAffine(matrix):
    ''' decomposeAffine(App.Matrix: matrix) ->
    [App.Vector: translation,
     App.Rotation: rotation
     list: scale
     [[],[],[]]: shear]
     decomposes the matrix - order of operation  shear, scale, rotate then translate
     '''
    matlist = matrix.A
    mat = np.array([matlist[0:4],matlist[4:8],matlist[8:12],matlist[12:16]])
    T, R, Z, S = decompose44(mat)
    translation = App.Vector(*T.tolist())
    rotation = FreeCAD.Rotation(*R.flatten().tolist())
    scale = Z.tolist()
    shear = [[1, S[0], S[1]], [0, 1, S[2]], [0, 0, 1]]
    return translation, rotation, scale, shear


sel =Gui.Selection.getSelectionEx('',0)[0]
path = sel.SubElementNames[0]
obj, matrix, shp = sel.Object.getSubObject(path, retType =2)
translation, rotation, scale, shearmatrix = decomposeAffine(matrix)
print(f'translation = {translation}\nRotation Axis {rotation.Axis} Angle {math.degrees(rotation.Angle)} deg\nscale {scale}\nshear {shearmatrix}') 
if hasattr(shp, 'Point'):
    print(f'vertex Global loc = {shp.Point}')
Select the object and run.
Attachments
shear.FCStd
(14.25 KiB) Downloaded 23 times
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Sheared font - no Macros

Post by onekk »

Nice to note and keep around just in case.

Thank you.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Roy_043
Veteran
Posts: 8450
Joined: Thu Dec 27, 2018 12:28 pm

Re: Sheared font - no Macros

Post by Roy_043 »

Post Reply