def test_convert_units():
    transform = CompositeTransform()
    transform.convert_units("m", "cm")

    cube_v = create_default_cube_verts()

    # Confidence check.
    np.testing.assert_array_equal(cube_v[0], [1.0, 0.0, 0.0])
    np.testing.assert_array_equal(cube_v[6], [5.0, 4.0, 4.0])

    transformed_cube_v = transform(cube_v)

    np.testing.assert_array_equal(transformed_cube_v[0], [100.0, 0.0, 0.0])
    np.testing.assert_array_equal(transformed_cube_v[6], [500.0, 400.0, 400.0])
def test_reverse_transforms():
    transforms = [CompositeTransform() for _ in range(5)]

    transforms[1].translate(np.array([8.0, 6.0, 7.0]))

    transforms[2].uniform_scale(10.0)

    transforms[3].translate(np.array([8.0, 6.0, 7.0]))
    transforms[3].uniform_scale(10.0)

    transforms[4].uniform_scale(10.0)
    transforms[4].translate(np.array([8.0, 6.0, 7.0]))

    for transform in transforms:
        cube_v = create_default_cube_verts()

        # Confidence check.
        np.testing.assert_array_equal(cube_v[0], [1.0, 0.0, 0.0])
        np.testing.assert_array_equal(cube_v[6], [5.0, 4.0, 4.0])

        transformed = transform(cube_v)

        untransformed_v = transform(transformed, reverse=True)

        np.testing.assert_array_almost_equal(untransformed_v[0], [1.0, 0.0, 0.0])
        np.testing.assert_array_almost_equal(untransformed_v[6], [5.0, 4.0, 4.0])
def test_reorient():
    # TODO We should also test a non-axis-aligned up and look.

    transform = CompositeTransform()
    transform.reorient(up=vg.basis.y, look=vg.basis.neg_x)

    cube_v = create_default_cube_verts()

    # Confidence check.
    np.testing.assert_array_equal(cube_v[0], [1.0, 0.0, 0.0])
    np.testing.assert_array_equal(cube_v[6], [5.0, 4.0, 4.0])

    transformed_cube_v = transform(cube_v)

    np.testing.assert_array_equal(transformed_cube_v[0], [0.0, 0.0, -1.0])
    np.testing.assert_array_equal(transformed_cube_v[6], [4, 4.0, -5.0])
def test_scale_then_translate():
    transform = CompositeTransform()
    transform.uniform_scale(10.0)
    transform.translate(np.array([8.0, 6.0, 7.0]))

    cube_v = create_default_cube_verts()

    # Confidence check.
    np.testing.assert_array_equal(cube_v[0], [1.0, 0.0, 0.0])
    np.testing.assert_array_equal(cube_v[6], [5.0, 4.0, 4.0])

    transformed_cube_v = transform(cube_v)

    np.testing.assert_array_equal(transformed_cube_v[0], [18.0, 6.0, 7.0])
    np.testing.assert_array_equal(transformed_cube_v[6], [58.0, 46.0, 47.0])
def test_rotate_then_translate():
    transform = CompositeTransform()
    transform.rotate(np.array([1.0, 2.0, 3.0]))
    transform.translate(np.array([3.0, 2.0, 1.0]))

    v = np.array([1.0, 0.0, 0.0]).reshape(-1, 3)

    # Forward.
    np.testing.assert_allclose(
        np.array([2.30507944, 1.80799303, 1.69297817]).reshape(-1, 3), transform(v)
    )
    # Reverse.
    np.testing.assert_allclose(
        np.array([1.08087689, -1.45082159, -2.3930779]).reshape(-1, 3),
        transform(v, reverse=True),
    )
def test_flip_error():
    transform = CompositeTransform()
    with pytest.raises(ValueError, match=r"Expected dim to be 0, 1, or 2"):
        transform.flip(-1)
def test_forward_reverse_equivalence():
    transform = CompositeTransform()
    transform.rotate(np.array([1.0, 2.0, 3.0]))
    transform.translate(np.array([3.0, 2.0, 1.0]))
    transform.uniform_scale(10.0)
    transform.rotate(np.array([7.0, 13.0, 5.0]))

    forward = transform.transform_matrix_for()
    reverse = transform.transform_matrix_for(reverse=True)
    np.testing.assert_allclose(reverse, np.linalg.inv(forward))

    forward = transform.transform_matrix_for(from_range=(0, 2))
    reverse = transform.transform_matrix_for(from_range=(0, 2), reverse=True)
    np.testing.assert_allclose(reverse, np.linalg.inv(forward))
Example #8
0
class Transform:
    """
    Encapsulate a composite transform operation.

    Invoke `.end()` to apply the transform operation and create a mesh with
    transformed vertices and faces.

    Args:
        target (lacecore.Mesh): The mesh on which to operate.

    See also:
        https://polliwog.readthedocs.io/en/latest/#polliwog.CompositeTransform
    """
    def __init__(self, target):
        self.target = target
        self._transform = CompositeTransform()
        self._flip_faces = False

    def append_transform(self, forward, reverse=None):
        """
        Append an arbitrary transformation, defined by 4x4 forward and reverse
        matrices.

        Returns:
            self
        """
        self._transform.append_transform(forward=forward, reverse=reverse)
        return self

    def uniform_scale(self, factor):
        """
        Scale by the given factor.

        Args:
            factor (float): The scale factor.

        Returns:
            self
        """
        self._transform.uniform_scale(factor=factor)
        return self

    def non_uniform_scale(self, x_factor, y_factor, z_factor):
        """
        Scale along each axis by the given factors.

        Args:
            x_factor (flot): The scale factor along the `x` axis.
            y_factor (flot): The scale factor along the `y` axis.
            z_factor (flot): The scale factor along the `z` axis.

        Returns:
            self
        """
        self._transform.non_uniform_scale(x_factor, y_factor, z_factor)
        return self

    def convert_units(self, from_units, to_units):
        """
        Convert the mesh from one set of units to another.

        Supports the length units from Ounce:
        https://github.com/lace/ounce/blob/master/ounce/core.py#L26

        Returns:
            self
        """
        self._transform.convert_units(from_units=from_units, to_units=to_units)
        return self

    def translate(self, translation):
        """
        Translate by the vector provided.

        Args:
            vector (np.arraylike): A 3x1 vector.

        Returns:
            self
        """
        self._transform.translate(translation=translation)
        return self

    def reorient(self, up, look):
        """
        Reorient using up and look.

        Returns:
            self
        """
        self._transform.reorient(up=up, look=look)
        return self

    def rotate(self, rotation):
        """
        Rotate by the given 3x3 rotation matrix or a Rodrigues vector.

        Returns:
            self
        """
        self._transform.rotate(rotation=rotation)
        return self

    def flip_faces(self):
        """
        Flip the orientation of the faces.

        Returns:
            self
        """
        self._flip_faces = not self._flip_faces
        return self

    def flip(self, dim, preserve_vertex_centroid=False):
        """
        Flip about the given axis.

        Args:
            dim (int): The axis to flip around: 0 for `x`, 1 for `y`, 2 for `z`.
            preserve_vertex_centroid (bool): When `True`, translate after
                flipping to preserve the original vertex centroid.

        Returns:
            self
        """
        if dim not in (0, 1, 2):
            raise ValueError("Expected dim to be 0, 1, or 2")

        self._transform.flip(dim=dim)
        self.flip_faces()

        if preserve_vertex_centroid:
            translation = np.zeros(3)
            # `2 * vertex_centroid[dim]` takes it all the way back to the other
            # side of the axis.
            translation[dim] = 2 * self.target.vertex_centroid[dim]
            self._transform.translate(translation=translation)

        return self

    def end(self, reverse=False):
        """
        Apply the requested transformation and return a new mesh.

        Args:
            reverse (bool): When `True` applies the selected transformations
                in reverse.

        Returns:
            lacecore.Mesh: The transformed mesh.
        """
        from .._mesh import Mesh  # Avoid circular import.

        return Mesh(
            v=self._transform(self.target.v),
            f=flip_faces(self.target.f) if self._flip_faces else self.target.f,
            face_groups=self.target.face_groups,
        )
Example #9
0
 def __init__(self, target):
     self.target = target
     self._transform = CompositeTransform()
     self._flip_faces = False