Skip to content

Transform

This page contains classes defined in hakowan.transform module.

Transform dataclass

Transform is the base class of all transforms.

Source code in hakowan/grammar/transform/transform.py
@dataclass(kw_only=True, slots=True)
class Transform:
    """Transform is the base class of all transforms."""

    _child: Optional["Transform"] = None

    def __imul__(self, other: "Transform") -> "Transform":
        """In place update by applying another transform after the current transform.

        Args:
            other: The transform to apply after the current transform.
        """
        # Because transform may be used in multiple places in the layer graph, and it may have a
        # child in the future, it must be deep copied to avoid undesired side effects.
        if self._child is None:
            self._child = copy.deepcopy(other)
        else:
            t = self._child
            while t._child is not None:
                t = t._child
            t._child = copy.deepcopy(other)
        return self

    def __mul__(self, other: "Transform") -> "Transform":
        """Apply another transform, `other`, after the current transform.

        Args:
            other: The other transform.

        Returns: A new transform that is the composition of the current transform and `other`.
        """
        r = copy.deepcopy(self)
        r *= other
        return r

__imul__(other)

In place update by applying another transform after the current transform.

Parameters:

Name Type Description Default
other Transform

The transform to apply after the current transform.

required
Source code in hakowan/grammar/transform/transform.py
def __imul__(self, other: "Transform") -> "Transform":
    """In place update by applying another transform after the current transform.

    Args:
        other: The transform to apply after the current transform.
    """
    # Because transform may be used in multiple places in the layer graph, and it may have a
    # child in the future, it must be deep copied to avoid undesired side effects.
    if self._child is None:
        self._child = copy.deepcopy(other)
    else:
        t = self._child
        while t._child is not None:
            t = t._child
        t._child = copy.deepcopy(other)
    return self

__mul__(other)

Apply another transform, other, after the current transform.

Parameters:

Name Type Description Default
other Transform

The other transform.

required

Returns: A new transform that is the composition of the current transform and other.

Source code in hakowan/grammar/transform/transform.py
def __mul__(self, other: "Transform") -> "Transform":
    """Apply another transform, `other`, after the current transform.

    Args:
        other: The other transform.

    Returns: A new transform that is the composition of the current transform and `other`.
    """
    r = copy.deepcopy(self)
    r *= other
    return r

Filter dataclass

Bases: Transform

Filter data based on a condition.

Attributes:

Name Type Description
data AttributeLike | None

The attribute to filter on. If None, the vertex position is used.

condition Callable

A callable that takes a single argument, the value of the attribute, and returns a boolean indicating whether the data should be kept.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class Filter(Transform):
    """Filter data based on a condition.

    Attributes:
        data: The attribute to filter on. If None, the vertex position is used.
        condition: A callable that takes a single argument, the value of the attribute, and returns
            a boolean indicating whether the data should be kept.
    """

    data: AttributeLike | None = None
    condition: Callable = field(default=_default_condition)

UVMesh dataclass

Bases: Transform

Extract UV mesh from data.

Attributes:

Name Type Description
uv AttributeLike | None

The attribute defining the UV coordinates. If None, automatically deetect the UV attribute from the data.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class UVMesh(Transform):
    """Extract UV mesh from data.

    Attributes:
        uv: The attribute defining the UV coordinates. If None, automatically deetect the UV
            attribute from the data.
    """

    uv: AttributeLike | None = None

Affine dataclass

Bases: Transform

Apply affine transformation to data.

Attributes:

Name Type Description
matrix ArrayLike

The 4x4 affine matrix to apply.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class Affine(Transform):
    """Apply affine transformation to data.

    Attributes:
        matrix: The 4x4 affine matrix to apply.
    """

    matrix: npt.ArrayLike

PrincipalAxes dataclass

Bases: Transform

Align PCA principal directions of vertex positions with a target orthonormal frame.

Covariance is computed from the current data-frame vertex positions. Principal axes are ordered by descending eigenvalue (largest variance first). The rotation and translation match those directions to the columns of frame: column 0 is the direction for the largest-variance axis, column 1 for the second, column 2 for the third.

The resulting affine is pre-composed with any prior global transform on the layer, so earlier Affine transforms (translate / rotate / scale) are preserved and applied before this PCA-based alignment.

Attributes:

Name Type Description
frame ArrayLike

3x3 matrix whose columns are the target orthonormal axes (see above).

orthonormalize_frame bool

If True (default), orthonormalize frame with QR so mildly skewed inputs still yield a proper rotation.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class PrincipalAxes(Transform):
    """Align PCA principal directions of vertex positions with a target orthonormal frame.

    Covariance is computed from the current data-frame vertex positions. Principal
    axes are ordered by descending eigenvalue (largest variance first). The rotation and
    translation match those directions to the columns of ``frame``: column 0 is the
    direction for the largest-variance axis, column 1 for the second, column 2 for the third.

    The resulting affine is pre-composed with any prior global transform on the layer, so
    earlier ``Affine`` transforms (translate / rotate / scale) are preserved and applied
    before this PCA-based alignment.

    Attributes:
        frame: 3x3 matrix whose columns are the target orthonormal axes (see above).
        orthonormalize_frame: If True (default), orthonormalize ``frame`` with QR so mildly
            skewed inputs still yield a proper rotation.
    """

    frame: npt.ArrayLike = field(default_factory=lambda: np.eye(3))
    orthonormalize_frame: bool = True

Compute dataclass

Bases: Transform

Compute new attributes from the current data frame.

Attributes:

Name Type Description
x str | None

Extract the x coordinate as an attribute.

y str | None

Extract the y coordinate as an attribute.

z str | None

Extract the z coordinate as an attribute.

normal str | None

Compute the normal vector field as an attribute.

vertex_normal str | None

Compute the vertex normal vector field as an attribute.

facet_normal str | None

Compute the facet normal vector field as an attribute.

component str | None

Compute connected component ids.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True, kw_only=True)
class Compute(Transform):
    """Compute new attributes from the current data frame.

    Attributes:
        x: Extract the x coordinate as an attribute.
        y: Extract the y coordinate as an attribute.
        z: Extract the z coordinate as an attribute.
        normal: Compute the normal vector field as an attribute.
        vertex_normal: Compute the vertex normal vector field as an attribute.
        facet_normal: Compute the facet normal vector field as an attribute.
        component: Compute connected component ids.
    """

    x: str | None = None
    y: str | None = None
    z: str | None = None
    normal: str | None = None
    vertex_normal: str | None = None
    facet_normal: str | None = None
    component: str | None = None

Explode dataclass

Bases: Transform

Explode data into multiple pieces.

Attributes:

Name Type Description
pieces AttributeLike

The attribute defining the pieces.

magnitude float

The magnitude of the displacement.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class Explode(Transform):
    """Explode data into multiple pieces.

    Attributes:
        pieces: The attribute defining the pieces.
        magnitude: The magnitude of the displacement.
    """

    pieces: AttributeLike
    magnitude: float = 1

Norm dataclass

Bases: Transform

Compute the row-wise norm of a given vector attribute.

Attributes:

Name Type Description
data AttributeLike

The vector attribute to compute the norm on.

norm_attr_name str

The name of the output norm attribute.

order int

The order of the norm. Default is 2, which is the L2 norm.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class Norm(Transform):
    """Compute the row-wise norm of a given vector attribute.

    Attributes:
        data: The vector attribute to compute the norm on.
        norm_attr_name: The name of the output norm attribute.
        order: The order of the norm. Default is 2, which is the L2 norm.
    """

    data: AttributeLike
    norm_attr_name: str
    order: int = 2

Boundary dataclass

Bases: Transform

Compute the boundary of a mesh.

Attributes:

Name Type Description
attributes list[str]

The attributes to take into account when computing the boundary. i.e. discontinuities in these attributes will be considered as boundaries.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True)
class Boundary(Transform):
    """Compute the boundary of a mesh.

    Attributes:
        attributes: The attributes to take into account when computing the boundary.
            i.e. discontinuities in these attributes will be considered as boundaries.
    """

    attributes: list[str] = field(default_factory=list)

Streamline dataclass

Bases: Transform

Replace the mesh with surface streamlines traced from a per-facet vector or cross field.

The output is a vertex-only mesh whose 2-vertex polygonal faces encode line segments along the streamlines, suitable for the curve mark. A per-vertex int32 attribute named by id_attr_name identifies which streamline each point belongs to.

Attributes:

Name Type Description
vec_field AttributeLike

The per-facet vector field attribute name. Vertex- or corner- domain attributes are averaged to per-facet first.

n int

Number of blue-noise seed faces to sample. Default 50.

cross_field bool

Treat the field as 4-RoSy cross field. Default True.

length float | None

Maximum object-space length per half-trace (measured on the data-frame mesh, before any layer-level affine transforms). Tracing stops once the accumulated length exceeds this value. None means no limit (trace until mesh boundary). Default None.

seed int

RNG seed passed to blue-noise sampling. Default 0.

min_length int

Discard streamlines shorter than this many sample points. Default 3.

id_attr_name str

Name of the per-vertex streamline-id attribute on the output mesh. Default _hakowan_streamline_id.

Source code in hakowan/grammar/transform/transform.py
@dataclass(slots=True, kw_only=True)
class Streamline(Transform):
    """Replace the mesh with surface streamlines traced from a per-facet vector or
    cross field.

    The output is a vertex-only mesh whose 2-vertex polygonal faces encode line
    segments along the streamlines, suitable for the ``curve`` mark.  A per-vertex
    ``int32`` attribute named by ``id_attr_name`` identifies which streamline
    each point belongs to.

    Attributes:
        vec_field: The per-facet vector field attribute name.  Vertex- or corner-
            domain attributes are averaged to per-facet first.
        n: Number of blue-noise seed faces to sample.  Default 50.
        cross_field: Treat the field as 4-RoSy cross field.  Default True.
        length: Maximum object-space length per half-trace (measured on the
            data-frame mesh, before any layer-level affine transforms).  Tracing
            stops once the accumulated length exceeds this value.  ``None`` means
            no limit (trace until mesh boundary).  Default None.
        seed: RNG seed passed to blue-noise sampling.  Default 0.
        min_length: Discard streamlines shorter than this many sample points.
            Default 3.
        id_attr_name: Name of the per-vertex streamline-id attribute on the
            output mesh.  Default ``_hakowan_streamline_id``.
    """

    vec_field: AttributeLike
    n: int = 50
    cross_field: bool = True
    length: float | None = None
    seed: int = 0
    min_length: int = 3
    id_attr_name: str = "_hakowan_streamline_id"