1"""
2Cython wrapper for the C++ translation of the Angus Johnson's Clipper
3library (ver. 6.2.1) (http://www.angusj.com/delphi/clipper.php)
4
5This wrapper was written by Maxime Chalton, Lukas Treyer and Gregor Ratajc.
6
7"""
8
9SILENT = True
10
11"""
12SCALING_FACTOR has been deprecated. See https://github.com/greginvm/pyclipper/wiki/Deprecating-SCALING_FACTOR
13for an explanation.
14"""
15SCALING_FACTOR = 1
16
17
18def log_action(description):
19    if not SILENT:
20        print description
21
22log_action("Python binding clipper library")
23
24import sys as _sys
25import struct
26import copy as _copy
27import unicodedata as _unicodedata
28import time as _time
29import warnings as _warnings
30import numbers as _numbers
31
32from cython.operator cimport dereference as deref
33
34cdef extern from "Python.h":
35    Py_INCREF(object o)
36    object Py_BuildValue(char *format, ...)
37    object PyBuffer_FromMemory(void *ptr, int size)
38    #int PyArg_ParseTuple(object struct,void* ptr)
39    char*PyString_AsString(object string)
40    int PyArg_VaParse(object args, char *format, ...)
41    int PyArg_Parse(object args, char *format, ...)
42    int PyObject_AsReadBuffer(object obj, void*buffer, int*buffer_len)
43    object PyBuffer_FromObject(object base, int offset, int size)
44    object PyBuffer_FromReadWriteObject(object base, int offset, int size)
45    PyBuffer_New(object o)
46
47
48cdef extern from "stdio.h":
49    cdef void printf(char*, ...)
50
51cdef extern from "stdlib.h":
52    cdef void*malloc(unsigned int size)
53    cdef void*free(void*p)
54    char *strdup(char *str)
55    int strcpy(void*str, void*src)
56    int memcpy(void*str, void*src, int size)
57
58from libcpp.vector cimport vector
59
60cdef extern from "extra_defines.hpp":
61    cdef int _USE_XYZ
62
63cdef extern from "clipper.hpp" namespace "ClipperLib":
64
65    # enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
66    cdef enum ClipType:
67        ctIntersection = 1,
68        ctUnion = 2,
69        ctDifference = 3,
70        ctXor = 4
71
72    # enum PolyType { ptSubject, ptClip };
73    cdef enum PolyType:
74        ptSubject = 1,
75        ptClip = 2
76
77    # By far the most widely used winding rules for polygon filling are
78    # EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
79    # Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
80    # see http://glprogramming.com/red/chapter11.html
81    # enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
82    cdef enum PolyFillType:
83        pftEvenOdd = 1,
84        pftNonZero = 2,
85        pftPositive = 3,
86        pftNegative = 4
87
88    # The correct type definition is taken from cpp source, so
89    # the use_int32 is handled correctly.
90    # If you need 32 bit ints, just uncomment //#define use_int32 in clipper.hpp
91    # and recompile
92    ctypedef signed long long cInt
93    ctypedef signed long long long64
94    ctypedef unsigned long long ulong64
95
96    ctypedef char bool
97
98    # TODO: handle "use_xyz" that adds Z coordinate
99    cdef struct IntPoint:
100        cInt X
101        cInt Y
102
103    #typedef std::vector< IntPoint > Path;
104    cdef cppclass Path:
105        Path()
106        void push_back(IntPoint &)
107        IntPoint& operator[](int)
108        IntPoint& at(int)
109        int size()
110
111    #typedef std::vector< Path > Paths;
112    cdef cppclass Paths:
113        Paths()
114        void push_back(Path &)
115        Path& operator[](int)
116        Path& at(int)
117        int size()
118
119    cdef cppclass PolyNode:
120        PolyNode()
121        Path Contour
122        PolyNodes Childs
123        PolyNode*Parent
124        PolyNode*GetNext()
125        bool IsHole()
126        bool IsOpen()
127        int ChildCount()
128
129    cdef cppclass PolyNodes:
130        PolyNodes()
131        void push_back(PolyNode &)
132        PolyNode*operator[](int)
133        PolyNode*at(int)
134        int size()
135
136    cdef cppclass PolyTree(PolyNode):
137        PolyTree()
138        PolyNode& GetFirst()
139        void Clear()
140        int Total()
141
142    #enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
143    cdef enum InitOptions:
144        ioReverseSolution = 1,
145        ioStrictlySimple = 2,
146        ioPreserveCollinear = 4
147
148    #enum JoinType { jtSquare, jtRound, jtMiter };
149    cdef enum JoinType:
150        jtSquare = 1,
151        jtRound = 2,
152        jtMiter = 3
153
154    #enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
155    cdef enum EndType:
156        etClosedPolygon = 1,
157        etClosedLine = 2,
158        etOpenButt = 3,
159        etOpenSquare = 4,
160        etOpenRound = 5
161
162    cdef struct IntRect:
163        cInt left
164        cInt top
165        cInt right
166        cInt bottom
167
168    cdef cppclass Clipper:
169        Clipper(int initOptions)
170        Clipper()
171        #~Clipper()
172        void Clear()
173        bool Execute(ClipType clipType, Paths & solution, PolyFillType subjFillType, PolyFillType clipFillType) nogil
174        bool Execute(ClipType clipType, PolyTree & solution, PolyFillType subjFillType, PolyFillType clipFillType) nogil
175        bool ReverseSolution()
176        void ReverseSolution(bool value)
177        bool StrictlySimple()
178        void StrictlySimple(bool value)
179        bool PreserveCollinear()
180        void PreserveCollinear(bool value)
181        bool AddPath(Path & path, PolyType polyType, bool closed)
182        bool AddPaths(Paths & paths, PolyType polyType, bool closed)
183        IntRect GetBounds() nogil
184
185    cdef cppclass ClipperOffset:
186        ClipperOffset(double miterLimit, double roundPrecision)
187        ClipperOffset(double miterLimit)
188        ClipperOffset()
189        #~ClipperOffset()
190        void AddPath(Path & path, JoinType joinType, EndType endType)
191        void AddPaths(Paths & paths, JoinType joinType, EndType endType)
192        void Execute(Paths & solution, double delta) nogil
193        void Execute(PolyTree & solution, double delta) nogil
194        void Clear()
195        double MiterLimit
196        double ArcTolerance
197
198    # prefixes are added to original functions to prevent naming collisions
199    bool c_Orientation "Orientation"(const Path & poly) nogil
200    double c_Area "Area"(const Path & poly) nogil
201    int c_PointInPolygon "PointInPolygon"(const IntPoint & pt, const Path & path) nogil
202
203    # In the following 4 functions default values for fillType and distance were removed
204    # because it caused a bug in C++ code generated by Cython.
205    # See Cython bug report: http://trac.cython.org/ticket/816
206    void c_SimplifyPolygon "SimplifyPolygon"(const Path & in_poly, Paths & out_polys, PolyFillType fillType) nogil
207    void c_SimplifyPolygons "SimplifyPolygons"(const Paths & in_polys, Paths & out_polys, PolyFillType fillType) nogil
208    void c_CleanPolygon "CleanPolygon"(const Path& in_poly, Path& out_poly, double distance) nogil
209    void c_CleanPolygons "CleanPolygons"(Paths& polys, double distance) nogil
210
211    void c_MinkowskiSum "MinkowskiSum"(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) nogil
212    void c_MinkowskiSum "MinkowskiSum"(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) nogil
213    void c_MinkowskiDiff "MinkowskiDiff"(const Path& poly1, const Path& poly2, Paths& solution) nogil
214
215    void c_PolyTreeToPaths "PolyTreeToPaths"(const PolyTree& polytree, Paths& paths)
216    void c_ClosedPathsFromPolyTree "ClosedPathsFromPolyTree"(const PolyTree& polytree, Paths& paths)
217    void c_OpenPathsFromPolyTree "OpenPathsFromPolyTree"(PolyTree& polytree, Paths& paths)
218
219    void c_ReversePath "ReversePath"(Path& p) nogil
220    void c_ReversePaths "ReversePaths"(Paths& p) nogil
221
222#============================= Enum mapping ================
223
224JT_SQUARE = jtSquare
225JT_ROUND = jtRound
226JT_MITER = jtMiter
227
228ET_CLOSEDPOLYGON = etClosedPolygon
229ET_CLOSEDLINE = etClosedLine
230ET_OPENBUTT = etOpenButt
231ET_OPENSQUARE = etOpenSquare
232ET_OPENROUND = etOpenRound
233
234CT_INTERSECTION = ctIntersection
235CT_UNION = ctUnion
236CT_DIFFERENCE = ctDifference
237CT_XOR = ctXor
238
239PT_SUBJECT = ptSubject
240PT_CLIP = ptClip
241
242PFT_EVENODD = pftEvenOdd
243PFT_NONZERO = pftNonZero
244PFT_POSITIVE = pftPositive
245PFT_NEGATIVE = pftNegative
246
247#=============================  PyPolyNode =================
248class PyPolyNode:
249    """
250    Represents ClipperLibs' PolyTree and PolyNode data structures.
251    """
252    def __init__(self):
253        self.Contour = []
254        self.Childs = []
255        self.Parent = None
256        self.IsHole = False
257        self.IsOpen = False
258        self.depth = 0
259
260#=============================  Other objects ==============
261from collections import namedtuple
262PyIntRect = namedtuple('PyIntRect', ['left', 'top', 'right', 'bottom'])
263
264class ClipperException(Exception):
265    pass
266
267#============================= Namespace functions =========
268def Orientation(poly):
269    """ Get orientation of the supplied polygon.
270    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/Orientation.htm
271
272    Keyword arguments:
273    poly -- closed polygon
274
275    Returns:
276    True  -- counter-clockwise orientation
277    False -- clockwise orientation
278    """
279    cdef Path c_path = _to_clipper_path(poly)
280    cdef bint result
281    with nogil:
282        result = <bint>c_Orientation(c_path)
283    return result
284
285
286def Area(poly):
287    """ Get area of the supplied polygon.
288    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/Area.htm
289
290    Keyword arguments:
291    poly -- closed polygon
292
293    Returns:
294    Positive number if orientation is True
295    Negative number if orientation is False
296    """
297
298    cdef Path c_path = _to_clipper_path(poly)
299    cdef double result
300    with nogil:
301        result = <double>c_Area(c_path)
302    return result
303
304
305def PointInPolygon(point, poly):
306    """ Determine where does the point lie regarding the provided polygon.
307    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/PointInPolygon.htm
308
309    Keyword arguments:
310    point -- point in question
311    poly  -- closed polygon
312
313    Returns:
314    0  -- point is not in polygon
315    -1 -- point is on polygon
316    1  -- point is in polygon
317    """
318
319    cdef IntPoint c_point = _to_clipper_point(point)
320    cdef Path c_path = _to_clipper_path(poly)
321    cdef int result
322    with nogil:
323        result = <int>c_PointInPolygon(c_point, c_path)
324    return result
325
326
327def SimplifyPolygon(poly, PolyFillType fill_type=pftEvenOdd):
328    """ Removes self-intersections from the supplied polygon.
329    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm
330
331    Keyword arguments:
332    poly      -- polygon to be simplified
333    fill_type -- PolyFillType used with the boolean union operation
334
335    Returns:
336    list of simplified polygons (containing one or more polygons)
337    """
338    cdef Paths out_polys
339    cdef Path c_path = _to_clipper_path(poly)
340    with nogil:
341        c_SimplifyPolygon(c_path, out_polys, fill_type)
342    return _from_clipper_paths(out_polys)
343
344
345def SimplifyPolygons(polys, PolyFillType fill_type=pftEvenOdd):
346    """ Removes self-intersections from the supplied polygons.
347    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygons.htm
348
349    Keyword arguments:
350    polys     -- polygons to be simplified
351    fill_type -- PolyFillType used with the boolean union operation
352
353    Returns:
354    list of simplified polygons
355    """
356    cdef Paths out_polys
357    cdef Paths c_polys = _to_clipper_paths(polys)
358    with nogil:
359        c_SimplifyPolygons(c_polys, out_polys, fill_type)
360    return _from_clipper_paths(out_polys)
361
362
363def CleanPolygon(poly, double distance=1.415):
364    """ Removes unnecessary vertices from the provided polygon.
365    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/CleanPolygon.htm
366
367    Keyword arguments:
368    poly     -- polygon to be cleaned
369    distance -- distance on which vertices are removed, see 'More info' (default: approx. sqrt of 2)
370
371    Returns:
372    cleaned polygon
373    """
374    cdef Path out_poly
375    cdef Path c_path = _to_clipper_path(poly)
376    with nogil:
377        c_CleanPolygon(c_path, out_poly, distance)
378    return _from_clipper_path(out_poly)
379
380
381def CleanPolygons(polys, double distance=1.415):
382    """ Removes unnecessary vertices from the provided polygons.
383    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/CleanPolygons.htm
384
385    Keyword arguments:
386    polys    -- polygons to be cleaned
387    distance -- distance on which vertices are removed, see 'More info' (default: approx. sqrt of 2)
388
389    Returns:
390    list of cleaned polygons
391    """
392    cdef Paths out_polys = _to_clipper_paths(polys)
393    with nogil:
394        c_CleanPolygons(out_polys, distance)
395    return _from_clipper_paths(out_polys)
396
397
398def MinkowskiSum(pattern, path, bint path_is_closed):
399    """ Performs Minkowski Addition of the pattern and path.
400    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/MinkowskiSum.htm
401
402    Keyword arguments:
403    pattern        -- polygon whose points are added to the path
404    path           -- open or closed path
405    path_is_closed -- set to True if passed path is closed, False if open
406
407    Returns:
408    list of polygons (containing one or more polygons)
409    """
410    cdef Paths solution
411    cdef Path c_pat = _to_clipper_path(pattern)
412    cdef Path c_path = _to_clipper_path(path)
413    with nogil:
414        c_MinkowskiSum(c_pat, c_path, solution, path_is_closed)
415    return _from_clipper_paths(solution)
416
417
418def MinkowskiSum2(pattern, paths, bint path_is_closed):
419    """ Performs Minkowski Addition of the pattern and paths.
420    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/MinkowskiSum.htm
421
422    Keyword arguments:
423    pattern        -- polygon whose points are added to the paths
424    paths          -- open or closed paths
425    path_is_closed -- set to True if passed paths are closed, False if open
426
427    Returns:
428    list of polygons
429    """
430    cdef Paths solution
431    cdef Path c_pat = _to_clipper_path(pattern)
432    cdef Paths c_paths = _to_clipper_paths(paths)
433    with nogil:
434        c_MinkowskiSum(c_pat, c_paths, solution, path_is_closed)
435    return _from_clipper_paths(solution)
436
437
438def MinkowskiDiff(poly1, poly2):
439    """ Performs Minkowski Difference.
440    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/MinkowskiDiff.htm
441
442    Keyword arguments:
443    poly1 -- polygon
444    poly2 -- polygon
445
446    Returns:
447    list of polygons
448    """
449    cdef Paths solution
450    cdef Path c_path1 = _to_clipper_path(poly1)
451    cdef Path c_path2 = _to_clipper_path(poly2)
452    with nogil:
453        c_MinkowskiDiff(c_path1, c_path2, solution)
454    return _from_clipper_paths(solution)
455
456
457def PolyTreeToPaths(poly_node):
458    """ Converts a PyPolyNode to a list of paths.
459    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/PolyTreeToPaths.htm
460
461    Keyword arguments:
462    py_poly_node -- PyPolyNode to be filtered
463
464    Returns:
465    list of paths
466    """
467    paths = []
468    _filter_polynode(poly_node, paths, filter_func=None)
469    return paths
470
471
472def ClosedPathsFromPolyTree(poly_node):
473    """ Filters out open paths from the PyPolyNode and returns only closed paths.
474    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/ClosedPathsFromPolyTree.htm
475
476    Keyword arguments:
477    py_poly_node -- PyPolyNode to be filtered
478
479    Returns:
480    list of closed paths
481    """
482
483    paths = []
484    _filter_polynode(poly_node, paths, filter_func=lambda pn: not pn.IsOpen)
485    return paths
486
487
488def OpenPathsFromPolyTree(poly_node):
489    """ Filters out closed paths from the PyPolyNode and returns only open paths.
490    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/OpenPathsFromPolyTree.htm
491
492    Keyword arguments:
493    py_poly_node -- PyPolyNode to be filtered
494
495    Returns:
496    list of open paths
497    """
498    paths = []
499    _filter_polynode(poly_node, paths, filter_func=lambda pn: pn.IsOpen)
500    return paths
501
502
503def ReversePath(path):
504    """ Reverses the vertex order (and hence orientation) in the specified path.
505    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/ReversePath.htm
506
507    Note: Might be more effective to reverse the path outside of this package (eg. via [::-1] on a list)
508    so there is no unneeded conversions to internal structures of this package.
509
510    Keyword arguments:
511    path -- path to be reversed
512
513    Returns:
514    reversed path
515    """
516    cdef Path c_path = _to_clipper_path(path)
517    with nogil:
518        c_ReversePath(c_path)
519    return _from_clipper_path(c_path)
520
521
522def ReversePaths(paths):
523    """ Reverses the vertex order (and hence orientation) in each path.
524    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/ReversePaths.htm
525
526    Note: Might be more effective to reverse each path outside of this package (eg. via [::-1] on a list)
527    so there is no unneeded conversions to internal structures of this package.
528
529    Keyword arguments:
530    paths -- paths to be reversed
531
532    Returns:
533    list if reversed paths
534    """
535    cdef Paths c_paths = _to_clipper_paths(paths)
536    with nogil:
537        c_ReversePaths(c_paths)
538    return _from_clipper_paths(c_paths)
539
540
541def scale_to_clipper(path_or_paths, scale = 2 ** 31):
542    """
543    Take a path or list of paths with coordinates represented by floats and scale them using the specified factor.
544    This function can be user to convert paths to a representation which is more appropriate for Clipper.
545
546    Clipper, and thus Pyclipper, uses 64-bit integers to represent coordinates internally. The actual supported
547    range (+/- 2 ** 62) is a bit smaller than the maximal values for this type. To operate on paths which use
548    fractional coordinates, it is necessary to translate them from and to a representation which does not depend
549    on floats. This can be done using this function and it's reverse, `scale_from_clipper()`.
550
551    For details, see http://www.angusj.com/delphi/clipper/documentation/Docs/Overview/Rounding.htm.
552
553    For example, to perform a clip operation on two polygons, the arguments to `Pyclipper.AddPath()` need to be wrapped
554    in `scale_to_clipper()` while the return value needs to be converted back with `scale_from_clipper()`:
555
556    >>> pc = Pyclipper()
557    >>> path = [[0, 0], [1, 0], [1 / 2, (3 / 4) ** (1 / 2)]] # A triangle.
558    >>> clip = [[0, 1 / 3], [1, 1 / 3], [1, 2 / 3], [0, 1 / 3]] # A rectangle.
559    >>> pc.AddPath(scale_to_clipper(path), PT_SUBJECT)
560    >>> pc.AddPath(scale_to_clipper(clip), PT_CLIP)
561    >>> scale_from_clipper(pc.Execute(CT_INTERSECTION))
562    [[[0.6772190444171429, 0.5590730146504939], [0.2383135547861457, 0.41277118446305394],
563      [0.19245008938014507, 0.3333333330228925], [0.8075499106198549, 0.3333333330228925]]]
564
565    :param path_or_paths: Either a list of paths or a path. A path is a list of tuples of numbers.
566    :param scale: The factor with which to multiply coordinates before converting rounding them to ints. The default
567    will give you a range of +/- 2 ** 31 with a precision of 2 ** -31.
568    """
569
570    def scale_value(x):
571        if hasattr(x, "__len__"):
572            return [scale_value(i) for i in x]
573        else:
574            return <cInt>(<double>x * scale)
575
576    return scale_value(path_or_paths)
577
578
579def scale_from_clipper(path_or_paths, scale = 2 ** 31):
580    """
581    Take a path or list of paths with coordinates represented by ints and scale them back to a fractional
582    representation. This function does the inverse of `scale_to_clipper()`.
583
584    :param path_or_paths: Either a list of paths or a path. A path is a list of tuples of numbers.
585    :param scale: The factor by which to divide coordinates when converting them to floats.
586    """
587
588    def scale_value(x):
589        if hasattr(x, "__len__"):
590            return [scale_value(i) for i in x]
591        else:
592            return <double>x / scale
593
594    return scale_value(path_or_paths)
595
596
597cdef class Pyclipper:
598
599    """Wraps the Clipper class.
600
601    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/_Body.htm
602    """
603    cdef Clipper *thisptr  # hold a C++ instance which we're wrapping
604    def __cinit__(self):
605        """ Creates an instance of the Clipper class. InitOptions from the Clipper class
606        are substituted with separate properties.
607
608        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Constructor.htm
609        """
610
611        log_action("Creating a Clipper instance")
612        self.thisptr = new Clipper()
613
614    def __dealloc__(self):
615        log_action("Deleting the Clipper instance")
616        del self.thisptr
617
618    def AddPath(self, path, PolyType poly_type, closed=True):
619        """ Add individual path.
620        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPath.htm
621
622        Keyword arguments:
623        path      -- path to be added
624        poly_type -- type of the added path - subject or clip
625        closed    -- True if the added path is closed, False if open
626
627        Returns:
628        True -- path is valid for clipping and was added
629
630        Raises:
631        ClipperException -- if path is invalid for clipping
632        """
633        cdef Path c_path = _to_clipper_path(path)
634        cdef bint result = <bint> self.thisptr.AddPath(c_path, poly_type, <bint> closed)
635        if not result:
636            raise ClipperException('The path is invalid for clipping')
637        return result
638
639    def AddPaths(self, paths, PolyType poly_type, closed=True):
640        """ Add a list of paths.
641        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/AddPaths.htm
642
643        Keyword arguments:
644        paths     -- paths to be added
645        poly_type -- type of added paths - subject or clip
646        closed    -- True if added paths are closed, False if open
647
648        Returns:
649        True -- all or some paths are valid for clipping and were added
650
651        Raises:
652        ClipperException -- all paths are invalid for clipping
653        """
654        cdef Paths c_paths = _to_clipper_paths(paths)
655        cdef bint result = <bint> self.thisptr.AddPaths(c_paths, poly_type, <bint> closed)
656        if not result:
657            raise ClipperException('All paths are invalid for clipping')
658        return result
659
660    def Clear(self):
661        """ Removes all subject and clip polygons.
662        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/Clear.htm
663        """
664        self.thisptr.Clear()
665
666    def GetBounds(self):
667        """ Returns an axis-aligned bounding rectangle that bounds all added polygons.
668        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperBase/Methods/GetBounds.htm
669
670        Returns:
671        PyIntRect with left, right, bottom, top vertices that define the axis-aligned bounding rectangle.
672        """
673        _check_scaling_factor()
674
675        cdef IntRect rr
676        with nogil:
677            rr = <IntRect> self.thisptr.GetBounds()
678        return PyIntRect(left=rr.left, top=rr.top,
679                         right=rr.right, bottom=rr.bottom)
680
681    def Execute(self, ClipType clip_type,
682                PolyFillType subj_fill_type=pftEvenOdd, PolyFillType clip_fill_type=pftEvenOdd):
683        """ Performs the clipping operation and returns a list of paths.
684        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Execute.htm
685
686        Keyword arguments:
687        clip_type      -- type of the clipping operation
688        subj_fill_type -- fill rule of subject paths
689        clip_fill_type -- fill rule of clip paths
690
691        Returns:
692        list of resulting paths
693
694        Raises:
695        ClipperException -- operation did not succeed
696        """
697
698        cdef Paths solution
699        cdef bint success
700        with nogil:
701            success = <bint> self.thisptr.Execute(clip_type, solution, subj_fill_type, clip_fill_type)
702        if not success:
703            raise ClipperException('Execution of clipper did not succeed!')
704        return _from_clipper_paths(solution)
705
706    def Execute2(self, ClipType clip_type,
707                 PolyFillType subj_fill_type=pftEvenOdd, PolyFillType clip_fill_type=pftEvenOdd):
708        """ Performs the clipping operation and returns a PyPolyNode.
709        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Methods/Execute.htm
710
711        Keyword arguments:
712        clip_type      -- type of the clipping operation
713        subj_fill_type -- fill rule of subject paths
714        clip_fill_type -- fill rule of clip paths
715
716        Returns:
717        PyPolyNode
718
719        Raises:
720        ClipperException -- operation did not succeed
721        """
722        cdef PolyTree solution
723        cdef bint success
724        with nogil:
725            success = <bint> self.thisptr.Execute(clip_type, solution, subj_fill_type, clip_fill_type)
726        if not success:
727            raise ClipperException('Execution of clipper did not succeed!')
728        return _from_poly_tree(solution)
729
730    property ReverseSolution:
731        """ Should polygons returned from Execute/Execute2 have their orientations
732        opposite to their normal orientations.
733        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/ReverseSolution.htm
734        """
735        def __get__(self):
736            return <bint> self.thisptr.ReverseSolution()
737
738        def __set__(self, value):
739            self.thisptr.ReverseSolution(<bint> value)
740
741    property PreserveCollinear:
742        """ Should clipper preserve collinear vertices.
743        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/PreserveCollinear.htm
744        """
745        def __get__(self):
746            return <bint> self.thisptr.PreserveCollinear()
747
748        def __set__(self, value):
749            self.thisptr.PreserveCollinear(<bint> value)
750
751    property StrictlySimple:
752        """ Should polygons returned from Execute/Execute2 be strictly simple (True) or may be weakly simple (False).
753        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/StrictlySimple.htm
754        """
755        def __get__(self):
756            return <bint> self.thisptr.StrictlySimple()
757
758        def __set__(self, value):
759            self.thisptr.StrictlySimple(<bint> value)
760
761
762cdef class PyclipperOffset:
763    """ Wraps the ClipperOffset class.
764
765    More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/_Body.htm
766    """
767    cdef ClipperOffset *thisptr
768
769    def __cinit__(self, double miter_limit=2.0, double arc_tolerance=0.25):
770        """ Creates an instance of the ClipperOffset class.
771
772        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Methods/Constructor.htm
773        """
774        log_action("Creating an ClipperOffset instance")
775        self.thisptr = new ClipperOffset(miter_limit, arc_tolerance)
776
777    def __dealloc__(self):
778        log_action("Deleting the ClipperOffset instance")
779        del self.thisptr
780
781    def AddPath(self, path, JoinType join_type, EndType end_type):
782        """ Add individual path.
783        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Methods/AddPath.htm
784
785        Keyword arguments:
786        path      -- path to be added
787        join_type -- join type of added path
788        end_type  -- end type of added path
789        """
790        cdef Path c_path = _to_clipper_path(path)
791        self.thisptr.AddPath(c_path, join_type, end_type)
792
793    def AddPaths(self, paths, JoinType join_type, EndType end_type):
794        """ Add a list of paths.
795        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Methods/AddPaths.htm
796
797        Keyword arguments:
798        path      -- paths to be added
799        join_type -- join type of added paths
800        end_type  -- end type of added paths
801        """
802        cdef Paths c_paths = _to_clipper_paths(paths)
803        self.thisptr.AddPaths(c_paths, join_type, end_type)
804
805    def Execute(self, double delta):
806        """ Performs the offset operation and returns a list of offset paths.
807        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Methods/Execute.htm
808
809        Keyword arguments:
810        delta -- amount to which the supplied paths will be offset - negative delta shrinks polygons,
811                 positive delta expands them.
812
813        Returns:
814        list of offset paths
815        """
816        cdef Paths c_solution
817        with nogil:
818            self.thisptr.Execute(c_solution, delta)
819        return _from_clipper_paths(c_solution)
820
821    def Execute2(self, double delta):
822        """ Performs the offset operation and returns a PyPolyNode with offset paths.
823        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Methods/Execute.htm
824
825        Keyword arguments:
826        delta -- amount to which the supplied paths will be offset - negative delta shrinks polygons,
827                 positive delta expands them.
828
829        Returns:
830        PyPolyNode
831        """
832        cdef PolyTree solution
833        with nogil:
834            self.thisptr.Execute(solution, delta)
835        return _from_poly_tree(solution)
836
837    def Clear(self):
838        """ Clears all paths.
839
840        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Methods/Clear.htm
841        """
842        self.thisptr.Clear()
843
844    property MiterLimit:
845        """ Maximum distance in multiples of delta that vertices can be offset from their
846        original positions.
847
848        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/MiterLimit.htm
849        """
850        def __get__(self):
851            return <double> self.thisptr.MiterLimit
852
853        def __set__(self, value):
854            self.thisptr.MiterLimit = <double> value
855
856    property ArcTolerance:
857        """ Maximum acceptable imprecision when arcs are approximated in
858        an offsetting operation.
859
860        More info: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
861        """
862        def __get__(self):
863            _check_scaling_factor()
864
865            return self.thisptr.ArcTolerance
866
867        def __set__(self, value):
868            _check_scaling_factor()
869
870            self.thisptr.ArcTolerance = value
871
872
873cdef _filter_polynode(pypolynode, result, filter_func=None):
874    if (filter_func is None or filter_func(pypolynode)) and len(pypolynode.Contour) > 0:
875        result.append(pypolynode.Contour)
876
877    for child in pypolynode.Childs:
878        _filter_polynode(child, result, filter_func)
879
880
881cdef _from_poly_tree(PolyTree &c_polytree):
882    poly_tree = PyPolyNode()
883    depths = [0]
884    for i in xrange(c_polytree.ChildCount()):
885        c_child = c_polytree.Childs[i]
886        py_child = _node_walk(c_child, poly_tree)
887        poly_tree.Childs.append(py_child)
888        depths.append(py_child.depth + 1)
889    poly_tree.depth = max(depths)
890    return poly_tree
891
892
893cdef _node_walk(PolyNode *c_polynode, object parent):
894
895    py_node = PyPolyNode()
896    py_node.Parent = parent
897
898    cdef object ishole = <bint>c_polynode.IsHole()
899    py_node.IsHole = ishole
900
901    cdef object isopen = <bint>c_polynode.IsOpen()
902    py_node.IsOpen = isopen
903
904    py_node.Contour = _from_clipper_path(c_polynode.Contour)
905
906    # kids
907    cdef PolyNode *cNode
908    depths = [0]
909    for i in range(c_polynode.ChildCount()):
910        c_node = c_polynode.Childs[i]
911        py_child = _node_walk(c_node, py_node)
912
913        depths.append(py_child.depth + 1)
914        py_node.Childs.append(py_child)
915
916    py_node.depth = max(depths)
917
918    return py_node
919
920
921cdef Paths _to_clipper_paths(object polygons):
922    cdef Paths paths = Paths()
923    for poly in polygons:
924        paths.push_back(_to_clipper_path(poly))
925    return paths
926
927
928cdef Path _to_clipper_path(object polygon):
929    _check_scaling_factor()
930
931    cdef Path path = Path()
932    cdef IntPoint p
933    for v in polygon:
934        path.push_back(_to_clipper_point(v))
935    return path
936
937
938cdef IntPoint _to_clipper_point(object py_point):
939    return IntPoint(py_point[0], py_point[1])
940
941
942cdef object _from_clipper_paths(Paths paths):
943
944    polys = []
945
946    cdef Path path
947    for i in xrange(paths.size()):
948        path = paths[i]
949        polys.append(_from_clipper_path(path))
950
951    return polys
952
953
954cdef object _from_clipper_path(Path path):
955    _check_scaling_factor()
956
957    poly = []
958    cdef IntPoint point
959    for i in xrange(path.size()):
960        point = path[i]
961        poly.append([point.X, point.Y])
962    return poly
963
964
965def _check_scaling_factor():
966    """
967    Check whether SCALING_FACTOR has been set by the code using this library and warn the user that it has been
968    deprecated and it's value is ignored.
969    """
970
971    if SCALING_FACTOR != 1:
972        _warnings.warn('SCALING_FACTOR is deprecated and it\'s value is ignored. See https://github.com/greginvm/pyclipper/wiki/Deprecating-SCALING_FACTOR for more information.', DeprecationWarning)
973