From 90f8f7618533693eb7c901e9c1df2253991580ff Mon Sep 17 00:00:00 2001 From: David Romero Date: Fri, 19 Jun 2020 21:36:22 -0500 Subject: [PATCH] Method for checking if one path is inside another path (#105) * Added method is_contained_by to the Path class * Adding a requirements.txt file Signed-off-by: David Romero * Correcting tests of is_contained_by --- requirements.txt | 2 ++ svgpathtools/path.py | 20 +++++++++++++++++++- test/test_path.py | 28 +++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2c3af04 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy +svgwrite diff --git a/svgpathtools/path.py b/svgpathtools/path.py index 93a6e2f..f2d07d0 100644 --- a/svgpathtools/path.py +++ b/svgpathtools/path.py @@ -115,7 +115,6 @@ def polygon(*points): return Path(*[Line(points[i], points[(i + 1) % len(points)]) for i in range(len(points))]) - # Conversion################################################################### def bpoints2bezier(bpoints): @@ -2700,3 +2699,22 @@ class Path(MutableSequence): def scaled(self, sx, sy=None, origin=0j): """Scale transform. See `scale` function for further explanation.""" return scale(self, sx=sx, sy=sy, origin=origin) + + def is_contained_by(self, other): + """Returns true if the path is fully contained in other closed path""" + assert isinstance(other, Path) + assert other.isclosed() + assert self != other + + if self.intersect(other, justonemode=True): + return False + + pt = self.point(0) + xmin, xmax, ymin, ymax = other.bbox() + pt_in_bbox = (xmin <= pt.real <= xmax) and (ymin <= pt.imag <= ymax) + + if not pt_in_bbox: + return False + + opt = complex(xmin-1, ymin-1) + return path_encloses_pt(pt, opt, other) diff --git a/test/test_path.py b/test/test_path.py index 22648b0..d4021ef 100644 --- a/test/test_path.py +++ b/test/test_path.py @@ -16,7 +16,6 @@ from svgpathtools.path import _NotImplemented4ArcException # take too long and be too error prone. Instead the curves have been verified # to be correct visually with the disvg() function. - def random_line(): x = (random.random() - 0.5) * 2000 y = (random.random() - 0.5) * 2000 @@ -1783,6 +1782,33 @@ class TestPathTools(unittest.TestCase): self.assertAlmostEqual(ccw_half_circle.area(), 3926.9908169872415, places=3) self.assertAlmostEqual(ccw_half_circle.area(chord_length=1e-3), 3926.9908169872415, places=6) + def test_is_contained_by(self): + enclosing_shape = Path() + enclosing_shape.append(Line((0+0j), (0+100j))) + enclosing_shape.append(Line((0+100j), (100+100j))) + enclosing_shape.append(Line((100+100j), (100+0j))) + enclosing_shape.append(Line((100+0j), (0+0j))) + + enclosed_path = Path() + enclosed_path.append(Line((10+10j), (90+90j))) + self.assertTrue(enclosed_path.is_contained_by(enclosing_shape)) + + not_enclosed_path = Path() + not_enclosed_path.append(Line((200+200j), (200+0j))) + self.assertFalse(not_enclosed_path.is_contained_by(enclosing_shape)) + + intersecting_path = Path() + intersecting_path.append(Line((50+50j), (200+50j))) + self.assertFalse(intersecting_path.is_contained_by(enclosing_shape)) + + larger_shape = Path() + larger_shape.append(Line((-10-10j), (-10+110j))) + larger_shape.append(Line((-10+110j), (110+110j))) + larger_shape.append(Line((110+110j), (110+-10j))) + larger_shape.append(Line((110-10j), (-10-10j))) + self.assertFalse(larger_shape.is_contained_by(enclosing_shape)) + self.assertTrue(enclosing_shape.is_contained_by(larger_shape)) + if __name__ == '__main__': unittest.main()