Coverage for pygeodesy/booleans.py : 92%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*-
Classes L{BooleanFHP} and L{BooleanGH} are I{composites} and provide I{boolean} operations C{intersection}, C{difference}, C{reverse-difference} and C{union}.
@note: A I{clip} is defined as a single, usually closed polygon, a I{composite} is a collection of one or more I{clip}s.
@see: U{Forster-Hormann-Popa<https://www.ScienceDirect.com/science/ article/pii/S259014861930007X>} and U{Greiner-Hormann <http://www.inf.USI.CH/hormann/papers/Greiner.1998.ECO.pdf>}. ''' # make sure int/int division yields float quotient, see .basics
_e_, _ELLIPSIS_, _few_, _height_, _lat_, \ _lon_, _name_, _scalar_, _SPACE_, _too_, \ _X_, _x_, _B_, _d_, _R_ # PYCHOK used! # from pygeodesy.props import Property_RO, property_RO # from .fsums # from pygeodesy.streprs import Fmt, pairs # from .named
# from math import fabs # from .fmath
# Intersection labels # Entry/Exit flags EXIT: ENTRY, None: None}
# RelativePosition types
(_RP.RIGHT, _RP.LEFT): _L.CROSSING, (_RP.LEFT, _RP.LEFT): _L.BOUNCING, (_RP.RIGHT, _RP.RIGHT): _L.BOUNCING, # overlapping cases (_RP.RIGHT, _RP.IS_Pp): _L.LEFT_ON, (_RP.IS_Pp, _RP.RIGHT): _L.LEFT_ON, (_RP.LEFT, _RP.IS_Pp): _L.RIGHT_ON, (_RP.IS_Pp, _RP.LEFT): _L.RIGHT_ON, (_RP.IS_Pm, _RP.IS_Pp): _L.ON_ON, (_RP.IS_Pp, _RP.IS_Pm): _L.ON_ON, (_RP.IS_Pm, _RP.RIGHT): _L.ON_LEFT, (_RP.RIGHT, _RP.IS_Pm): _L.ON_LEFT, (_RP.LEFT, _RP.IS_Pm): _L.ON_RIGHT, (_RP.IS_Pm, _RP.LEFT): _L.ON_RIGHT}
# Return 4-tuple (alpha, abs(alpha) near 0, 0 < alpha < 1, not 0 < alpha < 1) (a, False, False, True) if _0_EPS < fabs(a) else (a, True, False, False))
'''(INTERNAL) Return 2-tuple C{(min, max)}, oversized. '''
'''(INTERNAL) Is C{(x1, x2)} outside C{(lo, hi)}? '''
'''(INTERNAL) Base class for L{LatLonFHP} and L{LatLonGH}. ''' # _e_x_str = NN # shut up PyChecker
'''New C{LatLon[FHP|GH]} from separate C{lat}, C{lon}, C{height} and C{clipid} scalars or from a previous C{LatLon[FHP|GH]}, a C{Clip[FHP|GH]4Tuple} or some other C{LatLon} instance.
@arg lat_ll: Latitude (C{scalar}) or a lat/longitude (C{LatLon[FHP|GH]}, aC{Clip[FHP|GH]4Tuple} or some other C{LatLon}). @kwarg lon: Longitude (C{scalar}), iff B{C{lat_ll}} is scalar, ignored otherwise. @kwarg height: Height (C{scalar}), conventionally C{meter}. @kwarg clipid: Clip identifier (C{int}). @kwarg name: Optional name (C{str}). ''' else: # don't override defaults self.name = name.get(_name_, NN)
and other.y == self.y)
'''String C{repr} of this lat-/longitude. ''' t = _ELLIPSIS_(self._prev, self._next) return _SPACE_(self, Fmt.ANGLE(t))
'''String C{str} of this lat-/longitude. ''' t += (_clipid_, self._clipid), t += (_alpha_, self._alpha), # if self._dupof: # recursion risk # t += (_dupof_, self._dupof.name), k = _DOT_ if self._checked else _BANG_ t += self._e_x_str(k) # PYCHOK expected
# I{Signed} area of a triangle, I{doubled}. (p3.x - x) * (p2.y - y)
# I{Unsigned} area of a triangle, I{doubled} # or 0 if below th given threshold C{eps}.
'''Get the I{clipid} (C{int} or C{0}). '''
'''Get the I{height} (C{Height} or C{int}). '''
'''Is this an intersection? '''
'''Is this an original (boolean) point? '''
# Make this and an other point are neighbors. # assert isinstance(other, self.__class__)
# Return this vertex as a L{[Clip|LatLon][FHP|GH]4Tuple}.
'''A point or intersection in a L{BooleanFHP} clip. ''' # _prep2 = None, None # shup up PyChecker
'''New C{LatLonFHP} from separate C{lat}, C{lon}, C{h}eight and C{clipid} scalars, or from a previous L{LatLonFHP}, a L{ClipFHP4Tuple} or some other C{LatLon} instance.
@arg lat_ll: Latitude (C{scalar}) or a lat/longitude (L{LatLonFHP}, C{LatLon} or L{ClipFHP4Tuple}). @arg lon_h_clipid: Longitude (C{scalar}), C{h}eight and C{clipid} iff B{C{lat_ll}} is scalar, ignored otherwise. @kwarg name: Optional name (C{str}). '''
return self.x * other.y - self.y * other.x
raise _IsnotError(self.__class__.__name__, other=other)
raise _IsnotError(_scalar_, other=other)
if self._label: t = NN(self._label, t) if self._en_ex: t = NN(t, self._en_ex) return t
# Is this point a I{duplicate} intersection? and p is not self and p == self # and p._alpha in (None, self._alpha) and self._alpha in (_0_0, p._alpha))
# @property_RO # def _isduplicated(self): # # Return the number of I{duplicates}? # d, v = 0, self # while v: # if v._dupof is self: # d += 1 # v = v._next # if v is self: # break # return d
# Is this point inside a composite, # excluding certain C{_Clip}s. # edge [p1,p2] must straddle y else:
'''Is this point inside I{combined} composites based on C{winding number}?
@arg composites: One or more iterables or composites of clips and points (L{ClipFHP4Tuple}, L{ClipGH4Tuple}, L{LatLonFHP}, L{LatLonGH}, L{LatLon_} or any other C{LatLon}).
@see: U{Algorithm 6<https://www.ScienceDirect.com/science/article/pii/ S0925772101000128>}. ''' self._isinside(_CompositeEdges(self.__class__, composites))
'''Is this an intersection? May be C{ispoint} too! '''
'''Is this an I{original} point? May be C{isintersection} too! '''
# Adjust 2-tuple (._prev, ._next) iff a I{duplicate} intersection p = self._dupof while p._isduplicate: p = p._dupof p = p._prev while n._isduplicate: n = n._next
# def _edge2(self): # # Return the start and end point of the # # edge containing I{intersection} C{v}. # n = p = self # while p.isintersection: # p = p._prev # if p is self: # break # while n.isintersection: # n = n._next # if n is self: # break # # assert p == self or not p._2Abs(self, n) # return p, n
# Relative Position oracle self._2A(p2, p3) > 0 else \ _RP.RIGHT # PYCHOK indent else: # right turn (or straight) self._2A(p2, p3) < 0 else \ _RP.LEFT # PYCHOK indent
'''A point or intersection in a L{BooleanGH} clip. '''
'''New C{LatLonGH} from separate C{lat}, C{lon}, C{h}eight and C{clipid} scalars, or from a previous L{LatLonGH}, a L{ClipGH4Tuple} or some other C{LatLon} instance.
@arg lat_ll: Latitude (C{scalar}) or a lat/longitude (L{LatLonGH}, C{LatLon} or L{ClipGH4Tuple}). @arg lon_h_clipid: Longitude (C{scalar}), C{h}eight and C{clipid} iff B{C{lat_ll}} is scalar, ignored otherwise. @kwarg name: Optional name (C{str}). '''
# Check-mark this vertex and its link.
return NN(t, (NN if self._entry is None else ( _e_ if self._entry else _x_)))
# Is this vertex inside the composite? I{Odd-even rule}.
# The I{odd-even} rule counts the number of edges # intersecting a ray emitted East-bound from this # point to infinity. When I{odd} this point lies # inside, if I{even} outside. r = not r
'''Is this point inside I{combined} composites based on C{odd-even rule}?
@arg composites: One or more iterables or composites of clips and points (L{ClipFHP4Tuple}, L{ClipGH4Tuple}, L{LatLonFHP}, L{LatLonGH}, L{LatLon_} or any other C{LatLon}). ''' self._isinside(_CompositeEdges(self.__class__, composites))
'''(INTERNAL) A I{doubly-linked} list representing a I{closed} polygon of C{LatLon} points and intersections with other polygons in the same C{_Composite}. '''
# assert isinstance(composite, _CompositeBase)
# Is C{p} one of this clip's vertices? for v in self: if v is p: # or ==? return True return False
# Yield all points and intersections.
# Return the number of latlons.
# Check whether all verices are ON_ON. return True
# Append a point given as C{y}, C{x}, C{h}eight and C{clipid} # args or as a C{LatLon[FHP|GH]} or C{Clip[FHP|GH}4Tuple}. # assert v._clipid == self._id
else: # insert before _first
# def _appendedup(self, v, cid=0): # # Like C{._append}, but only append C{v} if not a # # duplicate of the one previously append[edup]'ed. # y, x, p = v.y, v.x, self._last # if p is None or y != p.y or x != p.x or cid != p._clipid: # p = self._append(y, x, v.height, cid) # if v._linked: # p._linked = True # to force errors # return p
# End a clip, close it and check length. p._next is f and p is not f: # "un-close" the clip f._prev = p = p._prev p._next = f self._len -= 1 # elif f and raiser: # raise self._OpenClipError(p, f) raise self._Error(_too_(_few_))
# def _dup(self, q, *alpha): # # Duplicate vertex, an intersection iff C{alpha} # # is non-negative C{float} and less than _1_0. # return self._insert(q.x, q.y, q, q._next, *alpha)
# Duplicate a point (or intersection) as intersection. # assert v._prev is q # assert q._next is v
# Yield each I{original} edge as a 2-tuple # C{(LatLon[FHP|GH], LatLon[FHP|GH])}. raise self._OpenClipError(p2, f)
def _Error(self, txt): # PYCHOK no cover # Build a C{ClipError} instance kwds = dict(len=len(self), txt=txt) if self._dups: kwds.update(dups=self._dups) cp = self._composite if self._id: try: i = cp._clips.index(self) if i != self._id: kwds[_clip_] = i except ValueError: pass kwds[_clipid_] = self._id return ClipError(cp.name, cp._kind, **kwds)
# insertVertex between points C{start} and # C{end}, ordered by C{alpha} iff given. # assert start is not end
# insert an intersection or duplicate else: # intersection at point # assert not v._linked # assert v._alpha is None
# Yield all intersections.
# Is this clip a hole inside its composite? v = self._first return v._isinside(self._composite, self) if v else False
# Are all intersections non-CROSSINGs?
def _OpenClipError(self, s, e): # PYCHOK no cover # Return a C{CloseError} instance t = NN(s, _ELLIPSIS_(_COMMASPACE_, e)) return self._Error(_SPACE_(_open_, t))
# getNonIntersectionPoint and -Vertex # create a pseudo-point else: # no ._prev, ._next k._clipid = n._clipid r = None
# Yield all points in original order.
# Remove C{v}. # n = v._isduplicated # if n: # raise NotImplementedError(dups=n, v=v) else: p = n = None self._first = n self._last = n
# Yield all I{un-checked} CROSSING intersections.
class _ClipEdges(_Clip): # PYCHOK no cover # Wrapper yielding edges, eliminating # null edges and adding closing edge.
def __init__(self, cps, LL): # PYCHOK signature self._cps = cps # _CompositeEdges self._LL = LL
def _edges2(self, *unused): # PYCHOK signature # Yield C{LatLon[FHP|GH]} point pairs. for cp in self._cps: self._composite = cp p2 = s = None for ll in cp: p1, p2 = p2, self._LL(ll) if s is None: s = p1 = p2 elif p1._clipid != p2._clipid: if p1 != s: yield p1, s s = p1 = p2 elif p1 != p2: yield p1, p2 self._id = p2._clipid if p1 != s: yield p1, s
'''(INTERNAL) A list of C{_Clips} representing a composite polygon of C{LatLon} points and mutual intersections. '''
# New L{BooleanFHP} or L{BooleanGH}. self._eps = eps
else: c._dups += 1
def __contains__(self, v): # PYCHOK no cover # Is C{p} one of the clips' points? for c in self._clips: if v in c: return True return False
# Yield all points and intersections.
# Return the total number of latlons.
'''String C{repr} of this composite. '''
'''String C{str} of this composite. '''
# Get the bottom and top C{y} bounds, oversized. max(v.y for v in self))
# Return a new instance
def _clipids(self): # PYCHOK no cover for c in self._clips: yield c._id
'''Return a tuple with all C{clipid}s, I{ordered}. ''' return tuple(self._clipids)
# Yield each I{original} edge as a 3-tuple # C{(LatLon[FHP|GH], LatLon[FHP|GH], _Clip)}.
# Yield all intersections.
# Get the left and right C{x} bounds, oversized. max(v.x for v in self))
def _points(self): # PYCHOK no cover # Yield all I{original} points, # some may be intersection too. for c in self._clips: for v in c._points(): yield v
# Yield the dedup'd results, as L{ClipFHP4Tuple}s else: # null, colinear, ... skipped yield f._toClas4(C, cid)
class _CompositeEdges(_CompositeBase): # PYCHOK no cover # Polygon wrapper yielding clips and edges, # eliminating duplicates and closing open clips.
def __init__(self, LL, composites): # PYCHOK signature self._cps = composites self._LL = LL
@property_RO def _clips(self): # Use one C{_Clip}. return (_ClipEdges(self._cps, self._LL),)
'''(INTERNAL) A list of C{_Clips} representing a composite polygon of L{LatLonFHP} points and mutual intersections. '''
'''New L{BooleanFHP}.
@arg lls: The polygon points (iterable of 2 or more C{LatLon}s, L{LatLonFHP}s or L{ClipFHP4Tuple}s). @kwarg raiser: If C{True} throw L{ClipError} exceptions for errors. @kwarg name_kind_eps: Optional C{B{name}=NN} (C{str}) and/or optional C{B{kind}='corners'} of B{C{lls}} points (C{str}) and C{B{eps}=EPS} tolerance for duplicate removal (C{str}). ''' self._raiser = True
# 2) Classify intersection chains. while True: # n.__dict__.pop('_label')
# 3) Copy labels
**closed_inull_raiser): # Clip this polygon with another one, C{corners}, # using the Foster/Hormann/Popa's algorithm. eps=P._eps, raiser=False)
# compute and insert intersections _outside(p1.y, p2.y, *bt)): # assert not p._linked # assert not q._linked
# label and classify intersections
# check for special cases # handle identicals
# set Entry/Exit flags
# handle splits and crossings
# yield the results
# Yield all clips marked C{._identical}. yield c
# 1) Intersections classification # determine local configuration at this intersection # and positions of Q- and Q+ relative to (P-, I, P+) q3._RPoracle(p1, p, p3)) # check intersecting and overlapping cases
# Yield the result clips, each as # a generator of L{_LatLonFHP}s yield c._all()
# Yield the result from an unchecked CROSSING. # yield v while True: raise ClipError(full_circle=v, clipid=v._clipid)
# 4) Set entry/exit flags while True:
# 3.5) Check special cases c._identical = True else: c._pushback = True
# 3.5) Handle identicals for c in self._identicals: for v in c._intersections(): d = cds.get(v._linked._clipid, None) if d and d._ishole is c._ishole: c._pushback = True break # next clip
# Yield all intersections marked C{._2split} # assert isinstance(p._2split, _Clip)
# 5) Handle split pairs and 6) crossing candidates
# assert Pc in P._clips
# Yield each link as a 2-tuple(p, q)
# overwrites p-q link p._link(qq) q._link(pp) else: q._label = qq._label = L.CROSSING
# Yield all intersections marked C{._2xing}
'''(INTERNAL) A I{circular, doubly-linked} list of L{LatLonGH}s, representing the original points of a polygon plus -if any- the intersections with another. '''
'''New L{BooleanGH}.
@arg lls: The polygon points (iterable of 2 or more C{LatLon}s, L{LatLonGH}s or L{ClipGH4Tuple}s). @kwarg name: Optional name (C{str}). @kwarg raiser: If C{True} throw C{ClipError} exceptions for errors or I{degenerate cases}. @kwarg xtend: If C{True} handle I{degenerate cases}. @kwarg name_kind_eps: Optional C{B{name}=NN} (C{str}) and/or optional C{B{kind}='corners'} of B{C{lls}} points (C{str}) and C{B{eps}=EPS} tolerance for duplicate removal (C{float}). '''
**closed_inull_raiser_xtend): # Clip this polygon with another one, C{corners}.
# Core of Greiner/Hormann's algorithm, enhanced U{Correia's # <https://GitHub.com/helderco/univ-polyclip>} implementation*** # and extended to optionally handle so-called "degenerate cases" raiser=False, xtend=False) # 1. find intersections _outside(s1.y, s2.y, *bt)):
# 2. identify entry/exit intersections
# 3. yield the result(s)
# Get the very first vertex return None
# Yield the (first) unchecked, intersection(s).
# Yield the result from an un-checked intersection. while True: raise ClipError(full_circle=v, clipid=v._clipid)
# An edge between two L{LatLonFHP} points.
# New edge between points C{p1} and C{p2}, each a L{LatLonFHP}.
# Return intersection Type or C{None} _outside(q1.y, q2.y, *self._bottom_top_eps)): # compute and classify alpha and beta # distinguish intersection types E.P_INTERSECT if a_0_1 and b_0 else ( E.Q_INTERSECT if a_0 and b_0_1 else ( E.V_INTERSECT if a_0 and b_0 else None)))
# compute and classify alpha and beta # distinguish overlap type E.P_OVERLAP if a_0_1 and _b_0_1 else ( E.Q_OVERLAP if _a_0_1 and b_0_1 else ( E.V_OVERLAP if a_0 and b_0 else None)))
else:
# An edge between two L{LatLonGH} points.
# New edge between points C{s1} and C{s2}, each a L{LatLonGH}. s1.y, s2.y - s1.y)
return 'edge(%s, %s)' % (self._s1, self._s2)
a = max(_0_0, min(alpha, _1_0)) if fabs(a - alpha) < _0_EPS: return a raise ClipError('alpha outside [0..1]: %.3f' % (alpha,))
# Return C{(alpha)}, see .points.nearestOn5
t = _SPACE_(self._intersect4.__name__, _EdgeGH(c1, c2)) raise ClipError('unhandled case %s' % (n,), txt=t)
# Yield the intersections of this and another edge.
# @return: None, 1 or 2 intersections, each a 4-Tuple # (x, y, s_alpha, c_alpha) with intersection # x, y and both alphas.
# @raise ClipError: Intersection unhandled.
# @see: U{Intersection point of two line segments # <http://PaulBourke.net/geometry/pointlineplane/>}. _outside(c1_y, c2.y, *self._bottom_top_eps)): y, sy = self._x_sx_y_sy
_EPS_0 < ca < _1_EPS): _EPS_0 < sa < _1_EPS):
# unhandled, "degenerate" cases 1, 2 or 3 elif self._raiser and not (sa < _EPS_0 or sa > _1_EPS): self._error(1, c1, c2) # intersection at s1 or s2
# intersection at c1 or c2 or at c1 or c2 and s1 or s2 sa = (cx * dy - cy * dx) / d e = 2 if sa < _EPS_0 or sa > _1_EPS else 3 self._error(e, c1, c2)
elif parallel and (sx or sy) and (cx or cy): # PYCHOK no cover # non-null, parallel or colinear edges sa1, d1 = self._alpha2(c1_x - x, c1_y - y, sx, sy) sa2, d2 = self._alpha2(c2.x - x, c2.y - y, sx, sy) if max(d1, d2) < _0_EPS: if self._xtend and not _outside(sa1, sa2, _EPS_0, _1_EPS): if sa1 > sa2: # anti-parallel sa1, sa2 = sa2, sa1 ca1, ca2 = _1_0, _0_0 else: # parallel ca1, ca2 = _0_0, _1_0 ca = fabs((sx / cx) if cx else (sy / cy)) # = hypot(sx, sy) / hypot(cx, cy) if sa1 < 0: # s1 is between c1 and c2 ca *= ca1 + sa1 yield x, y, ca1, self._alpha1(ca) else: # c1 is between s1 and s2 yield (x + sa1 * sx), (y + sa1 * sy), sa1, ca1 if sa2 > 1: # s2 is between c1 and c2 ca *= sa2 - _1_0 yield (x + sx), (y + sy), ca2, self._alpha1(ca2 - ca) else: # c2 is between s1 and s2 yield (x + sa2 * sx), (y + sa2 * sy), sa2, ca2 elif self._raiser and not _outside(sa1, sa2, _0_0, _1_EPS): self._error(4, c1, c2)
# def _intersect4(self, c1, c2, **unused): # # Intersect this and another edge. # # # @return: 4-Tuple (x, y, s_alpha, c_alpha) with the # # intersection point x, y and both alphas. # # # @raise ClipError: Intersection unhandled. # # # @see: U{Intersection point of two line segments # # <http://PaulBourke.net/geometry/pointlineplane/>}. # # if _outside(c1.x, c2.x, *self._left_right_eps) or \ # _outside(c1.y, c2.y, *self._bottom_top_eps): # sa = ca = None # else: # x, sx, \ # y, sy = self._x_sx_y_sy # # cx = c2.x - c1.x # cy = c2.y - c1.y # d = cy * sx - cx * sy # # if fabs(d) > _0_EPS: # non-parallel edges # dx = x - c1.x # dy = y - c1.y # sa = (cx * dy - cy * dx) / d # ca = (sx * dy - sy * dx) / d # if _0_EPS < ca < _EPS_1: # if _0_EPS < sa < _EPS_1: # yield (x + sa * sx), (y + sa * sy), sa, ca # # # unhandled, "degenerate" cases 1, 2 or 3 # elif self.raiser and not (sa < _EPS_0 or sa > _1_EPS): # self._error(1, c1, c2) # insection at s1 or s2 # # elif self.raiser and not (ca < _EPS_0 or ca > _1_EPS): # # intersection at c1 or c2 or at c1 or c2 and s1 or s2 # self._error(2 if sa < _EPS_0 or sa > _1_EPS else 3, c1, c2) # # elif self.raiser and (sx or sy) and (cx or cy): # # null, parallel or colinear edges # h = hypot(sx, sy) * _0_EPS # if min(_perpendicular(c1.x - x, c1.y - y, sx, sy), # _perpendicular(c2.x - x, c2.y - y, sx, sy)) < h: # self._error(4, c1, c2) # colinear, overlapping
# Shared C{Boolean[FHP|GH]} methods.
'''Intersection: C{this & other}. '''
'''In-place intersection: C{this &= other}. '''
'''In-place union: C{this |= other}. '''
'''Union: C{this | other}. '''
''' Reverse intersection: C{other & this} ''' return self._other(other).__and__(self)
''' Reverse union: C{other | this} ''' return self._other(other).__or__(self)
# Replace this with a L{BooleanFHP} result.
raise TypeError('not %s: %r' % (B.__name__, other))
'''I{Composite} polygon class providing I{boolean} operations between two C{composite} polygons using U{Forster-Hormann-Popa <https://www.ScienceDirect.com/science/article/pii/S259014861930007X>}'s C++ implementation transcoded to pure Python.
The supported operations between (composite) polygon A and B are:
- C = A | B or A |= B, union of A and B
- C = A & B or A &= B, intersection of A and B
@see: Function L{clipFHP4} and class L{BooleanGH}. '''
if _FOR_DOCS: __init__ = _CompositeFHP.__init__
return NotImplementedError(_DOT_(self.named2, op.__name__))
'''In-place difference: C{this -= other}. ''' raise self._ErrorOp(self.__isub__)
''' Reverse difference: C{other - this} ''' raise self._ErrorOp(self.__rsub__)
'''Difference: C{this - other}. ''' raise self._ErrorOp(self.__sub__)
'''I{Composite} polygon class providing I{boolean} operations between two C{composite} polygons using the U{Greiner-Hormann <http://www.inf.USI.CH/hormann/papers/Greiner.1998.ECO.pdf>} algorithm, U{Correia<https://GitHub.com/helderco/univ-polyclip>}'s implementation modified and extended.
The supported operations between (composite) polygon A and B are:
- C = A | B or A |= B, union of A and B
- C = A & B or A &= B, intersection of A and B
- C = A - B or A -= B, difference A less B
- C = B - A or B -= A, difference B less A
@note: To handle I{degenerate cases} like C{point-edge} and C{point-point} intersections, use class L{BooleanFHP}.
@see: Function L{clipGH4} and class L{BooleanFHP}. '''
if _FOR_DOCS: __init__ = _CompositeGH.__init__
'''In-place difference: C{this -= other}. ''' return self._inplace(self.__sub__(other))
''' Reverse difference: C{other - this} ''' return self._other(other).__sub__(self)
'''Difference: C{this - other}. '''
# **) MIT License # # Copyright (C) 2018-2023 -- mrJean1 at Gmail -- All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE.
# ***) GNU GPL 3 # # Copyright (C) 2011-2012 Helder Correia <Helder.MC@Gmail.com> # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of # the License, or any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.GNU.org/licenses/>. # # You should have received the README file along with this program. # If not, see <https://GitHub.com/helderco/univ-polyclip>. |