small updates
This commit is contained in:
parent
73c35b7de7
commit
0ee59c1026
@ -2,6 +2,8 @@
|
|||||||
# BentleyOttmann sweep-line implementation
|
# BentleyOttmann sweep-line implementation
|
||||||
# (for finding all intersections in a set of line segments)
|
# (for finding all intersections in a set of line segments)
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"isect_segments",
|
"isect_segments",
|
||||||
"isect_polygon",
|
"isect_polygon",
|
||||||
@ -228,8 +230,9 @@ class SweepLine:
|
|||||||
"_before",
|
"_before",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, queue: EventQueue):
|
||||||
self.intersections = {}
|
self.intersections = {}
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
self._current_event_point_x = None
|
self._current_event_point_x = None
|
||||||
self._events_current_sweep = RBTree(cmp=Event.Compare, cmp_data=self)
|
self._events_current_sweep = RBTree(cmp=Event.Compare, cmp_data=self)
|
||||||
@ -483,7 +486,7 @@ class EventQueue:
|
|||||||
"events_scan",
|
"events_scan",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, segments, line: SweepLine):
|
def __init__(self, segments):
|
||||||
self.events_scan = RBTree()
|
self.events_scan = RBTree()
|
||||||
# segments = [s for s in segments if s[0][0] != s[1][0] and s[0][1] != s[1][1]]
|
# segments = [s for s in segments if s[0][0] != s[1][0] and s[0][1] != s[1][1]]
|
||||||
|
|
||||||
@ -512,8 +515,6 @@ class EventQueue:
|
|||||||
self.offer(s[0], e_start)
|
self.offer(s[0], e_start)
|
||||||
self.offer(s[1], e_end)
|
self.offer(s[1], e_end)
|
||||||
|
|
||||||
line.queue = self
|
|
||||||
|
|
||||||
def offer(self, p, e: Event):
|
def offer(self, p, e: Event):
|
||||||
"""
|
"""
|
||||||
Offer a new event ``s`` at point ``p`` in this queue.
|
Offer a new event ``s`` at point ``p`` in this queue.
|
||||||
@ -545,7 +546,7 @@ class EventQueue:
|
|||||||
return p, events_current
|
return p, events_current
|
||||||
|
|
||||||
|
|
||||||
def isect_segments_impl(segments, include_segments=False) -> list:
|
def isect_segments_impl(segments, *, include_segments=False, validate=True) -> list:
|
||||||
# order points left -> right
|
# order points left -> right
|
||||||
if Real is float:
|
if Real is float:
|
||||||
segments = [
|
segments = [
|
||||||
@ -568,8 +569,24 @@ def isect_segments_impl(segments, include_segments=False) -> list:
|
|||||||
)
|
)
|
||||||
for s in segments]
|
for s in segments]
|
||||||
|
|
||||||
sweep_line = SweepLine()
|
# Ensure segments don't have duplicates or single points, see: #24.
|
||||||
queue = EventQueue(segments, sweep_line)
|
if validate:
|
||||||
|
segments_old = segments
|
||||||
|
segments = []
|
||||||
|
visited = set()
|
||||||
|
for s in segments_old:
|
||||||
|
# Ignore points.
|
||||||
|
if s[0] == s[1]:
|
||||||
|
continue
|
||||||
|
# Ignore duplicates.
|
||||||
|
if s in visited:
|
||||||
|
continue
|
||||||
|
visited.add(s)
|
||||||
|
segments.append(s)
|
||||||
|
del segments_old
|
||||||
|
|
||||||
|
queue = EventQueue(segments)
|
||||||
|
sweep_line = SweepLine(queue)
|
||||||
|
|
||||||
while len(queue.events_scan) > 0:
|
while len(queue.events_scan) > 0:
|
||||||
if USE_VERBOSE:
|
if USE_VERBOSE:
|
||||||
@ -586,28 +603,29 @@ def isect_segments_impl(segments, include_segments=False) -> list:
|
|||||||
return sweep_line.get_intersections_with_segments()
|
return sweep_line.get_intersections_with_segments()
|
||||||
|
|
||||||
|
|
||||||
def isect_polygon_impl(points, include_segments=False) -> list:
|
def isect_polygon_impl(points, *, include_segments=False, validate=True) -> list:
|
||||||
n = len(points)
|
n = len(points)
|
||||||
segments = [
|
segments = [
|
||||||
(tuple(points[i]), tuple(points[(i + 1) % n]))
|
(tuple(points[i]), tuple(points[(i + 1) % n]))
|
||||||
for i in range(n)]
|
for i in range(n)
|
||||||
return isect_segments_impl(segments, include_segments=include_segments)
|
]
|
||||||
|
return isect_segments_impl(segments, include_segments=include_segments, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
def isect_segments(segments) -> list:
|
def isect_segments(segments, *, validate=True) -> list:
|
||||||
return isect_segments_impl(segments, include_segments=False)
|
return isect_segments_impl(segments, include_segments=False, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
def isect_polygon(segments) -> list:
|
def isect_polygon(segments, *, validate=True) -> list:
|
||||||
return isect_polygon_impl(segments, include_segments=False)
|
return isect_polygon_impl(segments, include_segments=False, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
def isect_segments_include_segments(segments) -> list:
|
def isect_segments_include_segments(segments, *, validate=True) -> list:
|
||||||
return isect_segments_impl(segments, include_segments=True)
|
return isect_segments_impl(segments, include_segments=True, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
def isect_polygon_include_segments(segments) -> list:
|
def isect_polygon_include_segments(segments, *, validate=True) -> list:
|
||||||
return isect_polygon_impl(segments, include_segments=True)
|
return isect_polygon_impl(segments, include_segments=True, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
@ -780,7 +798,7 @@ _sentinel = object()
|
|||||||
|
|
||||||
|
|
||||||
class _ABCTree(object):
|
class _ABCTree(object):
|
||||||
def __init__(self, items=None, cmp=None, cmp_data=None):
|
def __init__(self, cmp=None, cmp_data=None):
|
||||||
"""T.__init__(...) initializes T; see T.__class__.__doc__ for signature"""
|
"""T.__init__(...) initializes T; see T.__class__.__doc__ for signature"""
|
||||||
self._root = None
|
self._root = None
|
||||||
self._count = 0
|
self._count = 0
|
||||||
@ -794,8 +812,6 @@ class _ABCTree(object):
|
|||||||
return 0
|
return 0
|
||||||
self._cmp = cmp
|
self._cmp = cmp
|
||||||
self._cmp_data = cmp_data
|
self._cmp_data = cmp_data
|
||||||
if items is not None:
|
|
||||||
self.update(items)
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""T.clear() -> None. Remove all items from T."""
|
"""T.clear() -> None. Remove all items from T."""
|
||||||
@ -813,7 +829,7 @@ class _ABCTree(object):
|
|||||||
"""Get items count."""
|
"""Get items count."""
|
||||||
return self._count
|
return self._count
|
||||||
|
|
||||||
def get_value(self, key):
|
def _get_value_or_sentinel(self, key):
|
||||||
node = self._root
|
node = self._root
|
||||||
while node is not None:
|
while node is not None:
|
||||||
cmp = self._cmp(self._cmp_data, key, node.key)
|
cmp = self._cmp(self._cmp_data, key, node.key)
|
||||||
@ -823,7 +839,13 @@ class _ABCTree(object):
|
|||||||
node = node.left
|
node = node.left
|
||||||
else:
|
else:
|
||||||
node = node.right
|
node = node.right
|
||||||
|
return _sentinel
|
||||||
|
|
||||||
|
def get_value(self, key):
|
||||||
|
value = self._get_value_or_sentinel(key)
|
||||||
|
if value is _sentinel:
|
||||||
raise KeyError(str(key))
|
raise KeyError(str(key))
|
||||||
|
return value
|
||||||
|
|
||||||
def pop_item(self):
|
def pop_item(self):
|
||||||
"""T.pop_item() -> (k, v), remove and return some (key, value) pair as a
|
"""T.pop_item() -> (k, v), remove and return some (key, value) pair as a
|
||||||
@ -951,11 +973,7 @@ class _ABCTree(object):
|
|||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
"""k in T -> True if T has a key k, else False"""
|
"""k in T -> True if T has a key k, else False"""
|
||||||
try:
|
return self._get_value_or_sentinel(key) is not _sentinel
|
||||||
self.get_value(key)
|
|
||||||
return True
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""T.__len__() <==> len(x)"""
|
"""T.__len__() <==> len(x)"""
|
||||||
@ -967,19 +985,20 @@ class _ABCTree(object):
|
|||||||
|
|
||||||
def set_default(self, key, default=None):
|
def set_default(self, key, default=None):
|
||||||
"""T.set_default(k[,d]) -> T.get(k,d), also set T[k]=d if k not in T"""
|
"""T.set_default(k[,d]) -> T.get(k,d), also set T[k]=d if k not in T"""
|
||||||
try:
|
value = self._get_value_or_sentinel(key)
|
||||||
return self.get_value(key)
|
if value is _sentinel:
|
||||||
except KeyError:
|
|
||||||
self.insert(key, default)
|
self.insert(key, default)
|
||||||
return default
|
return default
|
||||||
|
return value
|
||||||
setdefault = set_default # for compatibility to dict()
|
setdefault = set_default # for compatibility to dict()
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"""T.get(k[,d]) -> T[k] if k in T, else d. d defaults to None."""
|
"""T.get(k[,d]) -> T[k] if k in T, else d. d defaults to None."""
|
||||||
try:
|
|
||||||
return self.get_value(key)
|
value = self._get_value_or_sentinel(key)
|
||||||
except KeyError:
|
if value is _sentinel:
|
||||||
return default
|
return default
|
||||||
|
return value
|
||||||
|
|
||||||
def pop(self, key, *args):
|
def pop(self, key, *args):
|
||||||
"""T.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
"""T.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
||||||
@ -987,15 +1006,15 @@ class _ABCTree(object):
|
|||||||
"""
|
"""
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
raise TypeError("pop expected at most 2 arguments, got %d" % (1 + len(args)))
|
raise TypeError("pop expected at most 2 arguments, got %d" % (1 + len(args)))
|
||||||
try:
|
|
||||||
value = self.get_value(key)
|
value = self._get_value_or_sentinel(key)
|
||||||
|
if value is _sentinel:
|
||||||
|
if len(args) == 0:
|
||||||
|
raise KeyError(str(key))
|
||||||
|
return args[0]
|
||||||
|
|
||||||
self.remove(key)
|
self.remove(key)
|
||||||
return value
|
return value
|
||||||
except KeyError:
|
|
||||||
if len(args) == 0:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
return args[0]
|
|
||||||
|
|
||||||
def prev_key(self, key, default=_sentinel):
|
def prev_key(self, key, default=_sentinel):
|
||||||
"""Get predecessor to key, raises KeyError if key is min key
|
"""Get predecessor to key, raises KeyError if key is min key
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
<name>Rounder</name>
|
<name>Rounder</name>
|
||||||
<id>fablabchemnitz.de.rounder</id>
|
<id>fablabchemnitz.de.rounder</id>
|
||||||
<param name="titleMain" type="description">Rounding helpers.</param>
|
<param name="precision" type="int" min="0" max="20" gui-text="Rounding precision">2</param>
|
||||||
<param name="precision" type="int" min="0" max="20" gui-text="Rounding precission">2</param>
|
|
||||||
<param name="paths" type="bool" gui-text="Round nodes">true</param>
|
<param name="paths" type="bool" gui-text="Round nodes">true</param>
|
||||||
<param name="ctrl" type="bool" gui-text="Round handles">false</param>
|
<param name="ctrl" type="bool" gui-text="Round handles">false</param>
|
||||||
<param name="along" type="bool" gui-text="Move handles following node movement">true</param>
|
<param name="along" type="bool" gui-text="Move handles following node movement">true</param>
|
||||||
|
Reference in New Issue
Block a user