[apply_transform] include scale in transform

This commit is contained in:
jan 2026-03-09 02:34:11 -07:00
commit da20922224
3 changed files with 24 additions and 12 deletions

View file

@ -466,9 +466,11 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
memo = {}
if transform is None or transform is True:
transform = numpy.zeros(4)
transform = numpy.array([0, 0, 0, 0, 1], dtype=float)
elif transform is not False:
transform = numpy.asarray(transform, dtype=float)
if transform.size == 4:
transform = numpy.append(transform, 1.0)
original_pattern = pattern

View file

@ -191,10 +191,11 @@ class Ref(
xys = self.offset[None, :]
if self.repetition is not None:
xys = xys + self.repetition.displacements
transforms = numpy.empty((xys.shape[0], 4))
transforms = numpy.empty((xys.shape[0], 5))
transforms[:, :2] = xys
transforms[:, 2] = self.rotation
transforms[:, 3] = self.mirrored
transforms[:, 4] = self.scale
return transforms
def get_bounds_single(

View file

@ -87,33 +87,41 @@ def apply_transforms(
Apply a set of transforms (`outer`) to a second set (`inner`).
This is used to find the "absolute" transform for nested `Ref`s.
The two transforms should be of shape Ox4 and Ix4.
Rows should be of the form `(x_offset, y_offset, rotation_ccw_rad, mirror_across_x)`.
The output will be of the form (O*I)x4 (if `tensor=False`) or OxIx4 (`tensor=True`).
The two transforms should be of shape Ox5 and Ix5.
Rows should be of the form `(x_offset, y_offset, rotation_ccw_rad, mirror_across_x, scale)`.
The output will be of the form (O*I)x5 (if `tensor=False`) or OxIx5 (`tensor=True`).
Args:
outer: Transforms for the container refs. Shape Ox4.
inner: Transforms for the contained refs. Shape Ix4.
tensor: If `True`, an OxIx4 array is returned, with `result[oo, ii, :]` corresponding
outer: Transforms for the container refs. Shape Ox5.
inner: Transforms for the contained refs. Shape Ix5.
tensor: If `True`, an OxIx5 array is returned, with `result[oo, ii, :]` corresponding
to the `oo`th `outer` transform applied to the `ii`th inner transform.
If `False` (default), this is concatenated into `(O*I)x4` to allow simple
If `False` (default), this is concatenated into `(O*I)x5` to allow simple
chaining into additional `apply_transforms()` calls.
Returns:
OxIx4 or (O*I)x4 array. Final dimension is
`(total_x, total_y, total_rotation_ccw_rad, net_mirrored_x)`.
OxIx5 or (O*I)x5 array. Final dimension is
`(total_x, total_y, total_rotation_ccw_rad, net_mirrored_x, total_scale)`.
"""
outer = numpy.atleast_2d(outer).astype(float, copy=False)
inner = numpy.atleast_2d(inner).astype(float, copy=False)
if outer.shape[1] == 4:
outer = numpy.pad(outer, ((0, 0), (0, 1)), constant_values=1.0)
if inner.shape[1] == 4:
inner = numpy.pad(inner, ((0, 0), (0, 1)), constant_values=1.0)
# If mirrored, flip y's
xy_mir = numpy.tile(inner[:, :2], (outer.shape[0], 1, 1)) # dims are outer, inner, xyrm
xy_mir[outer[:, 3].astype(bool), :, 1] *= -1
# Apply outer scale to inner offset
xy_mir *= outer[:, None, 4, None]
rot_mats = [rotation_matrix_2d(angle) for angle in outer[:, 2]]
xy = numpy.einsum('ort,oit->oir', rot_mats, xy_mir)
tot = numpy.empty((outer.shape[0], inner.shape[0], 4))
tot = numpy.empty((outer.shape[0], inner.shape[0], 5))
tot[:, :, :2] = outer[:, None, :2] + xy
# If mirrored, flip inner rotation
@ -122,6 +130,7 @@ def apply_transforms(
tot[:, :, 2] = rotations % (2 * pi)
tot[:, :, 3] = (outer[:, None, 3] + inner[None, :, 3]) % 2 # net mirrored
tot[:, :, 4] = outer[:, None, 4] * inner[None, :, 4] # net scale
if tensor:
return tot