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))
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, )
def __init__(self, target): self.target = target self._transform = CompositeTransform() self._flip_faces = False