mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-15 23:30:28 -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:
|
One can sort by all of the following attributes:
|
||||||
|
|
||||||
.. autoclass:: build_common.SortBy
|
.. 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.
|
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
|
||||||
*************************************
|
*************************************
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ __all__ = [
|
||||||
"Type",
|
"Type",
|
||||||
"Rotation",
|
"Rotation",
|
||||||
"ShapeList",
|
"ShapeList",
|
||||||
"VertexList",
|
|
||||||
"Builder",
|
"Builder",
|
||||||
"Add",
|
"Add",
|
||||||
"BoundingBox",
|
"BoundingBox",
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
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