mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-16 15:40:49 -08:00
Common unittests, removed VertexList
This commit is contained in:
parent
81214ee86d
commit
f82b94121b
8 changed files with 390 additions and 103 deletions
|
|
@ -122,3 +122,4 @@ a fully custom selection:
|
|||
One can sort by all of the following attributes:
|
||||
|
||||
.. autoclass:: build_common.SortBy
|
||||
:noindex:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
***********
|
||||
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
|
||||
*************************************
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ __all__ = [
|
|||
"Type",
|
||||
"Rotation",
|
||||
"ShapeList",
|
||||
"VertexList",
|
||||
"Builder",
|
||||
"Add",
|
||||
"BoundingBox",
|
||||
|
|
|
|||
|
|
@ -57,23 +57,29 @@ Edge.__mod__ = __mod__custom
|
|||
Wire.__matmul__ = __matmul__custom
|
||||
Wire.__mod__ = __mod__custom
|
||||
|
||||
context_stack = []
|
||||
# context_stack = []
|
||||
|
||||
#
|
||||
# ENUMs
|
||||
#
|
||||
class Select(Enum):
|
||||
"""Selector scope - all or last operation"""
|
||||
|
||||
ALL = auto()
|
||||
LAST = auto()
|
||||
|
||||
|
||||
class Kind(Enum):
|
||||
"""Offset corner transition"""
|
||||
|
||||
ARC = auto()
|
||||
INTERSECTION = auto()
|
||||
TANGENT = auto()
|
||||
|
||||
|
||||
class Keep(Enum):
|
||||
"""Split options"""
|
||||
|
||||
TOP = auto()
|
||||
BOTTOM = auto()
|
||||
BOTH = auto()
|
||||
|
|
@ -90,6 +96,8 @@ class Mode(Enum):
|
|||
|
||||
|
||||
class Transition(Enum):
|
||||
"""Sweep discontinuity handling option"""
|
||||
|
||||
RIGHT = auto()
|
||||
ROUND = auto()
|
||||
TRANSFORMED = auto()
|
||||
|
|
@ -104,7 +112,7 @@ class FontStyle(Enum):
|
|||
|
||||
|
||||
class Halign(Enum):
|
||||
"""Horizontal Alignment"""
|
||||
"""Text Horizontal Alignment"""
|
||||
|
||||
CENTER = auto()
|
||||
LEFT = auto()
|
||||
|
|
@ -112,7 +120,7 @@ class Halign(Enum):
|
|||
|
||||
|
||||
class Valign(Enum):
|
||||
"""Vertical Alignment"""
|
||||
"""Text Vertical Alignment"""
|
||||
|
||||
CENTER = auto()
|
||||
TOP = auto()
|
||||
|
|
@ -120,17 +128,23 @@ class Valign(Enum):
|
|||
|
||||
|
||||
class Until(Enum):
|
||||
"""Extude limit"""
|
||||
|
||||
NEXT = auto()
|
||||
LAST = auto()
|
||||
|
||||
|
||||
class Axis(Enum):
|
||||
"""One of the three dimensions"""
|
||||
|
||||
X = auto()
|
||||
Y = auto()
|
||||
Z = auto()
|
||||
|
||||
|
||||
class SortBy(Enum):
|
||||
"""Sorting criteria"""
|
||||
|
||||
X = auto()
|
||||
Y = auto()
|
||||
Z = auto()
|
||||
|
|
@ -142,6 +156,8 @@ class SortBy(Enum):
|
|||
|
||||
|
||||
class Type(Enum):
|
||||
"""CAD object type"""
|
||||
|
||||
PLANE = auto()
|
||||
CYLINDER = auto()
|
||||
CONE = auto()
|
||||
|
|
@ -164,6 +180,8 @@ class Type(Enum):
|
|||
# DirectAPI Classes
|
||||
#
|
||||
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):
|
||||
self.about_x = about_x
|
||||
self.about_y = about_y
|
||||
|
|
@ -179,10 +197,13 @@ class Rotation(Location):
|
|||
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]
|
||||
|
||||
|
||||
class ShapeList(list):
|
||||
"""Subclass of list with custom filter and sort methods appropriate to CAD"""
|
||||
|
||||
axis_map = {
|
||||
Axis.X: ((1, 0, 0), (-1, 0, 0)),
|
||||
Axis.Y: ((0, 1, 0), (0, -1, 0)),
|
||||
|
|
@ -190,9 +211,21 @@ class ShapeList(list):
|
|||
}
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
return super().__init_subclass__()
|
||||
super().__init_subclass__()
|
||||
|
||||
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(
|
||||
lambda o: isinstance(o, Face) and o.geomType() == "PLANE", self
|
||||
|
|
@ -218,12 +251,10 @@ class ShapeList(list):
|
|||
list(
|
||||
filter(
|
||||
lambda o: (
|
||||
o.tangentAt(None) - Vector(*ShapeList.axis_map[axis][0])
|
||||
o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][0])
|
||||
).Length
|
||||
<= tolerance
|
||||
or (
|
||||
o.tangentAt(o.Center()) - Vector(*ShapeList.axis_map[axis][1])
|
||||
).Length
|
||||
or (o.tangentAt(0) - Vector(*ShapeList.axis_map[axis][1])).Length
|
||||
<= tolerance,
|
||||
linear_edges,
|
||||
)
|
||||
|
|
@ -245,6 +276,20 @@ class ShapeList(list):
|
|||
max: float,
|
||||
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 inclusive == (True, True):
|
||||
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)
|
||||
elif inclusive == (False, False):
|
||||
result = filter(lambda o: min < o.Center().x < max, self)
|
||||
result = sorted(result, key=lambda obj: obj.Center().x)
|
||||
elif axis == Axis.Y:
|
||||
if inclusive == (True, True):
|
||||
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)
|
||||
elif inclusive == (False, False):
|
||||
result = filter(lambda o: min < o.Center().y < max, self)
|
||||
result = sorted(result, key=lambda obj: obj.Center().y)
|
||||
elif axis == Axis.Z:
|
||||
if inclusive == (True, True):
|
||||
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)
|
||||
elif inclusive == (False, False):
|
||||
result = filter(lambda o: min < o.Center().z < max, self)
|
||||
result = sorted(result, key=lambda obj: obj.Center().z)
|
||||
|
||||
return ShapeList(result)
|
||||
|
||||
|
|
@ -279,135 +327,83 @@ class ShapeList(list):
|
|||
self,
|
||||
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)
|
||||
return ShapeList(result)
|
||||
|
||||
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:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Center().x,
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.Y:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Center().y,
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.Z:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Center().z,
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.LENGTH:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Length(),
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.RADIUS:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.radius(),
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.DISTANCE:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Center().Length,
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.AREA:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Area(),
|
||||
reverse=reverse,
|
||||
)
|
||||
elif sort_by == SortBy.VOLUME:
|
||||
obj = sorted(
|
||||
objects = sorted(
|
||||
self,
|
||||
key=lambda obj: obj.Volume(),
|
||||
reverse=reverse,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unable to sort shapes by {sort_by}")
|
||||
|
||||
return ShapeList(obj)
|
||||
|
||||
|
||||
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)
|
||||
return ShapeList(objects)
|
||||
|
||||
|
||||
class Builder(ABC):
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class BuildLine(Builder):
|
|||
self.line = []
|
||||
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 either all or the vertices created during the last operation.
|
||||
|
|
@ -91,7 +91,7 @@ class BuildLine(Builder):
|
|||
vertex_list.extend(edge.Vertices())
|
||||
elif select == Select.LAST:
|
||||
vertex_list = self.last_vertices
|
||||
return VertexList(set(vertex_list))
|
||||
return ShapeList(set(vertex_list))
|
||||
|
||||
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
|
||||
"""Return Edges from Line
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class BuildPart(Builder):
|
|||
self.last_solids = []
|
||||
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 either all or the vertices created during the last operation.
|
||||
|
|
@ -128,7 +128,7 @@ class BuildPart(Builder):
|
|||
vertex_list.extend(edge.Vertices())
|
||||
elif select == Select.LAST:
|
||||
vertex_list = self.last_vertices
|
||||
return VertexList(set(vertex_list))
|
||||
return ShapeList(set(vertex_list))
|
||||
|
||||
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
|
||||
"""Return Edges from Part
|
||||
|
|
|
|||
|
|
@ -77,13 +77,13 @@ class BuildSketch(Builder):
|
|||
return self.sketch
|
||||
|
||||
def __init__(self, mode: Mode = Mode.ADD):
|
||||
self.sketch = None
|
||||
self.sketch: Compound = None
|
||||
self.pending_edges: list[Edge] = []
|
||||
self.locations: list[Location] = [Location(Vector())]
|
||||
self.last_faces = []
|
||||
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 either all or the vertices created during the last operation.
|
||||
|
|
@ -100,7 +100,7 @@ class BuildSketch(Builder):
|
|||
vertex_list.extend(edge.Vertices())
|
||||
elif select == Select.LAST:
|
||||
vertex_list = self.last_vertices
|
||||
return VertexList(set(vertex_list))
|
||||
return ShapeList(set(vertex_list))
|
||||
|
||||
def edges(self, select: Select = Select.ALL) -> ShapeList[Edge]:
|
||||
"""Return Edges from Sketch
|
||||
|
|
@ -331,7 +331,6 @@ class FilletSketch(Compound):
|
|||
super().__init__(new_sketch.wrapped)
|
||||
|
||||
|
||||
|
||||
class Offset(Compound):
|
||||
"""Sketch Operation: Offset
|
||||
|
||||
|
|
@ -375,13 +374,11 @@ class Offset(Compound):
|
|||
super().__init__(Compound.makeCompound(new_faces).wrapped)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Objects
|
||||
#
|
||||
|
||||
|
||||
class Circle(Compound):
|
||||
"""Sketch Object: Circle
|
||||
|
||||
|
|
|
|||
243
tests/build_common_tests.py
Normal file
243
tests/build_common_tests.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue