#!/usr/bin/python
"""
Tests for Pyclipper wrapper library.
"""

from __future__ import print_function
from unittest2 import TestCase, main
import sys

if sys.version_info < (3,):
    integer_types = (int, long)
else:
    integer_types = (int,)

import pyclipper

# Example polygons from http://www.angusj.com/delphi/clipper.php
PATH_SUBJ_1 = [[180, 200], [260, 200], [260, 150], [180, 150]]  # square, orientation is False
PATH_SUBJ_2 = [[215, 160], [230, 190], [200, 190]]  # triangle
PATH_CLIP_1 = [[190, 210], [240, 210], [240, 130], [190, 130]]  # square
PATH_SIGMA = [[300, 400], [100, 400], [200, 300], [100, 200], [300, 200]]  # greek letter sigma
PATTERN = [[4, -6], [6, -6], [-4, 6], [-6, 6]]
INVALID_PATH = [[1, 1], ]  # less than 2 vertices


class TestPyclipperModule(TestCase):
    def test_has_classes(self):
        self.assertTrue(hasattr(pyclipper, 'Pyclipper'))
        self.assertTrue(hasattr(pyclipper, 'PyclipperOffset'))

    def test_has_namespace_methods(self):
        for method in ('Orientation', 'Area', 'PointInPolygon', 'SimplifyPolygon', 'SimplifyPolygons',
                       'CleanPolygon', 'CleanPolygons', 'MinkowskiSum', 'MinkowskiSum2', 'MinkowskiDiff',
                       'PolyTreeToPaths', 'ClosedPathsFromPolyTree', 'OpenPathsFromPolyTree',
                       'ReversePath', 'ReversePaths'):
            self.assertTrue(hasattr(pyclipper, method))


class TestNamespaceMethods(TestCase):
    def setUp(self):
        pyclipper.SCALING_FACTOR = 1

    def test_orientation(self):
        self.assertFalse(pyclipper.Orientation(PATH_SUBJ_1))
        self.assertTrue(pyclipper.Orientation(PATH_SUBJ_1[::-1]))

    def test_area(self):
        # area less than 0 because orientation is False
        area_neg = pyclipper.Area(PATH_SUBJ_1)
        area_pos = pyclipper.Area(PATH_SUBJ_1[::-1])
        self.assertLess(area_neg, 0)
        self.assertGreater(area_pos, 0)
        self.assertEqual(abs(area_neg), area_pos)

    def test_point_in_polygon(self):
        # on polygon
        self.assertEqual(pyclipper.PointInPolygon((180, 200), PATH_SUBJ_1), -1)

        # in polygon
        self.assertEqual(pyclipper.PointInPolygon((200, 180), PATH_SUBJ_1), 1)

        # outside of polygon
        self.assertEqual(pyclipper.PointInPolygon((500, 500), PATH_SUBJ_1), 0)

    def test_minkowski_sum(self):
        solution = pyclipper.MinkowskiSum(PATTERN, PATH_SIGMA, False)
        self.assertGreater(len(solution), 0)

    def test_minkowski_sum2(self):
        solution = pyclipper.MinkowskiSum2(PATTERN, [PATH_SIGMA], False)
        self.assertGreater(len(solution), 0)

    def test_minkowski_diff(self):
        solution = pyclipper.MinkowskiDiff(PATH_SUBJ_1, PATH_SUBJ_2)
        self.assertGreater(len(solution), 0)

    def test_reverse_path(self):
        solution = pyclipper.ReversePath(PATH_SUBJ_1)
        manualy_reversed = PATH_SUBJ_1[::-1]
        self.check_reversed_path(solution, manualy_reversed)

    def test_reverse_paths(self):
        solution = pyclipper.ReversePaths([PATH_SUBJ_1])
        manualy_reversed = [PATH_SUBJ_1[::-1]]
        self.check_reversed_path(solution[0], manualy_reversed[0])

    def check_reversed_path(self, path_1, path_2):
        if len(path_1) is not len(path_2):
            return False

        for i in range(len(path_1)):
            self.assertEqual(path_1[i][0], path_2[i][0])
            self.assertEqual(path_1[i][1], path_2[i][1])

    def test_simplify_polygon(self):
        solution = pyclipper.SimplifyPolygon(PATH_SUBJ_1)
        self.assertEqual(len(solution), 1)

    def test_simplify_polygons(self):
        solution = pyclipper.SimplifyPolygons([PATH_SUBJ_1])
        solution_single = pyclipper.SimplifyPolygon(PATH_SUBJ_1)
        self.assertEqual(len(solution), 1)
        self.assertEqual(len(solution), len(solution_single))
        _do_solutions_match(solution, solution_single)

    def test_clean_polygon(self):
        solution = pyclipper.CleanPolygon(PATH_CLIP_1)
        self.assertEqual(len(solution), len(PATH_CLIP_1))

    def test_clean_polygons(self):
        solution = pyclipper.CleanPolygons([PATH_CLIP_1])
        self.assertEqual(len(solution), 1)
        self.assertEqual(len(solution[0]), len(PATH_CLIP_1))


class TestFilterPyPolyNode(TestCase):
    def setUp(self):
        tree = pyclipper.PyPolyNode()
        tree.Contour.append(PATH_CLIP_1)
        tree.IsOpen = True

        child = pyclipper.PyPolyNode()
        child.IsOpen = False
        child.Parent = tree
        child.Contour = PATH_SUBJ_1
        tree.Childs.append(child)

        child = pyclipper.PyPolyNode()
        child.IsOpen = True
        child.Parent = tree
        child.Contour = PATH_SUBJ_2
        tree.Childs.append(child)

        child2 = pyclipper.PyPolyNode()
        child2.IsOpen = False
        child2.Parent = child
        child2.Contour = PATTERN
        child.Childs.append(child2)

        # empty contour should not
        # be included in filtered results
        child2 = pyclipper.PyPolyNode()
        child2.IsOpen = False
        child2.Parent = child
        child2.Contour = []
        child.Childs.append(child2)

        self.tree = tree

    def test_polytree_to_paths(self):
        paths = pyclipper.PolyTreeToPaths(self.tree)
        self.check_paths(paths, 4)

    def test_closed_paths_from_polytree(self):
        paths = pyclipper.ClosedPathsFromPolyTree(self.tree)
        self.check_paths(paths, 2)

    def test_open_paths_from_polytree(self):
        paths = pyclipper.OpenPathsFromPolyTree(self.tree)
        self.check_paths(paths, 2)

    def check_paths(self, paths, expected_nr):
        self.assertEqual(len(paths), expected_nr)
        self.assertTrue(all((len(path) > 0 for path in paths)))


class TestPyclipperAddPaths(TestCase):
    def setUp(self):
        pyclipper.SCALING_FACTOR = 1
        self.pc = pyclipper.Pyclipper()

    def test_add_path(self):
        # should not raise an exception
        self.pc.AddPath(PATH_CLIP_1, poly_type=pyclipper.PT_CLIP)

    def test_add_paths(self):
        # should not raise an exception
        self.pc.AddPaths([PATH_SUBJ_1, PATH_SUBJ_2], poly_type=pyclipper.PT_SUBJECT)

    def test_add_path_invalid_path(self):
        self.assertRaises(pyclipper.ClipperException, self.pc.AddPath, INVALID_PATH, pyclipper.PT_CLIP, True)

    def test_add_paths_invalid_path(self):
        self.assertRaises(pyclipper.ClipperException, self.pc.AddPaths, [INVALID_PATH, INVALID_PATH],
                          pyclipper.PT_CLIP, True)
        try:
            self.pc.AddPaths([INVALID_PATH, PATH_CLIP_1], pyclipper.PT_CLIP)
            self.pc.AddPaths([PATH_CLIP_1, INVALID_PATH], pyclipper.PT_CLIP)
        except pyclipper.ClipperException:
            self.fail("add_paths raised ClipperException when not all paths were invalid")


class TestClassProperties(TestCase):
    def check_property_assignment(self, pc, prop_name, values):
        for val in values:
            setattr(pc, prop_name, val)
            self.assertEqual(getattr(pc, prop_name), val)

    def test_pyclipper_properties(self):
        pc = pyclipper.Pyclipper()
        for prop_name in ('ReverseSolution', 'PreserveCollinear', 'StrictlySimple'):
            self.check_property_assignment(pc, prop_name, [True, False])

    def test_pyclipperoffset_properties(self):
        for factor in range(6):
            pyclipper.SCALING_FACTOR = 10 ** factor
            pc = pyclipper.PyclipperOffset()
            for prop_name in ('MiterLimit', 'ArcTolerance'):
                self.check_property_assignment(pc, prop_name, [2.912, 132.12, 12, -123])


class TestPyclipperExecute(TestCase):
    def setUp(self):
        pyclipper.SCALING_FACTOR = 1
        self.pc = pyclipper.Pyclipper()
        self.add_default_paths(self.pc)
        self.default_args = [pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD]

    @staticmethod
    def add_default_paths(pc):
        pc.AddPath(PATH_CLIP_1, pyclipper.PT_CLIP)
        pc.AddPaths([PATH_SUBJ_1, PATH_SUBJ_2], pyclipper.PT_SUBJECT)

    @staticmethod
    def add_paths(pc, clip_path, subj_paths, addend=None, multiplier=None):
        pc.AddPath(_modify_vertices(clip_path, addend=addend, multiplier=multiplier), pyclipper.PT_CLIP)
        for subj_path in subj_paths:
            pc.AddPath(_modify_vertices(subj_path, addend=addend, multiplier=multiplier), pyclipper.PT_SUBJECT)

    def test_get_bounds(self):
        bounds = self.pc.GetBounds()
        self.assertIsInstance(bounds, pyclipper.PyIntRect)
        self.assertEqual(bounds.left, 180)
        self.assertEqual(bounds.right, 260)
        self.assertEqual(bounds.top, 130)
        self.assertEqual(bounds.bottom, 210)

    def test_execute(self):
        solution = self.pc.Execute(*self.default_args)
        self.assertEqual(len(solution), 2)

    def test_execute2(self):
        solution = self.pc.Execute2(*self.default_args)
        self.assertIsInstance(solution, pyclipper.PyPolyNode)
        self.check_pypolynode(solution)

    def test_execute_empty(self):
        pc = pyclipper.Pyclipper()
        with self.assertRaises(pyclipper.ClipperException):
            pc.Execute(pyclipper.CT_UNION,
                       pyclipper.PFT_NONZERO,
                       pyclipper.PFT_NONZERO)

    def test_clear(self):
        self.pc.Clear()
        with self.assertRaises(pyclipper.ClipperException):
            self.pc.Execute(*self.default_args)

    def test_exact_results(self):
        """
        Test whether coordinates passed into the library are returned exactly, if they are not affected by the
        operation.
        """

        pc = pyclipper.Pyclipper()

        # Some large triangle.
        path = [[[0, 1], [0, 0], [15 ** 15, 0]]]

        pc.AddPaths(path, pyclipper.PT_SUBJECT, True)
        result = pc.Execute(pyclipper.PT_CLIP, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD)

        assert result == path

    def check_pypolynode(self, node):
        self.assertTrue(len(node.Contour) == 0 or len(node.Contour) > 2)

        # check vertex coordinate, should not be an iterable (in that case
        # that means that node.Contour is a list of paths, should be path
        if node.Contour:
            self.assertFalse(hasattr(node.Contour[0][0], '__iter__'))

        for child in node.Childs:
            self.check_pypolynode(child)


class TestPyclipperOffset(TestCase):
    def setUp(self):
        pyclipper.SCALING_FACTOR = 1

    @staticmethod
    def add_path(pc, path):
        pc.AddPath(path, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)

    def test_execute(self):
        pc = pyclipper.PyclipperOffset()
        self.add_path(pc, PATH_CLIP_1)
        solution = pc.Execute(2.0)
        self.assertIsInstance(solution, list)
        self.assertEqual(len(solution), 1)

    def test_execute2(self):
        pc = pyclipper.PyclipperOffset()
        self.add_path(pc, PATH_CLIP_1)
        solution = pc.Execute2(2.0)
        self.assertIsInstance(solution, pyclipper.PyPolyNode)
        self.assertEqual(len(pyclipper.OpenPathsFromPolyTree(solution)), 0)
        self.assertEqual(len(pyclipper.ClosedPathsFromPolyTree(solution)), 1)

    def test_clear(self):
        pc = pyclipper.PyclipperOffset()
        self.add_path(pc, PATH_CLIP_1)
        pc.Clear()
        solution = pc.Execute(2.0)
        self.assertIsInstance(solution, list)
        self.assertEqual(len(solution), 0)


class TestScalingFactorWarning(TestCase):
    def setUp(self):
        pyclipper.SCALING_FACTOR = 2.
        self.pc = pyclipper.Pyclipper()

    def test_orientation(self):
        with self.assertWarns(DeprecationWarning):
            pyclipper.Orientation(PATH_SUBJ_1)

    def test_area(self):
        with self.assertWarns(DeprecationWarning):
            pyclipper.Area(PATH_SUBJ_1)

    def test_point_in_polygon(self):
        with self.assertWarns(DeprecationWarning):
            self.assertEqual(pyclipper.PointInPolygon((180, 200), PATH_SUBJ_1), -1)

    def test_minkowski_sum(self):
        with self.assertWarns(DeprecationWarning):
            pyclipper.MinkowskiSum(PATTERN, PATH_SIGMA, False)

    def test_minkowski_sum2(self):
        with self.assertWarns(DeprecationWarning):
            pyclipper.MinkowskiSum2(PATTERN, [PATH_SIGMA], False)

    def test_minkowski_diff(self):
        with self.assertWarns(DeprecationWarning):
            pyclipper.MinkowskiDiff(PATH_SUBJ_1, PATH_SUBJ_2)

    def test_add_path(self):
        with self.assertWarns(DeprecationWarning):
            self.pc.AddPath(PATH_CLIP_1, poly_type=pyclipper.PT_CLIP)

    def test_add_paths(self):
        with self.assertWarns(DeprecationWarning):
            self.pc.AddPaths([PATH_SUBJ_1, PATH_SUBJ_2], poly_type=pyclipper.PT_SUBJECT)


class TestScalingFunctions(TestCase):
    scale = 2 ** 31
    path = [(0, 0), (1, 1)]
    paths = [path] * 3

    def test_value_scale_to(self):
        value = 0.5
        res = pyclipper.scale_to_clipper(value, self.scale)

        assert isinstance(res, integer_types)
        assert res == int(value * self.scale)

    def test_value_scale_from(self):
        value = 1000000000000
        res = pyclipper.scale_from_clipper(value, self.scale)

        assert isinstance(res, float)
        # Convert to float to get "normal" division in Python < 3.
        assert res == float(value) / self.scale

    def test_path_scale_to(self):
        res = pyclipper.scale_to_clipper(self.path)

        assert len(res) == len(self.path)
        assert all(isinstance(i, list) for i in res)
        assert all(isinstance(j, integer_types) for i in res for j in i)

    def test_path_scale_from(self):
        res = pyclipper.scale_from_clipper(self.path)

        assert len(res) == len(self.path)
        assert all(isinstance(i, list) for i in res)
        assert all(isinstance(j, float) for i in res for j in i)

    def test_paths_scale_to(self):
        res = pyclipper.scale_to_clipper(self.paths)

        assert len(res) == len(self.paths)
        assert all(isinstance(i, list) for i in res)
        assert all(isinstance(j, list) for i in res for j in i)
        assert all(isinstance(k, integer_types) for i in res for j in i for k in j)

    def test_paths_scale_from(self):
        res = pyclipper.scale_from_clipper(self.paths)

        assert len(res) == len(self.paths)
        assert all(isinstance(i, list) for i in res)
        assert all(isinstance(j, list) for i in res for j in i)
        assert all(isinstance(k, float) for i in res for j in i for k in j)


class TestNonStandardNumbers(TestCase):

    def test_sympyzero(self):
        try:
            from sympy import Point2D
            from sympy.core.numbers import Zero
        except ImportError:
            self.skipTest("Skipping, sympy not available")

        path = [(0,0), (0,1)]
        path = [Point2D(v) for v in [(0,0), (0,1)]]
        assert type(path[0].x) == Zero
        path = pyclipper.scale_to_clipper(path)
        assert path == [[0, 0], [0, 2147483648]]


def _do_solutions_match(paths_1, paths_2, factor=None):
    if len(paths_1) != len(paths_2):
        return False

    paths_1 = [_modify_vertices(p, multiplier=factor, converter=round if factor else None) for p in paths_1]
    paths_2 = [_modify_vertices(p, multiplier=factor, converter=round if factor else None) for p in paths_2]

    return all(((p_1 in paths_2) for p_1 in paths_1))


def _modify_vertices(path, addend=0.0, multiplier=1.0, converter=None):
    path = path[:]

    def convert_coordinate(c):
        if multiplier is not None:
            c *= multiplier
        if addend is not None:
            c += addend
        if converter:
            c = converter(c)
        return c

    return [[convert_coordinate(c) for c in v] for v in path]


def run_tests():
    main()


if __name__ == '__main__':
    run_tests()

[((450, 4, 450, 10), 'unittest2.main', 'main', ({}, {}), '()', False, 'from unittest2 import TestCase, main\n'), ((49, 19, 49, 46), 'pyclipper.Area', 'pyclipper.Area', ({(49, 34, 49, 45): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((50, 19, 50, 52), 'pyclipper.Area', 'pyclipper.Area', ({(50, 34, 50, 51): 'PATH_SUBJ_1[::-1]'}, {}), '(PATH_SUBJ_1[::-1])', False, 'import pyclipper\n'), ((66, 19, 66, 69), 'pyclipper.MinkowskiSum', 'pyclipper.MinkowskiSum', ({(66, 42, 66, 49): 'PATTERN', (66, 51, 66, 61): 'PATH_SIGMA', (66, 63, 66, 68): 'False'}, {}), '(PATTERN, PATH_SIGMA, False)', False, 'import pyclipper\n'), ((70, 19, 70, 72), 'pyclipper.MinkowskiSum2', 'pyclipper.MinkowskiSum2', ({(70, 43, 70, 50): 'PATTERN', (70, 52, 70, 64): '[PATH_SIGMA]', (70, 66, 70, 71): 'False'}, {}), '(PATTERN, [PATH_SIGMA], False)', False, 'import pyclipper\n'), ((74, 19, 74, 68), 'pyclipper.MinkowskiDiff', 'pyclipper.MinkowskiDiff', ({(74, 43, 74, 54): 'PATH_SUBJ_1', (74, 56, 74, 67): 'PATH_SUBJ_2'}, {}), '(PATH_SUBJ_1, PATH_SUBJ_2)', False, 'import pyclipper\n'), ((78, 19, 78, 53), 'pyclipper.ReversePath', 'pyclipper.ReversePath', ({(78, 41, 78, 52): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((83, 19, 83, 56), 'pyclipper.ReversePaths', 'pyclipper.ReversePaths', ({(83, 42, 83, 55): '[PATH_SUBJ_1]'}, {}), '([PATH_SUBJ_1])', False, 'import pyclipper\n'), ((96, 19, 96, 57), 'pyclipper.SimplifyPolygon', 'pyclipper.SimplifyPolygon', ({(96, 45, 96, 56): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((100, 19, 100, 60), 'pyclipper.SimplifyPolygons', 'pyclipper.SimplifyPolygons', ({(100, 46, 100, 59): '[PATH_SUBJ_1]'}, {}), '([PATH_SUBJ_1])', False, 'import pyclipper\n'), ((101, 26, 101, 64), 'pyclipper.SimplifyPolygon', 'pyclipper.SimplifyPolygon', ({(101, 52, 101, 63): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((107, 19, 107, 54), 'pyclipper.CleanPolygon', 'pyclipper.CleanPolygon', ({(107, 42, 107, 53): 'PATH_CLIP_1'}, {}), '(PATH_CLIP_1)', False, 'import pyclipper\n'), ((111, 19, 111, 57), 'pyclipper.CleanPolygons', 'pyclipper.CleanPolygons', ({(111, 43, 111, 56): '[PATH_CLIP_1]'}, {}), '([PATH_CLIP_1])', False, 'import pyclipper\n'), ((118, 15, 118, 37), 'pyclipper.PyPolyNode', 'pyclipper.PyPolyNode', ({}, {}), '()', False, 'import pyclipper\n'), ((122, 16, 122, 38), 'pyclipper.PyPolyNode', 'pyclipper.PyPolyNode', ({}, {}), '()', False, 'import pyclipper\n'), ((128, 16, 128, 38), 'pyclipper.PyPolyNode', 'pyclipper.PyPolyNode', ({}, {}), '()', False, 'import pyclipper\n'), ((134, 17, 134, 39), 'pyclipper.PyPolyNode', 'pyclipper.PyPolyNode', ({}, {}), '()', False, 'import pyclipper\n'), ((142, 17, 142, 39), 'pyclipper.PyPolyNode', 'pyclipper.PyPolyNode', ({}, {}), '()', False, 'import pyclipper\n'), ((151, 16, 151, 52), 'pyclipper.PolyTreeToPaths', 'pyclipper.PolyTreeToPaths', ({(151, 42, 151, 51): 'self.tree'}, {}), '(self.tree)', False, 'import pyclipper\n'), ((155, 16, 155, 60), 'pyclipper.ClosedPathsFromPolyTree', 'pyclipper.ClosedPathsFromPolyTree', ({(155, 50, 155, 59): 'self.tree'}, {}), '(self.tree)', False, 'import pyclipper\n'), ((159, 16, 159, 58), 'pyclipper.OpenPathsFromPolyTree', 'pyclipper.OpenPathsFromPolyTree', ({(159, 48, 159, 57): 'self.tree'}, {}), '(self.tree)', False, 'import pyclipper\n'), ((170, 18, 170, 39), 'pyclipper.Pyclipper', 'pyclipper.Pyclipper', ({}, {}), '()', False, 'import pyclipper\n'), ((200, 13, 200, 34), 'pyclipper.Pyclipper', 'pyclipper.Pyclipper', ({}, {}), '()', False, 'import pyclipper\n'), ((215, 18, 215, 39), 'pyclipper.Pyclipper', 'pyclipper.Pyclipper', ({}, {}), '()', False, 'import pyclipper\n'), ((248, 13, 248, 34), 'pyclipper.Pyclipper', 'pyclipper.Pyclipper', ({}, {}), '()', False, 'import pyclipper\n'), ((265, 13, 265, 34), 'pyclipper.Pyclipper', 'pyclipper.Pyclipper', ({}, {}), '()', False, 'import pyclipper\n'), ((296, 13, 296, 40), 'pyclipper.PyclipperOffset', 'pyclipper.PyclipperOffset', ({}, {}), '()', False, 'import pyclipper\n'), ((303, 13, 303, 40), 'pyclipper.PyclipperOffset', 'pyclipper.PyclipperOffset', ({}, {}), '()', False, 'import pyclipper\n'), ((311, 13, 311, 40), 'pyclipper.PyclipperOffset', 'pyclipper.PyclipperOffset', ({}, {}), '()', False, 'import pyclipper\n'), ((322, 18, 322, 39), 'pyclipper.Pyclipper', 'pyclipper.Pyclipper', ({}, {}), '()', False, 'import pyclipper\n'), ((364, 14, 364, 59), 'pyclipper.scale_to_clipper', 'pyclipper.scale_to_clipper', ({(364, 41, 364, 46): 'value', (364, 48, 364, 58): 'self.scale'}, {}), '(value, self.scale)', False, 'import pyclipper\n'), ((371, 14, 371, 61), 'pyclipper.scale_from_clipper', 'pyclipper.scale_from_clipper', ({(371, 43, 371, 48): 'value', (371, 50, 371, 60): 'self.scale'}, {}), '(value, self.scale)', False, 'import pyclipper\n'), ((378, 14, 378, 51), 'pyclipper.scale_to_clipper', 'pyclipper.scale_to_clipper', ({(378, 41, 378, 50): 'self.path'}, {}), '(self.path)', False, 'import pyclipper\n'), ((385, 14, 385, 53), 'pyclipper.scale_from_clipper', 'pyclipper.scale_from_clipper', ({(385, 43, 385, 52): 'self.path'}, {}), '(self.path)', False, 'import pyclipper\n'), ((392, 14, 392, 52), 'pyclipper.scale_to_clipper', 'pyclipper.scale_to_clipper', ({(392, 41, 392, 51): 'self.paths'}, {}), '(self.paths)', False, 'import pyclipper\n'), ((400, 14, 400, 54), 'pyclipper.scale_from_clipper', 'pyclipper.scale_from_clipper', ({(400, 43, 400, 53): 'self.paths'}, {}), '(self.paths)', False, 'import pyclipper\n'), ((420, 15, 420, 47), 'pyclipper.scale_to_clipper', 'pyclipper.scale_to_clipper', ({(420, 42, 420, 46): 'path'}, {}), '(path)', False, 'import pyclipper\n'), ((44, 25, 44, 59), 'pyclipper.Orientation', 'pyclipper.Orientation', ({(44, 47, 44, 58): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((45, 24, 45, 64), 'pyclipper.Orientation', 'pyclipper.Orientation', ({(45, 46, 45, 63): 'PATH_SUBJ_1[::-1]'}, {}), '(PATH_SUBJ_1[::-1])', False, 'import pyclipper\n'), ((57, 25, 57, 74), 'pyclipper.PointInPolygon', 'pyclipper.PointInPolygon', ({(57, 50, 57, 60): '(180, 200)', (57, 62, 57, 73): 'PATH_SUBJ_1'}, {}), '((180, 200), PATH_SUBJ_1)', False, 'import pyclipper\n'), ((60, 25, 60, 74), 'pyclipper.PointInPolygon', 'pyclipper.PointInPolygon', ({(60, 50, 60, 60): '(200, 180)', (60, 62, 60, 73): 'PATH_SUBJ_1'}, {}), '((200, 180), PATH_SUBJ_1)', False, 'import pyclipper\n'), ((63, 25, 63, 74), 'pyclipper.PointInPolygon', 'pyclipper.PointInPolygon', ({(63, 50, 63, 60): '(500, 500)', (63, 62, 63, 73): 'PATH_SUBJ_1'}, {}), '((500, 500), PATH_SUBJ_1)', False, 'import pyclipper\n'), ((207, 17, 207, 44), 'pyclipper.PyclipperOffset', 'pyclipper.PyclipperOffset', ({}, {}), '()', False, 'import pyclipper\n'), ((326, 12, 326, 46), 'pyclipper.Orientation', 'pyclipper.Orientation', ({(326, 34, 326, 45): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((330, 12, 330, 39), 'pyclipper.Area', 'pyclipper.Area', ({(330, 27, 330, 38): 'PATH_SUBJ_1'}, {}), '(PATH_SUBJ_1)', False, 'import pyclipper\n'), ((338, 12, 338, 62), 'pyclipper.MinkowskiSum', 'pyclipper.MinkowskiSum', ({(338, 35, 338, 42): 'PATTERN', (338, 44, 338, 54): 'PATH_SIGMA', (338, 56, 338, 61): '(False)'}, {}), '(PATTERN, PATH_SIGMA, False)', False, 'import pyclipper\n'), ((342, 12, 342, 65), 'pyclipper.MinkowskiSum2', 'pyclipper.MinkowskiSum2', ({(342, 36, 342, 43): 'PATTERN', (342, 45, 342, 57): '[PATH_SIGMA]', (342, 59, 342, 64): '(False)'}, {}), '(PATTERN, [PATH_SIGMA], False)', False, 'import pyclipper\n'), ((346, 12, 346, 61), 'pyclipper.MinkowskiDiff', 'pyclipper.MinkowskiDiff', ({(346, 36, 346, 47): 'PATH_SUBJ_1', (346, 49, 346, 60): 'PATH_SUBJ_2'}, {}), '(PATH_SUBJ_1, PATH_SUBJ_2)', False, 'import pyclipper\n'), ((418, 16, 418, 26), 'sympy.Point2D', 'Point2D', ({(418, 24, 418, 25): 'v'}, {}), '(v)', False, 'from sympy import Point2D\n'), ((307, 29, 307, 70), 'pyclipper.OpenPathsFromPolyTree', 'pyclipper.OpenPathsFromPolyTree', ({(307, 61, 307, 69): 'solution'}, {}), '(solution)', False, 'import pyclipper\n'), ((308, 29, 308, 72), 'pyclipper.ClosedPathsFromPolyTree', 'pyclipper.ClosedPathsFromPolyTree', ({(308, 63, 308, 71): 'solution'}, {}), '(solution)', False, 'import pyclipper\n'), ((334, 29, 334, 78), 'pyclipper.PointInPolygon', 'pyclipper.PointInPolygon', ({(334, 54, 334, 64): '(180, 200)', (334, 66, 334, 77): 'PATH_SUBJ_1'}, {}), '((180, 200), PATH_SUBJ_1)', False, 'import pyclipper\n')]