mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-15 07:10:25 -08:00
Merge pull request #879 from snoyer/filter_by-property
allow to filter and group by property
This commit is contained in:
commit
9268f31a8c
2 changed files with 41 additions and 3 deletions
|
|
@ -2351,7 +2351,7 @@ class ShapeList(list[T]):
|
||||||
|
|
||||||
def filter_by(
|
def filter_by(
|
||||||
self,
|
self,
|
||||||
filter_by: ShapePredicate | Axis | Plane | GeomType,
|
filter_by: ShapePredicate | Axis | Plane | GeomType | property,
|
||||||
reverse: bool = False,
|
reverse: bool = False,
|
||||||
tolerance: float = 1e-5,
|
tolerance: float = 1e-5,
|
||||||
) -> ShapeList[T]:
|
) -> ShapeList[T]:
|
||||||
|
|
@ -2446,6 +2446,11 @@ class ShapeList(list[T]):
|
||||||
# convert input to callable predicate
|
# convert input to callable predicate
|
||||||
if callable(filter_by):
|
if callable(filter_by):
|
||||||
predicate = filter_by
|
predicate = filter_by
|
||||||
|
elif isinstance(filter_by, property):
|
||||||
|
|
||||||
|
def predicate(obj):
|
||||||
|
return filter_by.__get__(obj)
|
||||||
|
|
||||||
elif isinstance(filter_by, Axis):
|
elif isinstance(filter_by, Axis):
|
||||||
predicate = axis_parallel_predicate(filter_by, tolerance=tolerance)
|
predicate = axis_parallel_predicate(filter_by, tolerance=tolerance)
|
||||||
elif isinstance(filter_by, Plane):
|
elif isinstance(filter_by, Plane):
|
||||||
|
|
@ -2524,7 +2529,9 @@ class ShapeList(list[T]):
|
||||||
|
|
||||||
def group_by(
|
def group_by(
|
||||||
self,
|
self,
|
||||||
group_by: Callable[[Shape], K] | Axis | Edge | Wire | SortBy = Axis.Z,
|
group_by: (
|
||||||
|
Callable[[Shape], K] | Axis | Edge | Wire | SortBy | property
|
||||||
|
) = Axis.Z,
|
||||||
reverse=False,
|
reverse=False,
|
||||||
tol_digits=6,
|
tol_digits=6,
|
||||||
) -> GroupBy[T, K]:
|
) -> GroupBy[T, K]:
|
||||||
|
|
@ -2594,6 +2601,9 @@ class ShapeList(list[T]):
|
||||||
elif callable(group_by):
|
elif callable(group_by):
|
||||||
key_f = group_by
|
key_f = group_by
|
||||||
|
|
||||||
|
elif isinstance(group_by, property):
|
||||||
|
key_f = group_by.__get__
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported group_by function: {group_by}")
|
raise ValueError(f"Unsupported group_by function: {group_by}")
|
||||||
|
|
||||||
|
|
@ -2625,7 +2635,7 @@ class ShapeList(list[T]):
|
||||||
|
|
||||||
def sort_by(
|
def sort_by(
|
||||||
self,
|
self,
|
||||||
sort_by: Axis | Callable[[T], K] | Edge | Wire | SortBy = Axis.Z,
|
sort_by: Axis | Callable[[T], K] | Edge | Wire | SortBy | property = Axis.Z,
|
||||||
reverse: bool = False,
|
reverse: bool = False,
|
||||||
) -> ShapeList[T]:
|
) -> ShapeList[T]:
|
||||||
"""sort by
|
"""sort by
|
||||||
|
|
@ -2651,6 +2661,9 @@ class ShapeList(list[T]):
|
||||||
# If a callable is provided, use it directly as the key
|
# If a callable is provided, use it directly as the key
|
||||||
objects = sorted(self, key=sort_by, reverse=reverse)
|
objects = sorted(self, key=sort_by, reverse=reverse)
|
||||||
|
|
||||||
|
elif isinstance(sort_by, property):
|
||||||
|
objects = sorted(self, key=sort_by.__get__, reverse=reverse)
|
||||||
|
|
||||||
elif isinstance(sort_by, Axis):
|
elif isinstance(sort_by, Axis):
|
||||||
if sort_by.wrapped is None:
|
if sort_by.wrapped is None:
|
||||||
raise ValueError("Cannot sort by an empty axis")
|
raise ValueError("Cannot sort by an empty axis")
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,14 @@ class TestShapeList(unittest.TestCase):
|
||||||
self.assertAlmostEqual(smallest.area, math.pi * 1**2, 5)
|
self.assertAlmostEqual(smallest.area, math.pi * 1**2, 5)
|
||||||
self.assertAlmostEqual(largest.area, math.pi * 2**2, 5)
|
self.assertAlmostEqual(largest.area, math.pi * 2**2, 5)
|
||||||
|
|
||||||
|
def test_sort_by_property(self):
|
||||||
|
box1 = Box(1, 1, 1)
|
||||||
|
box2 = Box(2, 2, 2)
|
||||||
|
box3 = Box(3, 3, 3)
|
||||||
|
unsorted_boxes = ShapeList([box2, box3, box1])
|
||||||
|
assert unsorted_boxes.sort_by(Solid.volume) == [box1, box2, box3]
|
||||||
|
assert unsorted_boxes.sort_by(Solid.volume, reverse=True) == [box3, box2, box1]
|
||||||
|
|
||||||
def test_sort_by_invalid(self):
|
def test_sort_by_invalid(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Solid.make_box(1, 1, 1).faces().sort_by(">Z")
|
Solid.make_box(1, 1, 1).faces().sort_by(">Z")
|
||||||
|
|
@ -119,6 +127,12 @@ class TestShapeList(unittest.TestCase):
|
||||||
self.assertEqual(len(shapelist.filter_by(lambda s: s.label == "A")), 2)
|
self.assertEqual(len(shapelist.filter_by(lambda s: s.label == "A")), 2)
|
||||||
self.assertEqual(len(shapelist.filter_by(lambda s: s.label == "B")), 1)
|
self.assertEqual(len(shapelist.filter_by(lambda s: s.label == "B")), 1)
|
||||||
|
|
||||||
|
def test_filter_by_property(self):
|
||||||
|
box1 = Box(2, 2, 2)
|
||||||
|
box2 = Box(2, 2, 2).translate((1, 1, 1))
|
||||||
|
assert len((box1 + box2).edges().filter_by(Edge.is_interior)) == 6
|
||||||
|
assert len((box1 - box2).edges().filter_by(Edge.is_interior)) == 3
|
||||||
|
|
||||||
def test_first_last(self):
|
def test_first_last(self):
|
||||||
vertices = (
|
vertices = (
|
||||||
Solid.make_box(1, 1, 1).vertices().sort_by(Axis((0, 0, 0), (1, 1, 1)))
|
Solid.make_box(1, 1, 1).vertices().sort_by(Axis((0, 0, 0), (1, 1, 1)))
|
||||||
|
|
@ -187,6 +201,17 @@ class TestShapeList(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual([len(group) for group in result], [1, 3, 2])
|
self.assertEqual([len(group) for group in result], [1, 3, 2])
|
||||||
|
|
||||||
|
def test_group_by_property(self):
|
||||||
|
box1 = Box(2, 2, 2)
|
||||||
|
box2 = Box(2, 2, 2).translate((1, 1, 1))
|
||||||
|
g1 = (box1 + box2).edges().group_by(Edge.is_interior)
|
||||||
|
assert len(g1.group(True)) == 6
|
||||||
|
assert len(g1.group(False)) == 24
|
||||||
|
|
||||||
|
g2 = (box1 - box2).edges().group_by(Edge.is_interior)
|
||||||
|
assert len(g2.group(True)) == 3
|
||||||
|
assert len(g2.group(False)) == 18
|
||||||
|
|
||||||
def test_group_by_retrieve_groups(self):
|
def test_group_by_retrieve_groups(self):
|
||||||
boxesA = [Solid.make_box(1, 1, 1) for _ in range(3)]
|
boxesA = [Solid.make_box(1, 1, 1) for _ in range(3)]
|
||||||
boxesB = [Solid.make_box(1, 1, 1) for _ in range(2)]
|
boxesB = [Solid.make_box(1, 1, 1) for _ in range(2)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue