Module sverchok.utils.geom_2d.make_monotone
Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
from sverchok.utils.avl_tree import AVLTree
from .dcel import Point as Point_template, HalfEdge as HalfEdge_template, DCELMesh as DCELMesh_template
from .sort_mesh import SortPointsUpDown, SortHalfEdgesCCW, SortEdgeSweepingAlgorithm
from .dcel_debugger import Debugger
def monotone_sv_face_with_holes(vert_face, vert_holes=None, face_holes=None, accuracy=1e-5):
"""
Get one face in Sverhok format and splitting it into monotone pieces
Vertices of face should be given in ordered along face indexes order
Also it is possible to create holes in input face
Mesh of holes should be inside face without any intersection
:param vert_face:
:param vert_holes:
:param face_holes:
:param accuracy: two floats figures are equal if their difference is lower then accuracy value, float
:return: vertices in Sverchok format, faces in Sverchok format
"""
mesh = DCELMesh(accuracy)
mesh.from_sv_faces(vert_face, [list(range(len(vert_face)))])
main_face = mesh.faces[0] # once this will be broken
if vert_holes and face_holes:
main_face.insert_holes(vert_holes, face_holes)
make_monotone(main_face)
rebuild_face_list(mesh)
return mesh.to_sv_mesh(edges=False, only_select=True)
def monotone_faces_with_holes(dcel_mesh, del_flag='del'):
"""
Split polygons with holes into monotone pieces of DCEL mesh data structure
Faces already should have actual information about inner component
:param dcel_mesh: DCELMesh
:param del_flag: faces with such flag just are just ignore by the algorithm
:return: DCELMesh with split faces
"""
is_inners = False
for face in dcel_mesh.faces:
if del_flag in face.flags:
continue
elif face.outer and face.inners:
is_inners = True
make_monotone(face)
if is_inners:
rebuild_face_list(dcel_mesh)
return dcel_mesh
# #############################################################################
# ########### - partitioning to monotone pieces algorithm - ###################
# #############################################################################
x, y, z = 0, 1, 2
class Point(Point_template, SortPointsUpDown):
monotone_current_face = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._type = None
self._monotone_face = None
@property
def type(self):
# the type should be updated each time when polygon is changed in partitioning algorithm
# during handle of polygon point does not change type
face = self.monotone_face
if not self._type:
hedge = None # is hedge with origin in the point and belonging to current monotone face
for coin_hedge in self.hedge.ccw_hedges:
if coin_hedge.face == face:
hedge = coin_hedge
break
if not hedge:
raise LookupError("This mean that either monotone face is marked incorrect or "
"coincidence half edges are marked incorrect or something else")
next_point = hedge.next.origin
last_point = hedge.last.origin
is_up_next = next_point < self # the less point the upper it is
is_up_last = last_point < self
if not is_up_next and not is_up_last:
self._type = 'start' if hedge < hedge.last.twin else 'split'
elif is_up_last and is_up_next:
self._type = 'merge' if hedge > hedge.last.twin else 'end'
else:
self._type = 'regular'
return self._type
@property
def monotone_face(self):
# returns face coincidence to a point which is handling by the algorithm
if not self.monotone_current_face:
raise Exception('Which polygon is handling should be set before')
elif self._monotone_face != self.monotone_current_face:
self._monotone_face = self.monotone_current_face
self._type = None
return self._monotone_face
class HalfEdge(HalfEdge_template, SortHalfEdgesCCW):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.edge = None
class DCELMesh(DCELMesh_template):
Point = Point
HalfEdge = HalfEdge
class Edge(SortEdgeSweepingAlgorithm):
def __init__(self, up_p, low_p):
super().__init__(up_p, low_p)
self.helper = None
def make_monotone(face):
"""
Splits polygon into monotone pieces optionally with holes
:param face: face of half edge data structure
:return new half edges
Probably approach of implementation of monotone algorithm should be reconsidered according new DCEL data structure
"""
face.mesh.Point.monotone_current_face = face
status = AVLTree()
q = sorted(build_points_list(face))[::-1]
_ = [p.type for p in q] # not very cool but this will set type for all points before main algorithm
while q:
event_point = q.pop()
Edge.global_event_point = event_point
handle_functions[event_point.type](event_point, status, find_hedge(event_point))
def build_points_list(face):
# build list of points for partitioning algorithm, all point of outer and inners components
verts = []
for hedge in face.outer.loop_hedges:
verts.append(hedge.origin)
for inner_hedge in face.inners:
for hedge in inner_hedge.loop_hedges:
verts.append(hedge.origin)
return verts
def find_hedge(point):
# find hedge with origin in current point and with partitioning face
for hedge in point.hedge.ccw_hedges:
if hedge.face == point.monotone_face:
break
return hedge
def rebuild_face_list(dcel_mesh):
# rebuild face list after partition algorithm
# this function should correct boundless face but it does not !!!!
for hedge in dcel_mesh.hedges:
if hedge.face:
continue
face = dcel_mesh.Face(dcel_mesh)
face.select = True
face.outer = hedge
for h in hedge.loop_hedges:
h.face = face
used = set()
faces = []
for hedge in dcel_mesh.hedges:
if hedge.face == dcel_mesh.unbounded:
continue
if hedge not in used:
faces.append(hedge.face)
[used.add(h) for h in hedge.loop_hedges]
dcel_mesh.faces = faces
def insert_edge(up_p, low_p):
# insert new edge into half edge data structure
up_hedge = up_p.mesh.HalfEdge(up_p.mesh, up_p)
up_p.mesh.hedges.append(up_hedge)
low_hedge = up_p.mesh.HalfEdge(up_p.mesh, low_p)
up_p.mesh.hedges.append(low_hedge)
up_hedge.twin = low_hedge
low_hedge.twin = up_hedge
up_p_hedge = find_hedge(up_p)
low_p_hedge = find_hedge(low_p)
up_ccw_hedges = []
status = 1
for h in up_p_hedge.ccw_hedges:
up_ccw_hedges.append(h)
if h.twin.face and h.twin.face == up_p_hedge.face:
status -= 1
break
if status != 0:
raise Exception('Hedge ({}) does not have neighbour with the same face'.format(up_p_hedge))
if len(up_ccw_hedges) == 2:
up_next = up_ccw_hedges[0]
elif 2 < len(up_ccw_hedges) < 5:
if up_ccw_hedges[0] > up_hedge:
if ((up_ccw_hedges[2] < up_hedge and up_ccw_hedges[2] < up_ccw_hedges[0]) or
(up_ccw_hedges[2] > up_hedge and up_ccw_hedges[2] > up_ccw_hedges[0])):
up_next = up_ccw_hedges[2]
elif ((up_ccw_hedges[1] < up_hedge and up_ccw_hedges[1] < up_ccw_hedges[0]) or
(up_ccw_hedges[1] > up_hedge and up_ccw_hedges[1] > up_ccw_hedges[0])):
up_next = up_ccw_hedges[1]
else:
up_next = up_ccw_hedges[0]
else:
up_next = up_ccw_hedges[1] if up_ccw_hedges[0] < up_ccw_hedges[1] < up_hedge else up_ccw_hedges[0]
else:
raise Exception('Unexpected number of half edges in point {}'.format(up_p))
low_ccw_hedges = []
status = 1
for h in low_p_hedge.ccw_hedges:
low_ccw_hedges.append(h)
if h.twin.face and h.twin.face == low_p_hedge.face:
status -= 1
break
if status != 0:
raise Exception('Hedge ({}) does not have neighbour with the same face'.format(low_p.hedge.i))
if len(low_ccw_hedges) == 2:
low_next = low_ccw_hedges[0]
elif len(low_ccw_hedges) == 3:
if low_ccw_hedges[0] > low_hedge:
if ((low_ccw_hedges[0] > low_ccw_hedges[1] < low_hedge) or
(low_ccw_hedges[0] < low_ccw_hedges[1] > low_hedge)):
low_next = low_ccw_hedges[1]
else:
low_next = low_ccw_hedges[0]
else:
low_next = low_ccw_hedges[1] if low_ccw_hedges[0] < low_ccw_hedges[1] < low_hedge else low_ccw_hedges[0]
else:
raise Exception('Unexpected number of half edges in point {}'.format(low_p))
up_hedge.last = up_next.last
up_hedge.next = low_next
low_hedge.next = up_next
low_hedge.last = low_next.last
up_next.last.next = up_hedge
up_next.last = low_hedge
low_next.last.next = low_hedge
low_next.last = up_hedge
if hasattr(up_hedge, 'in_faces'):
# actually this part related with merge mesh algorithm only
up_hedge.in_faces = set(up_hedge.next.in_faces)
low_hedge.in_faces = set(low_hedge.next.in_faces)
def handle_start_point(point, status, hedge):
# Read Computational Geometry by Mark de Berg
edge = Edge(point, hedge.twin.origin)
hedge.edge = edge
hedge.twin.edge = edge
edge.helper = point
status.insert(edge)
def handle_end_point(point, status, hedge):
# Read Computational Geometry by Mark de Berg
status.remove(hedge.last.edge)
helper = hedge.last.edge.helper
if helper.type == 'merge':
insert_edge(helper, point)
def handle_split_point(point, status, hedge):
# Read Computational Geometry by Mark de Berg
left_node = status.find_nearest_left(point.co[x])
insert_edge(left_node.key.helper, point)
left_node.key.helper = point
edge = Edge(point, hedge.twin.origin)
hedge.edge = edge
hedge.twin.edge = edge
edge.helper = point
status.insert(edge)
def handle_merge_point(point, status, hedge):
# Read Computational Geometry by Mark de Berg
right_helper = hedge.last.edge.helper
last_hedge = hedge.last
if right_helper.type == 'merge':
insert_edge(right_helper, point)
status.remove(last_hedge.edge)
left_node = status.find_nearest_left(point.co[x])
left_helper = left_node.key.helper
if left_helper.type == 'merge':
insert_edge(left_helper, point)
left_node.key.helper = point
def handle_regular_point(point, status, hedge):
# Read Computational Geometry by Mark de Berg
if point < hedge.twin.origin:
right_helper = hedge.last.edge.helper
status.remove(hedge.last.edge)
edge = Edge(point, hedge.twin.origin)
hedge.edge = edge
hedge.twin.edge = edge
edge.helper = point
status.insert(edge)
if right_helper.type == 'merge':
insert_edge(right_helper, point)
else:
left_node = status.find_nearest_left(point.co[x])
left_helper = left_node.key.helper
left_node.key.helper = point
if left_helper.type == 'merge':
insert_edge(left_helper, point)
handle_functions = {'start': handle_start_point, 'end': handle_end_point, 'split': handle_split_point,
'merge': handle_merge_point, 'regular': handle_regular_point}
Functions
def build_points_list(face)
-
Expand source code
def build_points_list(face): # build list of points for partitioning algorithm, all point of outer and inners components verts = [] for hedge in face.outer.loop_hedges: verts.append(hedge.origin) for inner_hedge in face.inners: for hedge in inner_hedge.loop_hedges: verts.append(hedge.origin) return verts
def find_hedge(point)
-
Expand source code
def find_hedge(point): # find hedge with origin in current point and with partitioning face for hedge in point.hedge.ccw_hedges: if hedge.face == point.monotone_face: break return hedge
def handle_end_point(point, status, hedge)
-
Expand source code
def handle_end_point(point, status, hedge): # Read Computational Geometry by Mark de Berg status.remove(hedge.last.edge) helper = hedge.last.edge.helper if helper.type == 'merge': insert_edge(helper, point)
def handle_merge_point(point, status, hedge)
-
Expand source code
def handle_merge_point(point, status, hedge): # Read Computational Geometry by Mark de Berg right_helper = hedge.last.edge.helper last_hedge = hedge.last if right_helper.type == 'merge': insert_edge(right_helper, point) status.remove(last_hedge.edge) left_node = status.find_nearest_left(point.co[x]) left_helper = left_node.key.helper if left_helper.type == 'merge': insert_edge(left_helper, point) left_node.key.helper = point
def handle_regular_point(point, status, hedge)
-
Expand source code
def handle_regular_point(point, status, hedge): # Read Computational Geometry by Mark de Berg if point < hedge.twin.origin: right_helper = hedge.last.edge.helper status.remove(hedge.last.edge) edge = Edge(point, hedge.twin.origin) hedge.edge = edge hedge.twin.edge = edge edge.helper = point status.insert(edge) if right_helper.type == 'merge': insert_edge(right_helper, point) else: left_node = status.find_nearest_left(point.co[x]) left_helper = left_node.key.helper left_node.key.helper = point if left_helper.type == 'merge': insert_edge(left_helper, point)
def handle_split_point(point, status, hedge)
-
Expand source code
def handle_split_point(point, status, hedge): # Read Computational Geometry by Mark de Berg left_node = status.find_nearest_left(point.co[x]) insert_edge(left_node.key.helper, point) left_node.key.helper = point edge = Edge(point, hedge.twin.origin) hedge.edge = edge hedge.twin.edge = edge edge.helper = point status.insert(edge)
def handle_start_point(point, status, hedge)
-
Expand source code
def handle_start_point(point, status, hedge): # Read Computational Geometry by Mark de Berg edge = Edge(point, hedge.twin.origin) hedge.edge = edge hedge.twin.edge = edge edge.helper = point status.insert(edge)
def insert_edge(up_p, low_p)
-
Expand source code
def insert_edge(up_p, low_p): # insert new edge into half edge data structure up_hedge = up_p.mesh.HalfEdge(up_p.mesh, up_p) up_p.mesh.hedges.append(up_hedge) low_hedge = up_p.mesh.HalfEdge(up_p.mesh, low_p) up_p.mesh.hedges.append(low_hedge) up_hedge.twin = low_hedge low_hedge.twin = up_hedge up_p_hedge = find_hedge(up_p) low_p_hedge = find_hedge(low_p) up_ccw_hedges = [] status = 1 for h in up_p_hedge.ccw_hedges: up_ccw_hedges.append(h) if h.twin.face and h.twin.face == up_p_hedge.face: status -= 1 break if status != 0: raise Exception('Hedge ({}) does not have neighbour with the same face'.format(up_p_hedge)) if len(up_ccw_hedges) == 2: up_next = up_ccw_hedges[0] elif 2 < len(up_ccw_hedges) < 5: if up_ccw_hedges[0] > up_hedge: if ((up_ccw_hedges[2] < up_hedge and up_ccw_hedges[2] < up_ccw_hedges[0]) or (up_ccw_hedges[2] > up_hedge and up_ccw_hedges[2] > up_ccw_hedges[0])): up_next = up_ccw_hedges[2] elif ((up_ccw_hedges[1] < up_hedge and up_ccw_hedges[1] < up_ccw_hedges[0]) or (up_ccw_hedges[1] > up_hedge and up_ccw_hedges[1] > up_ccw_hedges[0])): up_next = up_ccw_hedges[1] else: up_next = up_ccw_hedges[0] else: up_next = up_ccw_hedges[1] if up_ccw_hedges[0] < up_ccw_hedges[1] < up_hedge else up_ccw_hedges[0] else: raise Exception('Unexpected number of half edges in point {}'.format(up_p)) low_ccw_hedges = [] status = 1 for h in low_p_hedge.ccw_hedges: low_ccw_hedges.append(h) if h.twin.face and h.twin.face == low_p_hedge.face: status -= 1 break if status != 0: raise Exception('Hedge ({}) does not have neighbour with the same face'.format(low_p.hedge.i)) if len(low_ccw_hedges) == 2: low_next = low_ccw_hedges[0] elif len(low_ccw_hedges) == 3: if low_ccw_hedges[0] > low_hedge: if ((low_ccw_hedges[0] > low_ccw_hedges[1] < low_hedge) or (low_ccw_hedges[0] < low_ccw_hedges[1] > low_hedge)): low_next = low_ccw_hedges[1] else: low_next = low_ccw_hedges[0] else: low_next = low_ccw_hedges[1] if low_ccw_hedges[0] < low_ccw_hedges[1] < low_hedge else low_ccw_hedges[0] else: raise Exception('Unexpected number of half edges in point {}'.format(low_p)) up_hedge.last = up_next.last up_hedge.next = low_next low_hedge.next = up_next low_hedge.last = low_next.last up_next.last.next = up_hedge up_next.last = low_hedge low_next.last.next = low_hedge low_next.last = up_hedge if hasattr(up_hedge, 'in_faces'): # actually this part related with merge mesh algorithm only up_hedge.in_faces = set(up_hedge.next.in_faces) low_hedge.in_faces = set(low_hedge.next.in_faces)
def make_monotone(face)
-
Splits polygon into monotone pieces optionally with holes :param face: face of half edge data structure :return new half edges Probably approach of implementation of monotone algorithm should be reconsidered according new DCEL data structure
Expand source code
def make_monotone(face): """ Splits polygon into monotone pieces optionally with holes :param face: face of half edge data structure :return new half edges Probably approach of implementation of monotone algorithm should be reconsidered according new DCEL data structure """ face.mesh.Point.monotone_current_face = face status = AVLTree() q = sorted(build_points_list(face))[::-1] _ = [p.type for p in q] # not very cool but this will set type for all points before main algorithm while q: event_point = q.pop() Edge.global_event_point = event_point handle_functions[event_point.type](event_point, status, find_hedge(event_point))
def monotone_faces_with_holes(dcel_mesh, del_flag='del')
-
Split polygons with holes into monotone pieces of DCEL mesh data structure Faces already should have actual information about inner component :param dcel_mesh: DCELMesh :param del_flag: faces with such flag just are just ignore by the algorithm :return: DCELMesh with split faces
Expand source code
def monotone_faces_with_holes(dcel_mesh, del_flag='del'): """ Split polygons with holes into monotone pieces of DCEL mesh data structure Faces already should have actual information about inner component :param dcel_mesh: DCELMesh :param del_flag: faces with such flag just are just ignore by the algorithm :return: DCELMesh with split faces """ is_inners = False for face in dcel_mesh.faces: if del_flag in face.flags: continue elif face.outer and face.inners: is_inners = True make_monotone(face) if is_inners: rebuild_face_list(dcel_mesh) return dcel_mesh
def monotone_sv_face_with_holes(vert_face, vert_holes=None, face_holes=None, accuracy=1e-05)
-
Get one face in Sverhok format and splitting it into monotone pieces Vertices of face should be given in ordered along face indexes order Also it is possible to create holes in input face Mesh of holes should be inside face without any intersection :param vert_face: :param vert_holes: :param face_holes: :param accuracy: two floats figures are equal if their difference is lower then accuracy value, float :return: vertices in Sverchok format, faces in Sverchok format
Expand source code
def monotone_sv_face_with_holes(vert_face, vert_holes=None, face_holes=None, accuracy=1e-5): """ Get one face in Sverhok format and splitting it into monotone pieces Vertices of face should be given in ordered along face indexes order Also it is possible to create holes in input face Mesh of holes should be inside face without any intersection :param vert_face: :param vert_holes: :param face_holes: :param accuracy: two floats figures are equal if their difference is lower then accuracy value, float :return: vertices in Sverchok format, faces in Sverchok format """ mesh = DCELMesh(accuracy) mesh.from_sv_faces(vert_face, [list(range(len(vert_face)))]) main_face = mesh.faces[0] # once this will be broken if vert_holes and face_holes: main_face.insert_holes(vert_holes, face_holes) make_monotone(main_face) rebuild_face_list(mesh) return mesh.to_sv_mesh(edges=False, only_select=True)
def rebuild_face_list(dcel_mesh)
-
Expand source code
def rebuild_face_list(dcel_mesh): # rebuild face list after partition algorithm # this function should correct boundless face but it does not !!!! for hedge in dcel_mesh.hedges: if hedge.face: continue face = dcel_mesh.Face(dcel_mesh) face.select = True face.outer = hedge for h in hedge.loop_hedges: h.face = face used = set() faces = [] for hedge in dcel_mesh.hedges: if hedge.face == dcel_mesh.unbounded: continue if hedge not in used: faces.append(hedge.face) [used.add(h) for h in hedge.loop_hedges] dcel_mesh.faces = faces
Classes
class DCELMesh (accuracy=None)
-
Expand source code
class DCELMesh(DCELMesh_template): Point = Point HalfEdge = HalfEdge
Ancestors
Subclasses
Class variables
var HalfEdge
-
Half edges are sorting in counterclockwise direction from -X direction. Should be used with HalfEdge class from dcel_mesh module
var Point
-
This allowed sort points from upward to downward direction. Besides points with equal Y coordinate are sorted from left to right. (0, 1, 0) < (0, 0, 0) (0, 1, 0) < (1, 1, 0) (0, 1.001, 0) == (0, 1.002, 0) if accuracy <= 1e-3
Should be used with another class with "co" - (x, y, z) and "accuracy" - (float) attributes
class Edge (up_p, low_p)
-
Sorting edges for sweeping line algorithm. Edges are sorted along horizontal sweeping line from left to right according their intersection with sweep line. If Edges intersects in one points they are sorted in cww order from -X direction.
There is global event point parameter determining position of sweep line. It should be updated each time when sweep line is changing its position.
Expand source code
class Edge(SortEdgeSweepingAlgorithm): def __init__(self, up_p, low_p): super().__init__(up_p, low_p) self.helper = None
Ancestors
class HalfEdge (*args, **kwargs)
-
Half edges are sorting in counterclockwise direction from -X direction. Should be used with HalfEdge class from dcel_mesh module
Expand source code
class HalfEdge(HalfEdge_template, SortHalfEdgesCCW): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.edge = None
Ancestors
Subclasses
Inherited members
class Point (*args, **kwargs)
-
This allowed sort points from upward to downward direction. Besides points with equal Y coordinate are sorted from left to right. (0, 1, 0) < (0, 0, 0) (0, 1, 0) < (1, 1, 0) (0, 1.001, 0) == (0, 1.002, 0) if accuracy <= 1e-3
Should be used with another class with "co" - (x, y, z) and "accuracy" - (float) attributes
Expand source code
class Point(Point_template, SortPointsUpDown): monotone_current_face = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._type = None self._monotone_face = None @property def type(self): # the type should be updated each time when polygon is changed in partitioning algorithm # during handle of polygon point does not change type face = self.monotone_face if not self._type: hedge = None # is hedge with origin in the point and belonging to current monotone face for coin_hedge in self.hedge.ccw_hedges: if coin_hedge.face == face: hedge = coin_hedge break if not hedge: raise LookupError("This mean that either monotone face is marked incorrect or " "coincidence half edges are marked incorrect or something else") next_point = hedge.next.origin last_point = hedge.last.origin is_up_next = next_point < self # the less point the upper it is is_up_last = last_point < self if not is_up_next and not is_up_last: self._type = 'start' if hedge < hedge.last.twin else 'split' elif is_up_last and is_up_next: self._type = 'merge' if hedge > hedge.last.twin else 'end' else: self._type = 'regular' return self._type @property def monotone_face(self): # returns face coincidence to a point which is handling by the algorithm if not self.monotone_current_face: raise Exception('Which polygon is handling should be set before') elif self._monotone_face != self.monotone_current_face: self._monotone_face = self.monotone_current_face self._type = None return self._monotone_face
Ancestors
Subclasses
Class variables
var monotone_current_face
Instance variables
var monotone_face
-
Expand source code
@property def monotone_face(self): # returns face coincidence to a point which is handling by the algorithm if not self.monotone_current_face: raise Exception('Which polygon is handling should be set before') elif self._monotone_face != self.monotone_current_face: self._monotone_face = self.monotone_current_face self._type = None return self._monotone_face
var type
-
Expand source code
@property def type(self): # the type should be updated each time when polygon is changed in partitioning algorithm # during handle of polygon point does not change type face = self.monotone_face if not self._type: hedge = None # is hedge with origin in the point and belonging to current monotone face for coin_hedge in self.hedge.ccw_hedges: if coin_hedge.face == face: hedge = coin_hedge break if not hedge: raise LookupError("This mean that either monotone face is marked incorrect or " "coincidence half edges are marked incorrect or something else") next_point = hedge.next.origin last_point = hedge.last.origin is_up_next = next_point < self # the less point the upper it is is_up_last = last_point < self if not is_up_next and not is_up_last: self._type = 'start' if hedge < hedge.last.twin else 'split' elif is_up_last and is_up_next: self._type = 'merge' if hedge > hedge.last.twin else 'end' else: self._type = 'regular' return self._type