* #788 Add __repr__ and __str__ methods to Matrix * Shortened code with join & improved test * Renamed variable and fixed use of join * Replaced __getitem__ with transposed_list * Optimized repr and removed str methods * Cleanup comment * Increased geom.py code coverage to near 100% * Reformatted with black 19.10b0 defaults * Remove the typing import
This commit is contained in:
@ -362,6 +362,14 @@ class Matrix:
|
|||||||
else:
|
else:
|
||||||
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""
|
||||||
|
Generate a valid python expression representing this Matrix
|
||||||
|
"""
|
||||||
|
matrix_transposed = self.transposed_list()
|
||||||
|
matrix_str = ",\n ".join(str(matrix_transposed[i::4]) for i in range(4))
|
||||||
|
return f"Matrix([{matrix_str}])"
|
||||||
|
|
||||||
|
|
||||||
class Plane(object):
|
class Plane(object):
|
||||||
"""A 2D coordinate system in space
|
"""A 2D coordinate system in space
|
||||||
|
@ -47,6 +47,11 @@ class TestCadObjects(BaseTest):
|
|||||||
v9.z = 3.0
|
v9.z = 3.0
|
||||||
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Vector("vector")
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Vector(1, 2, 3, 4)
|
||||||
|
|
||||||
def testVertex(self):
|
def testVertex(self):
|
||||||
"""
|
"""
|
||||||
Tests basic vertex functions
|
Tests basic vertex functions
|
||||||
@ -66,6 +71,46 @@ class TestCadObjects(BaseTest):
|
|||||||
# OCC uses some approximations
|
# OCC uses some approximations
|
||||||
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
||||||
|
|
||||||
|
# Test adding to an existing bounding box
|
||||||
|
v0 = Vertex.makeVertex(0, 0, 0)
|
||||||
|
bb2 = v0.BoundingBox().add(v.BoundingBox())
|
||||||
|
|
||||||
|
bb3 = bb1.add(bb2)
|
||||||
|
self.assertTupleAlmostEquals((2, 2, 2), (bb3.xlen, bb3.ylen, bb3.zlen), 7)
|
||||||
|
|
||||||
|
bb3 = bb2.add((3, 3, 3))
|
||||||
|
self.assertTupleAlmostEquals((3, 3, 3), (bb3.xlen, bb3.ylen, bb3.zlen), 7)
|
||||||
|
|
||||||
|
bb3 = bb2.add(Vector(3, 3, 3))
|
||||||
|
self.assertTupleAlmostEquals((3, 3, 3), (bb3.xlen, bb3.ylen, bb3.zlen), 7)
|
||||||
|
|
||||||
|
# Test 2D bounding boxes
|
||||||
|
bb1 = (
|
||||||
|
Vertex.makeVertex(1, 1, 0)
|
||||||
|
.BoundingBox()
|
||||||
|
.add(Vertex.makeVertex(2, 2, 0).BoundingBox())
|
||||||
|
)
|
||||||
|
bb2 = (
|
||||||
|
Vertex.makeVertex(0, 0, 0)
|
||||||
|
.BoundingBox()
|
||||||
|
.add(Vertex.makeVertex(3, 3, 0).BoundingBox())
|
||||||
|
)
|
||||||
|
bb3 = (
|
||||||
|
Vertex.makeVertex(0, 0, 0)
|
||||||
|
.BoundingBox()
|
||||||
|
.add(Vertex.makeVertex(1.5, 1.5, 0).BoundingBox())
|
||||||
|
)
|
||||||
|
# Test that bb2 contains bb1
|
||||||
|
self.assertEqual(bb2, BoundBox.findOutsideBox2D(bb1, bb2))
|
||||||
|
self.assertEqual(bb2, BoundBox.findOutsideBox2D(bb2, bb1))
|
||||||
|
# Test that neither bounding box contains the other
|
||||||
|
self.assertIsNone(BoundBox.findOutsideBox2D(bb1, bb3))
|
||||||
|
|
||||||
|
# Test creation of a bounding box from a shape - note the low accuracy comparison
|
||||||
|
# as the box is a little larger than the shape
|
||||||
|
bb1 = BoundBox._fromTopoDS(Solid.makeCylinder(1, 1).wrapped, optimal=False)
|
||||||
|
self.assertTupleAlmostEquals((2, 2, 1), (bb1.xlen, bb1.ylen, bb1.zlen), 1)
|
||||||
|
|
||||||
def testEdgeWrapperCenter(self):
|
def testEdgeWrapperCenter(self):
|
||||||
e = self._make_circle()
|
e = self._make_circle()
|
||||||
|
|
||||||
@ -276,6 +321,20 @@ class TestCadObjects(BaseTest):
|
|||||||
point.toTuple(), (59 / 7, 55 / 7, 51 / 7), decimal_places
|
point.toTuple(), (59 / 7, 55 / 7, 51 / 7), decimal_places
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def testVectorNotImplemented(self):
|
||||||
|
v = Vector(1, 2, 3)
|
||||||
|
with self.assertRaises(NotImplementedError):
|
||||||
|
v.distanceToLine()
|
||||||
|
with self.assertRaises(NotImplementedError):
|
||||||
|
v.projectToLine()
|
||||||
|
with self.assertRaises(NotImplementedError):
|
||||||
|
v.distanceToPlane()
|
||||||
|
|
||||||
|
def testVectorSpecialMethods(self):
|
||||||
|
v = Vector(1, 2, 3)
|
||||||
|
self.assertEqual(repr(v), "Vector: (1.0, 2.0, 3.0)")
|
||||||
|
self.assertEqual(str(v), "Vector: (1.0, 2.0, 3.0)")
|
||||||
|
|
||||||
def testMatrixCreationAndAccess(self):
|
def testMatrixCreationAndAccess(self):
|
||||||
def matrix_vals(m):
|
def matrix_vals(m):
|
||||||
return [[m[r, c] for c in range(4)] for r in range(4)]
|
return [[m[r, c] for c in range(4)] for r in range(4)]
|
||||||
@ -320,7 +379,9 @@ class TestCadObjects(BaseTest):
|
|||||||
]
|
]
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Matrix(invalid)
|
Matrix(invalid)
|
||||||
|
# Test input with invalid type
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Matrix("invalid")
|
||||||
# Test input with invalid size / nested types
|
# Test input with invalid size / nested types
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
||||||
@ -340,6 +401,77 @@ class TestCadObjects(BaseTest):
|
|||||||
with self.assertRaises(IndexError):
|
with self.assertRaises(IndexError):
|
||||||
m["ab"]
|
m["ab"]
|
||||||
|
|
||||||
|
# test __repr__ methods
|
||||||
|
m = Matrix(vals4x4)
|
||||||
|
mRepr = "Matrix([[1.0, 0.0, 0.0, 1.0],\n [0.0, 1.0, 0.0, 2.0],\n [0.0, 0.0, 1.0, 3.0],\n [0.0, 0.0, 0.0, 1.0]])"
|
||||||
|
self.assertEqual(repr(m), mRepr)
|
||||||
|
self.assertEqual(str(eval(repr(m))), mRepr)
|
||||||
|
|
||||||
|
def testMatrixFunctionality(self):
|
||||||
|
# Test rotate methods
|
||||||
|
def matrix_almost_equal(m, target_matrix):
|
||||||
|
for r, row in enumerate(target_matrix):
|
||||||
|
for c, target_value in enumerate(row):
|
||||||
|
self.assertAlmostEqual(m[r, c], target_value)
|
||||||
|
|
||||||
|
root_3_over_2 = math.sqrt(3) / 2
|
||||||
|
m_rotate_x_30 = [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, root_3_over_2, -1 / 2, 0],
|
||||||
|
[0, 1 / 2, root_3_over_2, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
]
|
||||||
|
mx = Matrix()
|
||||||
|
mx.rotateX(30 * DEG2RAD)
|
||||||
|
matrix_almost_equal(mx, m_rotate_x_30)
|
||||||
|
|
||||||
|
m_rotate_y_30 = [
|
||||||
|
[root_3_over_2, 0, 1 / 2, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[-1 / 2, 0, root_3_over_2, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
]
|
||||||
|
my = Matrix()
|
||||||
|
my.rotateY(30 * DEG2RAD)
|
||||||
|
matrix_almost_equal(my, m_rotate_y_30)
|
||||||
|
|
||||||
|
m_rotate_z_30 = [
|
||||||
|
[root_3_over_2, -1 / 2, 0, 0],
|
||||||
|
[1 / 2, root_3_over_2, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
]
|
||||||
|
mz = Matrix()
|
||||||
|
mz.rotateZ(30 * DEG2RAD)
|
||||||
|
matrix_almost_equal(mz, m_rotate_z_30)
|
||||||
|
|
||||||
|
# Test matrix multipy vector
|
||||||
|
v = Vector(1, 0, 0)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
mz.multiply(v).toTuple(), (root_3_over_2, 1 / 2, 0), 7
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test matrix multipy matrix
|
||||||
|
m_rotate_xy_30 = [
|
||||||
|
[root_3_over_2, 0, 1 / 2, 0],
|
||||||
|
[1 / 4, root_3_over_2, -root_3_over_2 / 2, 0],
|
||||||
|
[-root_3_over_2 / 2, 1 / 2, 3 / 4, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
]
|
||||||
|
mxy = mx.multiply(my)
|
||||||
|
matrix_almost_equal(mxy, m_rotate_xy_30)
|
||||||
|
|
||||||
|
# Test matrix inverse
|
||||||
|
vals4x4 = [[1, 2, 3, 4], [5, 1, 6, 7], [8, 9, 1, 10], [0, 0, 0, 1]]
|
||||||
|
vals4x4_invert = [
|
||||||
|
[-53 / 144, 25 / 144, 1 / 16, -53 / 144],
|
||||||
|
[43 / 144, -23 / 144, 1 / 16, -101 / 144],
|
||||||
|
[37 / 144, 7 / 144, -1 / 16, -107 / 144],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
]
|
||||||
|
m = Matrix(vals4x4).inverse()
|
||||||
|
matrix_almost_equal(m, vals4x4_invert)
|
||||||
|
|
||||||
def testTranslate(self):
|
def testTranslate(self):
|
||||||
e = Edge.makeCircle(2, (1, 2, 3))
|
e = Edge.makeCircle(2, (1, 2, 3))
|
||||||
e2 = e.translate(Vector(0, 0, 1))
|
e2 = e.translate(Vector(0, 0, 1))
|
||||||
@ -394,6 +526,55 @@ class TestCadObjects(BaseTest):
|
|||||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def testInvalidPlane(self):
|
||||||
|
# Test plane creation error handling
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Plane.named("XX", (0, 0, 0))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(0, 0, 0), normal=(0, 1, 1))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 0))
|
||||||
|
|
||||||
|
def testPlaneMethods(self):
|
||||||
|
# Test error checking
|
||||||
|
p = Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 0))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
p.toLocalCoords("box")
|
||||||
|
with self.assertRaises(NotImplementedError):
|
||||||
|
p.mirrorInPlane([Solid.makeBox(1, 1, 1)], "Z")
|
||||||
|
|
||||||
|
# Test translation to local coordinates
|
||||||
|
local_box = Workplane(p.toLocalCoords(Solid.makeBox(1, 1, 1)))
|
||||||
|
local_box_vertices = [(v.X, v.Y, v.Z) for v in local_box.vertices().vals()]
|
||||||
|
target_vertices = [
|
||||||
|
(0, -1, 0),
|
||||||
|
(0, 0, 0),
|
||||||
|
(0, -1, 1),
|
||||||
|
(0, 0, 1),
|
||||||
|
(1, -1, 0),
|
||||||
|
(1, 0, 0),
|
||||||
|
(1, -1, 1),
|
||||||
|
(1, 0, 1),
|
||||||
|
]
|
||||||
|
for i, target_point in enumerate(target_vertices):
|
||||||
|
self.assertTupleAlmostEquals(target_point, local_box_vertices[i], 7)
|
||||||
|
|
||||||
|
# Test mirrorInPlane
|
||||||
|
mirror_box = Workplane(p.mirrorInPlane([Solid.makeBox(1, 1, 1)], "Y")[0])
|
||||||
|
mirror_box_vertices = [(v.X, v.Y, v.Z) for v in mirror_box.vertices().vals()]
|
||||||
|
target_vertices = [
|
||||||
|
(0, 0, 1),
|
||||||
|
(0, 0, 0),
|
||||||
|
(0, -1, 1),
|
||||||
|
(0, -1, 0),
|
||||||
|
(-1, 0, 1),
|
||||||
|
(-1, 0, 0),
|
||||||
|
(-1, -1, 1),
|
||||||
|
(-1, -1, 0),
|
||||||
|
]
|
||||||
|
for i, target_point in enumerate(target_vertices):
|
||||||
|
self.assertTupleAlmostEquals(target_point, mirror_box_vertices[i], 7)
|
||||||
|
|
||||||
def testLocation(self):
|
def testLocation(self):
|
||||||
|
|
||||||
# Vector
|
# Vector
|
||||||
@ -418,6 +599,17 @@ class TestCadObjects(BaseTest):
|
|||||||
== loc3.wrapped.Transformation().TranslationPart().Z()
|
== loc3.wrapped.Transformation().TranslationPart().Z()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Test creation from the OCP.gp.gp_Trsf object
|
||||||
|
loc4 = Location(gp_Trsf())
|
||||||
|
self.assertTupleAlmostEquals(loc4.toTuple()[0], (0, 0, 0), 7)
|
||||||
|
self.assertTupleAlmostEquals(loc4.toTuple()[1], (0, 0, 0), 7)
|
||||||
|
|
||||||
|
# Test error handling on creation
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Location((0, 0, 1))
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Location("xy_plane")
|
||||||
|
|
||||||
def testEdgeWrapperRadius(self):
|
def testEdgeWrapperRadius(self):
|
||||||
|
|
||||||
# get a radius from a simple circle
|
# get a radius from a simple circle
|
||||||
|
Reference in New Issue
Block a user