diff --git a/cadquery/selectors.py b/cadquery/selectors.py index 07fc31e..72362c8 100644 --- a/cadquery/selectors.py +++ b/cadquery/selectors.py @@ -70,6 +70,49 @@ class NearestToPointSelector(Selector): return [ min(objectList,key=dist) ] +class BoxSelector(Selector): + """ + Selects objects inside the 3D box defined by 2 points. + + If `boundingbox` is True only the objects that have their bounding + box inside the given box is selected. Otherwise only center point + of the object is tested. + + Applicability: all types of shapes + + Example:: + + CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1)) + """ + def __init__(self, point0, point1, boundingbox=False): + self.p0 = Vector(*point0) + self.p1 = Vector(*point1) + self.test_boundingbox = boundingbox + + def filter(self, objectList): + + result = [] + x0, y0, z0 = self.p0.toTuple() + x1, y1, z1 = self.p1.toTuple() + + def isInsideBox(p): + # using XOR for checking if x/y/z is in between regardless + # of order of x/y/z0 and x/y/z1 + return ((p.x < x0) ^ (p.x < x1)) and \ + ((p.y < y0) ^ (p.y < y1)) and \ + ((p.z < z0) ^ (p.z < z1)) + + for o in objectList: + if self.test_boundingbox: + bb = o.BoundingBox() + if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \ + isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)): + result.append(o) + else: + if isInsideBox(o.Center()): + result.append(o) + + return result class BaseDirSelector(Selector): """ diff --git a/tests/TestCQSelectors.py b/tests/TestCQSelectors.py index e81a393..678766a 100644 --- a/tests/TestCQSelectors.py +++ b/tests/TestCQSelectors.py @@ -178,6 +178,102 @@ class TestCQSelectors(BaseTest): s = c.solids(selectors.NearestToPointSelector(t)).vals() self.assertEqual(1,len(s)) + def testBox(self): + c = CQ(makeUnitCube()) + + # test vertice selection + test_data_vertices = [ + # box point0, box point1, selected vertice + ((0.9, 0.9, 0.9), (1.1, 1.1, 1.1), (1.0, 1.0, 1.0)), + ((-0.1, 0.9, 0.9), (0.9, 1.1, 1.1), (0.0, 1.0, 1.0)), + ((-0.1, -0.1, 0.9), (0.1, 0.1, 1.1), (0.0, 0.0, 1.0)), + ((-0.1, -0.1, -0.1), (0.1, 0.1, 0.1), (0.0, 0.0, 0.0)), + ((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)), + ((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)), + ((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)), + ((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0)) + ] + + for d in test_data_vertices: + vl = c.vertices(selectors.BoxSelector(d[0], d[1])).vals() + self.assertEqual(1, len(vl)) + v = vl[0] + self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3) + + # this time box points are swapped + vl = c.vertices(selectors.BoxSelector(d[1], d[0])).vals() + self.assertEqual(1, len(vl)) + v = vl[0] + self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3) + + # test multiple vertices selection + vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, 0.9),(0.1, 1.1, 1.1))).vals() + self.assertEqual(2, len(vl)) + vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, -0.1),(0.1, 1.1, 1.1))).vals() + self.assertEqual(4, len(vl)) + + # test edge selection + test_data_edges = [ + # box point0, box point1, edge center + ((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)), + ((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)), + ((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)), + ((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0)) + ] + + for d in test_data_edges: + el = c.edges(selectors.BoxSelector(d[0], d[1])).vals() + self.assertEqual(1, len(el)) + ec = el[0].Center() + self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3) + + # test again by swapping box points + el = c.edges(selectors.BoxSelector(d[1], d[0])).vals() + self.assertEqual(1, len(el)) + ec = el[0].Center() + self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3) + + # test multiple edge selection + el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals() + self.assertEqual(2, len(el)) + el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals() + self.assertEqual(3, len(el)) + + # test face selection + test_data_faces = [ + # box point0, box point1, face center + ((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)), + ((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)), + ((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)), + ((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0)) + ] + + for d in test_data_faces: + fl = c.faces(selectors.BoxSelector(d[0], d[1])).vals() + self.assertEqual(1, len(fl)) + fc = fl[0].Center() + self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3) + + # test again by swapping box points + fl = c.faces(selectors.BoxSelector(d[1], d[0])).vals() + self.assertEqual(1, len(fl)) + fc = fl[0].Center() + self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3) + + # test multiple face selection + fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals() + self.assertEqual(2, len(fl)) + fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals() + self.assertEqual(3, len(fl)) + + # test boundingbox option + el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals() + self.assertEqual(1, len(el)) + fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals() + self.assertEqual(0, len(fl)) + fl = c.faces(selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals() + self.assertEqual(1, len(fl)) + def testFaceCount(self): c = CQ(makeUnitCube()) self.assertEqual( 6, c.faces().size() ) @@ -194,4 +290,4 @@ class TestCQSelectors(BaseTest): self.assertEqual(1,v2.size() ) #another way #make sure the vertex is the right one - self.assertTupleAlmostEquals((0.0,0.0,1.0),v2.val().toTuple() ,3) \ No newline at end of file + self.assertTupleAlmostEquals((0.0,0.0,1.0),v2.val().toTuple() ,3)