Common unittests, removed VertexList

This commit is contained in:
Roger Maitland 2022-07-27 14:25:57 -04:00
parent 81214ee86d
commit f82b94121b
8 changed files with 390 additions and 103 deletions

View file

@ -122,3 +122,4 @@ a fully custom selection:
One can sort by all of the following attributes: One can sort by all of the following attributes:
.. autoclass:: build_common.SortBy .. autoclass:: build_common.SortBy
:noindex:

View file

@ -168,6 +168,57 @@ extrudes these pending faces into `Solid` objects. Likewise, `Loft` will take al
Normally the user will not need to interact directly with pending objects. Normally the user will not need to interact directly with pending objects.
***********
Shape Lists
***********
The builders include methods to extract Edges, Faces, Solids, or Vertices from the objects
they are building. These methods are as follows:
+-------------+---------+---------+----------+------------+
| Size | Edges | Faces | Solids | Vertices |
+=============+=========+=========+==========+============+
| BuildLine | edges() | | | vertices() |
+-------------+---------+---------+----------+------------+
| BuildSketch | edges() | faces() | | vertices() |
+-------------+---------+---------+----------+------------+
| BuildPart | edges() | edges() | solids() | vertices() |
+-------------+---------+---------+----------+------------+
All of these methods return objects in a subclass of `list`, a `ShapeList` with some custom
filtering and sorting methods predefined described as follows.
.. autoclass:: build_common.ShapeList
:members:
:exclude-members: axis_map
The filter and sort parameters use the following Enums (numeric values are meaningless):
.. autoclass:: build_common.Axis
:members:
.. autoclass:: build_common.SortBy
:members:
.. autoclass:: build_common.Type
:members:
It is important to note that standard list methods such as `sorted` or `filtered` can
be used to easily build complex selectors beyond what is available with the predefined
sorts and filters. Here is an example of a custom filters:
.. code-block:: python
with BuildSketch() as din:
...
outside_vertices = filter(
lambda v: (v.Y == 0.0 or v.Y == height)
and -overall_width / 2 < v.X < overall_width / 2,
din.vertices(),
)
************************************* *************************************
Multiple Work Planes - BuildPart Only Multiple Work Planes - BuildPart Only
************************************* *************************************

View file

@ -19,7 +19,6 @@ __all__ = [
"Type", "Type",
"Rotation", "Rotation",
"ShapeList", "ShapeList",
"VertexList",
"Builder", "Builder",
"Add", "Add",
"BoundingBox", "BoundingBox",

View file

@ -57,23 +57,29 @@ Edge.__mod__ = __mod__custom
Wire.__matmul__ = __matmul__custom Wire.__matmul__ = __matmul__custom
Wire.__mod__ = __mod__custom Wire.__mod__ = __mod__custom
context_stack = [] # context_stack = []
# #
# ENUMs # ENUMs
# #
class Select(Enum): class Select(Enum):
"""Selector scope - all or last operation"""
ALL = auto() ALL = auto()
LAST = auto() LAST = auto()
class Kind(Enum): class Kind(Enum):
"""Offset corner transition"""
ARC = auto() ARC = auto()
INTERSECTION = auto() INTERSECTION = auto()
TANGENT = auto() TANGENT = auto()
class Keep(Enum): class Keep(Enum):
"""Split options"""
TOP = auto() TOP = auto()
BOTTOM = auto() BOTTOM = auto()
BOTH = auto() BOTH = auto()
@ -90,6 +96,8 @@ class Mode(Enum):
class Transition(Enum): class Transition(Enum):
"""Sweep discontinuity handling option"""
RIGHT = auto() RIGHT = auto()
ROUND = auto() ROUND = auto()
TRANSFORMED = auto() TRANSFORMED = auto()
@ -104,7 +112,7 @@ class FontStyle(Enum):
class Halign(Enum): class Halign(Enum):
"""Horizontal Alignment""" """Text Horizontal Alignment"""
CENTER = auto() CENTER = auto()
LEFT = auto() LEFT = auto()
@ -112,7 +120,7 @@ class Halign(Enum):
class Valign(Enum): class Valign(Enum):
"""Vertical Alignment""" """Text Vertical Alignment"""
CENTER = auto() CENTER = auto()
TOP = auto() TOP = auto()
@ -120,17 +128,23 @@ class Valign(Enum):
class Until(Enum): class Until(Enum):
"""Extude limit"""
NEXT = auto() NEXT = auto()
LAST = auto() LAST = auto()
class Axis(Enum): class Axis(Enum):
"""One of the three dimensions"""
X = auto() X = auto()
Y = auto() Y = auto()
Z = auto() Z = auto()
class SortBy(Enum): class SortBy(Enum):
"""Sorting criteria"""
X = auto() X = auto()
Y = auto() Y = auto()
Z = auto() Z = auto()
@ -142,6 +156,8 @@ class SortBy(Enum):
class Type(Enum): class Type(Enum):
"""CAD object type"""
PLANE = auto() PLANE = auto()
CYLINDER = auto() CYLINDER = auto()
CONE = auto() CONE = auto()
@ -164,6 +180,8 @@ class Type(Enum):
# DirectAPI Classes # DirectAPI Classes
# #
class Rotation(Location): class Rotation(Location):
"""Subclass of Location used only for object rotation"""
def __init__(self, about_x: float = 0, about_y: float = 0, about_z: float = 0): def __init__(self, about_x: float = 0, about_y: float = 0, about_z: float = 0):
self.about_x = about_x self.about_x = about_x
self.about_y = about_y self.about_y = about_y
@ -179,10 +197,13 @@ class Rotation(Location):
super().__init__(Location(rx * ry * rz).wrapped) super().__init__(Location(rx * ry * rz).wrapped)
#:TypeVar("RotationLike"): Three tuple of angles about x, y, z or Rotation
RotationLike = Union[tuple[float, float, float], Rotation] RotationLike = Union[tuple[float, float, float], Rotation]
class ShapeList(list): class ShapeList(list):
"""Subclass of list with custom filter and sort methods appropriate to CAD"""
axis_map = { axis_map = {
Axis.X: ((1, 0, 0), (-1, 0, 0)), Axis.X: ((1, 0, 0), (-1, 0, 0)),
Axis.Y: ((0, 1, 0), (0, -1, 0)), Axis.Y: ((0, 1, 0), (0, -1, 0)),
@ -190,9 +211,21 @@ class ShapeList(list):
} }
def __init_subclass__(cls) -> None: def __init_subclass__(cls) -> None:
return super().__init_subclass__() super().__init_subclass__()
def filter_by_axis(self, axis: Axis, tolerance=1e-5): def filter_by_axis(self, axis: Axis, tolerance=1e-5):
"""filter by axis
Filter objects of type planar Face or linear Edge by their normal or tangent
(respectively) and sort the results by the given axis.
Args:
axis (Axis): axis to filter and sort by
tolerance (_type_, optional): maximum deviation from axis. Defaults to 1e-5.
Returns:
ShapeList: sublist of Faces or Edges
"""
planar_faces = filter( planar_faces = filter(
lambda o: isinstance(o, Face) and o.geomType() == "PLANE", self lambda o: isinstance(o, Face) and o.geomType() == "PLANE", self
@ -218,12 +251,10 @@ class ShapeList(list):
list( list(
filter( filter(
lambda o: ( lambda o: (
o.tangentAt(None) - Vector(*ShapeList.axis_map[axis][0]) o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][0])
).Length ).Length
<= tolerance <= tolerance
or ( or (o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][1])).Length
o.tangentAt(o.Center()) - Vector(*ShapeList.axis_map[axis][1])
).Length
<= tolerance, <= tolerance,
linear_edges, linear_edges,
) )
@ -245,6 +276,20 @@ class ShapeList(list):
max: float, max: float,
inclusive: tuple[bool, bool] = (True, True), inclusive: tuple[bool, bool] = (True, True),
): ):
"""filter by position
Filter and sort objects by the position of their centers along given axis.
min and max values can be inclusive or exclusive depending on the inclusive tuple.
Args:
axis (Axis): axis to sort by
min (float): minimum value
max (float): maximum value
inclusive (tuple[bool, bool], optional): include min,max values. Defaults to (True, True).
Returns:
ShapeList: filtered object list
"""
if axis == Axis.X: if axis == Axis.X:
if inclusive == (True, True): if inclusive == (True, True):
result = filter(lambda o: min <= o.Center().x <= max, self) result = filter(lambda o: min <= o.Center().x <= max, self)
@ -254,6 +299,7 @@ class ShapeList(list):
result = filter(lambda o: min < o.Center().x <= max, self) result = filter(lambda o: min < o.Center().x <= max, self)
elif inclusive == (False, False): elif inclusive == (False, False):
result = filter(lambda o: min < o.Center().x < max, self) result = filter(lambda o: min < o.Center().x < max, self)
result = sorted(result, key=lambda obj: obj.Center().x)
elif axis == Axis.Y: elif axis == Axis.Y:
if inclusive == (True, True): if inclusive == (True, True):
result = filter(lambda o: min <= o.Center().y <= max, self) result = filter(lambda o: min <= o.Center().y <= max, self)
@ -263,6 +309,7 @@ class ShapeList(list):
result = filter(lambda o: min < o.Center().y <= max, self) result = filter(lambda o: min < o.Center().y <= max, self)
elif inclusive == (False, False): elif inclusive == (False, False):
result = filter(lambda o: min < o.Center().y < max, self) result = filter(lambda o: min < o.Center().y < max, self)
result = sorted(result, key=lambda obj: obj.Center().y)
elif axis == Axis.Z: elif axis == Axis.Z:
if inclusive == (True, True): if inclusive == (True, True):
result = filter(lambda o: min <= o.Center().z <= max, self) result = filter(lambda o: min <= o.Center().z <= max, self)
@ -272,6 +319,7 @@ class ShapeList(list):
result = filter(lambda o: min < o.Center().z <= max, self) result = filter(lambda o: min < o.Center().z <= max, self)
elif inclusive == (False, False): elif inclusive == (False, False):
result = filter(lambda o: min < o.Center().z < max, self) result = filter(lambda o: min < o.Center().z < max, self)
result = sorted(result, key=lambda obj: obj.Center().z)
return ShapeList(result) return ShapeList(result)
@ -279,135 +327,83 @@ class ShapeList(list):
self, self,
type: Type, type: Type,
): ):
"""filter by type
Filter the objects by the provided type. Note that not all types apply to all
objects.
Args:
type (Type): type to sort by
Returns:
ShapeList: filtered list of objects
"""
result = filter(lambda o: o.geomType() == type.name, self) result = filter(lambda o: o.geomType() == type.name, self)
return ShapeList(result) return ShapeList(result)
def sort_by(self, sort_by: SortBy = SortBy.Z, reverse: bool = False): def sort_by(self, sort_by: SortBy = SortBy.Z, reverse: bool = False):
"""sort by
Sort objects by provided criteria. Note that not all sort_by criteria apply to all
objects.
Args:
sort_by (SortBy, optional): sort criteria. Defaults to SortBy.Z.
reverse (bool, optional): flip order of sort. Defaults to False.
Returns:
ShapeList: sorted list of objects
"""
if sort_by == SortBy.X: if sort_by == SortBy.X:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Center().x, key=lambda obj: obj.Center().x,
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.Y: elif sort_by == SortBy.Y:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Center().y, key=lambda obj: obj.Center().y,
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.Z: elif sort_by == SortBy.Z:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Center().z, key=lambda obj: obj.Center().z,
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.LENGTH: elif sort_by == SortBy.LENGTH:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Length(), key=lambda obj: obj.Length(),
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.RADIUS: elif sort_by == SortBy.RADIUS:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.radius(), key=lambda obj: obj.radius(),
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.DISTANCE: elif sort_by == SortBy.DISTANCE:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Center().Length, key=lambda obj: obj.Center().Length,
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.AREA: elif sort_by == SortBy.AREA:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Area(), key=lambda obj: obj.Area(),
reverse=reverse, reverse=reverse,
) )
elif sort_by == SortBy.VOLUME: elif sort_by == SortBy.VOLUME:
obj = sorted( objects = sorted(
self, self,
key=lambda obj: obj.Volume(), key=lambda obj: obj.Volume(),
reverse=reverse, reverse=reverse,
) )
else:
raise ValueError(f"Unable to sort shapes by {sort_by}")
return ShapeList(obj) return ShapeList(objects)
class VertexList(list):
def __init_subclass__(cls) -> None:
return super().__init_subclass__()
def filter_by_position(
self,
axis: Axis,
min: float,
max: float,
inclusive: tuple[bool, bool] = (True, True),
):
if axis == Axis.X:
if inclusive == (True, True):
result = filter(lambda v: min <= v.X <= max, self)
elif inclusive == (True, False):
result = filter(lambda v: min <= v.X < max, self)
elif inclusive == (False, True):
result = filter(lambda v: min < v.X <= max, self)
elif inclusive == (False, False):
result = filter(lambda v: min < v.X < max, self)
elif axis == Axis.Y:
if inclusive == (True, True):
result = filter(lambda v: min <= v.Y <= max, self)
elif inclusive == (True, False):
result = filter(lambda v: min <= v.Y < max, self)
elif inclusive == (False, True):
result = filter(lambda v: min < v.Y <= max, self)
elif inclusive == (False, False):
result = filter(lambda v: min < v.Y < max, self)
elif axis == Axis.Z:
if inclusive == (True, True):
result = filter(lambda v: min <= v.Z <= max, self)
elif inclusive == (True, False):
result = filter(lambda v: min <= v.Z < max, self)
elif inclusive == (False, True):
result = filter(lambda v: min < v.Z <= max, self)
elif inclusive == (False, False):
result = filter(lambda v: min < v.Z < max, self)
return VertexList(result)
def sort_by(self, sort_by: SortBy = SortBy.Z, reverse: bool = False):
if sort_by == SortBy.X:
vertices = sorted(
self,
key=lambda obj: obj.X,
reverse=reverse,
)
elif sort_by == SortBy.Y:
vertices = sorted(
self,
key=lambda obj: obj.Y,
reverse=reverse,
)
elif sort_by == SortBy.Z:
vertices = sorted(
self,
key=lambda obj: obj.Z,
reverse=reverse,
)
elif sort_by == SortBy.DISTANCE:
vertices = sorted(
self,
key=lambda obj: obj.toVector().Length,
reverse=reverse,
)
else:
raise ValueError(f"Unable to sort vertices by {sort_by}")
return VertexList(vertices)
class Builder(ABC): class Builder(ABC):

View file

@ -74,7 +74,7 @@ class BuildLine(Builder):
self.line = [] self.line = []
super().__init__(mode) super().__init__(mode)
def vertices(self, select: Select = Select.ALL) -> VertexList[Vertex]: def vertices(self, select: Select = Select.ALL) -> ShapeList[Vertex]:
"""Return Vertices from Line """Return Vertices from Line
Return either all or the vertices created during the last operation. Return either all or the vertices created during the last operation.
@ -91,7 +91,7 @@ class BuildLine(Builder):
vertex_list.extend(edge.Vertices()) vertex_list.extend(edge.Vertices())
elif select == Select.LAST: elif select == Select.LAST:
vertex_list = self.last_vertices vertex_list = self.last_vertices
return VertexList(set(vertex_list)) return ShapeList(set(vertex_list))
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]: def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
"""Return Edges from Line """Return Edges from Line

View file

@ -111,7 +111,7 @@ class BuildPart(Builder):
self.last_solids = [] self.last_solids = []
super().__init__(mode) super().__init__(mode)
def vertices(self, select: Select = Select.ALL) -> VertexList[Vertex]: def vertices(self, select: Select = Select.ALL) -> ShapeList[Vertex]:
"""Return Vertices from Part """Return Vertices from Part
Return either all or the vertices created during the last operation. Return either all or the vertices created during the last operation.
@ -128,7 +128,7 @@ class BuildPart(Builder):
vertex_list.extend(edge.Vertices()) vertex_list.extend(edge.Vertices())
elif select == Select.LAST: elif select == Select.LAST:
vertex_list = self.last_vertices vertex_list = self.last_vertices
return VertexList(set(vertex_list)) return ShapeList(set(vertex_list))
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]: def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
"""Return Edges from Part """Return Edges from Part

View file

@ -77,13 +77,13 @@ class BuildSketch(Builder):
return self.sketch return self.sketch
def __init__(self, mode: Mode = Mode.ADD): def __init__(self, mode: Mode = Mode.ADD):
self.sketch = None self.sketch: Compound = None
self.pending_edges: list[Edge] = [] self.pending_edges: list[Edge] = []
self.locations: list[Location] = [Location(Vector())] self.locations: list[Location] = [Location(Vector())]
self.last_faces = [] self.last_faces = []
super().__init__(mode) super().__init__(mode)
def vertices(self, select: Select = Select.ALL) -> VertexList[Vertex]: def vertices(self, select: Select = Select.ALL) -> ShapeList[Vertex]:
"""Return Vertices from Sketch """Return Vertices from Sketch
Return either all or the vertices created during the last operation. Return either all or the vertices created during the last operation.
@ -100,7 +100,7 @@ class BuildSketch(Builder):
vertex_list.extend(edge.Vertices()) vertex_list.extend(edge.Vertices())
elif select == Select.LAST: elif select == Select.LAST:
vertex_list = self.last_vertices vertex_list = self.last_vertices
return VertexList(set(vertex_list)) return ShapeList(set(vertex_list))
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]: def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
"""Return Edges from Sketch """Return Edges from Sketch
@ -331,7 +331,6 @@ class FilletSketch(Compound):
super().__init__(new_sketch.wrapped) super().__init__(new_sketch.wrapped)
class Offset(Compound): class Offset(Compound):
"""Sketch Operation: Offset """Sketch Operation: Offset
@ -375,13 +374,11 @@ class Offset(Compound):
super().__init__(Compound.makeCompound(new_faces).wrapped) super().__init__(Compound.makeCompound(new_faces).wrapped)
# #
# Objects # Objects
# #
class Circle(Compound): class Circle(Compound):
"""Sketch Object: Circle """Sketch Object: Circle

243
tests/build_common_tests.py Normal file
View file

@ -0,0 +1,243 @@
"""
build123d common tests
name: build_common_tests.py
by: Gumyr
date: July 25th 2022
desc: Unit tests for the build123d common module
license:
Copyright 2022 Gumyr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import unittest
from cadquery import Edge, Wire, Solid
from build123d import *
def _assertTupleAlmostEquals(self, expected, actual, places, msg=None):
"""Check Tuples"""
for i, j in zip(actual, expected):
self.assertAlmostEqual(i, j, places, msg=msg)
unittest.TestCase.assertTupleAlmostEquals = _assertTupleAlmostEquals
class TestCommonOperations(unittest.TestCase):
"""Test custom operators"""
def test_matmul(self):
self.assertTupleAlmostEquals(
(Edge.makeLine((0, 0, 0), (1, 1, 1)) @ 0.5).toTuple(), (0.5, 0.5, 0.5), 5
)
def test_mod(self):
self.assertTupleAlmostEquals(
(Wire.makeCircle(10, (0, 0, 0), (0, 0, 1)) % 0.5).toTuple(), (0, -1, 0), 5
)
class TestRotation(unittest.TestCase):
"""Test the Rotation derived class of Location"""
def test_init(self):
thirty_by_three = Rotation(30, 30, 30)
box_vertices = Solid.makeBox(1, 1, 1).moved(thirty_by_three).Vertices()
self.assertTupleAlmostEquals(
box_vertices[0].toTuple(), (0.5, -0.4330127, 0.75), 5
)
self.assertTupleAlmostEquals(box_vertices[1].toTuple(), (0.0, 0.0, 0.0), 7)
self.assertTupleAlmostEquals(
box_vertices[2].toTuple(), (0.0669872, 0.191987, 1.399519), 5
)
self.assertTupleAlmostEquals(
box_vertices[3].toTuple(), (-0.4330127, 0.625, 0.6495190), 5
)
self.assertTupleAlmostEquals(
box_vertices[4].toTuple(), (1.25, 0.2165063, 0.625), 5
)
self.assertTupleAlmostEquals(
box_vertices[5].toTuple(), (0.75, 0.649519, -0.125), 5
)
self.assertTupleAlmostEquals(
box_vertices[6].toTuple(), (0.816987, 0.841506, 1.274519), 5
)
self.assertTupleAlmostEquals(
box_vertices[7].toTuple(), (0.3169872, 1.2745190, 0.52451905), 5
)
class TestShapeList(unittest.TestCase):
"""Test the ShapeList derived class"""
def test_filter_by_axis(self):
"""test the filter and sorting of Faces and Edges by axis"""
with BuildPart() as test:
Box(1, 1, 1)
for axis in [Axis.X, Axis.Y, Axis.Z]:
with self.subTest(axis=axis):
faces = test.faces().filter_by_axis(axis)
edges = test.edges().filter_by_axis(axis)
self.assertTrue(isinstance(faces, list))
self.assertTrue(isinstance(faces, ShapeList))
self.assertEqual(len(faces), 2)
self.assertTrue(isinstance(edges, list))
self.assertTrue(isinstance(edges, ShapeList))
self.assertEqual(len(edges), 4)
if axis == Axis.X:
self.assertLessEqual(faces[0].Center().x, faces[1].Center().x)
self.assertLessEqual(edges[0].Center().x, edges[-1].Center().x)
elif axis == Axis.Y:
self.assertLessEqual(faces[0].Center().y, faces[1].Center().y)
self.assertLessEqual(edges[0].Center().y, edges[-1].Center().y)
elif axis == Axis.Z:
self.assertLessEqual(faces[0].Center().z, faces[1].Center().z)
self.assertLessEqual(edges[0].Center().z, edges[-1].Center().z)
def test_filter_by_position(self):
"""test the filter and sorting of Faces and Edges by position"""
with BuildPart() as test:
Box(2, 2, 2)
for axis in [Axis.X, Axis.Y, Axis.Z]:
for inclusive in [
(True, True),
(True, False),
(False, True),
(False, False),
]:
with self.subTest(axis=axis, inclusive=inclusive):
faces = test.faces().filter_by_position(axis, -1, 1, inclusive)
edges = test.edges().filter_by_position(axis, -1, 1, inclusive)
self.assertTrue(isinstance(faces, list))
self.assertTrue(isinstance(faces, ShapeList))
self.assertEqual(len(faces), sum(inclusive) + 4)
self.assertTrue(isinstance(edges, list))
self.assertTrue(isinstance(edges, ShapeList))
self.assertEqual(len(edges), 4 * sum(inclusive) + 4)
if axis == Axis.X:
self.assertLessEqual(
faces[0].Center().x, faces[-1].Center().x
)
self.assertLessEqual(
edges[0].Center().x, edges[-1].Center().x
)
elif axis == Axis.Y:
self.assertLessEqual(
faces[0].Center().y, faces[-1].Center().y
)
self.assertLessEqual(
edges[0].Center().y, edges[-1].Center().y
)
elif axis == Axis.Z:
self.assertLessEqual(
faces[0].Center().z, faces[-1].Center().z
)
self.assertLessEqual(
edges[0].Center().z, edges[-1].Center().z
)
def test_filter_by_type(self):
"""test the filter and sorting by type"""
with BuildPart() as test:
Box(2, 2, 2)
objects = test.faces()
objects.extend(test.edges())
self.assertEqual(len(objects.filter_by_type(Type.PLANE)), 6)
self.assertEqual(len(objects.filter_by_type(Type.LINE)), 12)
def test_sort_by_type(self):
"""test sorting by different attributes"""
with self.subTest(sort_by=SortBy.AREA):
with BuildPart() as test:
Wedge(1, 1, 1, 0, 0, 0.5, 0.5)
faces = test.faces().sort_by(SortBy.AREA)
self.assertEqual(faces[0].Area(), 0.25)
self.assertEqual(faces[-1].Area(), 1)
with self.subTest(sort_by=SortBy.LENGTH):
with BuildPart() as test:
Wedge(1, 1, 1, 0, 0, 0.5, 0.5)
edges = test.edges().sort_by(SortBy.LENGTH)
self.assertEqual(edges[0].Length(), 0.5)
self.assertAlmostEqual(edges[-1].Length(), 1.2247448713915892, 7)
with self.subTest(sort_by=SortBy.DISTANCE):
with BuildPart() as test:
Box(1, 1, 1, centered=(False, True, True))
faces = test.faces().sort_by(SortBy.DISTANCE)
self.assertAlmostEqual(faces[0].Center().Length, 0, 7)
self.assertAlmostEqual(faces[-1].Center().Length, 1, 7)
with self.subTest(sort_by=SortBy.VOLUME):
with BuildPart() as test:
Box(1, 1, 1)
PushPoints((0, 0, 10))
Box(2, 2, 2)
solids = test.solids().sort_by(SortBy.VOLUME)
self.assertAlmostEqual(solids[0].Volume(), 1, 7)
self.assertAlmostEqual(solids[-1].Volume(), 8, 7)
with self.subTest(sort_by=SortBy.RADIUS):
with BuildPart() as test:
Cone(1, 0.5, 2)
edges = test.edges().filter_by_type(Type.CIRCLE).sort_by(SortBy.RADIUS)
self.assertEqual(edges[0].radius(), 0.5)
self.assertEqual(edges[-1].radius(), 1)
with self.subTest(sort_by=SortBy.X):
with BuildPart() as test:
Box(1, 1, 1)
edges = test.edges().sort_by(SortBy.X)
self.assertEqual(edges[0].Center().x, -0.5)
self.assertEqual(edges[-1].Center().x, 0.5)
with self.subTest(sort_by=SortBy.Y):
with BuildPart() as test:
Box(1, 1, 1)
edges = test.edges().sort_by(SortBy.Y)
self.assertEqual(edges[0].Center().y, -0.5)
self.assertEqual(edges[-1].Center().y, 0.5)
with self.subTest(sort_by=SortBy.Z):
with BuildPart() as test:
Box(1, 1, 1)
edges = test.edges().sort_by(SortBy.Z)
self.assertEqual(edges[0].Center().z, -0.5)
self.assertEqual(edges[-1].Center().z, 0.5)
class TestBuilder(unittest.TestCase):
"""Test the Builder base class"""
def test_exit(self):
"""test transferring objects to parent"""
with BuildPart() as outer:
with BuildSketch() as inner:
Circle(1)
self.assertEqual(len(outer.pending_faces), 1)
with BuildSketch() as inner:
with BuildLine():
CenterArc((0, 0), 1, 0, 360)
BuildFace()
self.assertEqual(len(outer.pending_faces), 1)
if __name__ == "__main__":
unittest.main()