Module sverchok.utils.quad_grid
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
class SvQuadGridParser(object):
"""
Parse a "bmesh" object, which contains a "quad grid", i.e.
mesh consisting of quads only, in topology similar to subdivided plane (m x n vertices).
Return vertices ordered row-by-row.
Raise exception if the given mesh has invalid topology.
"""
def __init__(self, bm):
self.bmesh = bm
self._init_check()
self._init()
def _init_check(self):
# Run some checks that the mesh has correct topology.
# Not all necessary conditions are checked here, only
# simplest ones.
for face in self.bmesh.faces:
if len(face.verts) != 4:
raise Exception(f"One of faces has {len(face.verts)} vertices instead of 4")
corners_cnt = 0
edges_cnt = 0
for vert in self.bmesh.verts:
if vert.is_wire:
raise Exception("One of verts is a wire")
k = len(vert.link_edges)
if k not in {2,3,4}:
raise Exception(f"One of vertices has {k} adjacent edges instead of 2,3,4")
if k == 4 and vert.is_boundary:
raise Exception("Vertex with 4 adjacent edges is boundary")
if k != 4 and not vert.is_boundary:
raise Exception(f"Vertex with {k} adjacent edges is not boundary")
if k == 2:
corners_cnt += 1
if k == 3:
edges_cnt += 1
if corners_cnt != 4:
raise Exception(f"Mesh has {corners_cnt} corners instead of 4")
if edges_cnt % 2 != 0:
raise Exception("Mesh has {edges_cnt} edge vertices, which does not divide by 2")
for edge in self.bmesh.edges:
k = len(edge.link_faces)
if k not in {1,2}:
raise Exception(f"One of edges has {k} adjacent faces instead of 1,2")
if k == 2 and edge.is_boundary:
raise Exception("Edge with 2 adjacent faces is boundary!?")
if k == 1 and not edge.is_boundary:
raise Exception("Edge with 1 adjacent face is not boundary!?")
def _init(self):
# Find a corner to start with.
# We are searching for such a vertex and it's loop,
# so the edge is pointing in positive X direction.
# If we can't find such, then search for positive Y...
self.row_length = None
self.y_direction = False
good_x = []
good_y = []
for vert in self.bmesh.verts:
if len(vert.link_edges) == 2:
for loop in vert.link_loops:
other_vert = loop.edge.other_vert(vert)
#info(f"V#{vert.index}: x {vert.co.x} => #{other_vert.index} {other_vert.co.x}")
if other_vert.co.x > vert.co.x:
good_x.append(loop)
if other_vert.co.y > vert.co.y:
good_y.append(loop)
if good_x:
loop = good_x[0]
elif good_y:
loop = good_y[0]
self.y_direction = True
else:
raise Exception("Could not find a vertex to start from")
vert = loop.vert
#info(f"Start with v.{vert.index}, loop {self._show_loop(loop)}")
self.current_loop = loop
self.current_vert = vert
self.start_loop = loop
self.start_vert = vert
def _reset(self):
self.current_loop = self.start_loop
self.current_vert = self.start_vert
def _show_loop(self, loop):
# Debug utility
vert = loop.vert
other_vert = loop.edge.other_vert(vert)
return f"[{vert.index}->{other_vert.index}]({loop.face.index})"
def _next_loop(self):
# Find the next loop in "forward" direction
start_k = len(self.current_vert.link_edges)
next_loop = self.current_loop.link_loop_next
k = len(next_loop.vert.link_edges)
#info(f"> from {self._show_loop(self.current_loop)} => {self._show_loop(next_loop)}, k={k} (for v.{next_loop.vert.index}) of {self.row_start_k}")
if k == self.row_start_k:
loop = next_loop
else:
loop = next_loop.link_loop_radial_next.link_loop_next
#info(f"> to {self._show_loop(loop)}")
return loop
def _prev_loop(self):
# Find the next loop in "backward" direction.
# This method is called only when processing the last row of vertices.
start_k = len(self.current_vert.link_edges)
next_loop = self.current_loop.link_loop_prev
other_vert = next_loop.edge.other_vert(next_loop.vert)
k = len(other_vert.link_edges)
#info(f"< from {self._show_loop(self.current_loop)} => {self._show_loop(next_loop)}, k={k} for v.{other_vert.index}")
if k == 2:
loop = next_loop
else:
loop = next_loop.link_loop_radial_prev.link_loop_prev
#info(f"< to {self._show_loop(loop)}")
return loop
def _step_forward(self):
# Step to the next loop in row.
self.current_loop = self._next_loop()
self.current_vert = self.current_loop.vert
def _step_backward(self):
# Step to the previous loop in row.
# This method is called only when processing the last loop of vertices.
self.current_loop = self._prev_loop()
self.current_vert = self.current_loop.vert
def _walk_row_forward(self):
# Walk along the row of vertices in "forward" direction.
self.row_start_loop = self.current_loop
self.row_start_vert = self.current_vert
self.row_start_k = len(self.current_vert.link_edges)
result = [self.current_vert.index]
while True:
self._step_forward()
k = len(self.current_vert.link_edges)
#info(f"> C: {self.current_vert.index}, k={k}")
result.append(self.current_vert.index)
if self.current_vert.is_boundary and k == self.row_start_k:
break
n = len(result)
if self.row_length is None:
self.row_length = n
elif self.row_length != n:
raise Exception(f"Row has length {n} instead of {self.row_length}")
return result
def _walk_row_backward(self):
# Walk along the row of vertices in "backward" direction.
# This method is called only when processing the last loop of vertices.
if self.row_length is None:
raise Exception("_walk_row_backward was called before setting row_length")
self.row_start_loop = self.current_loop
self.row_start_vert = self.current_vert
self.row_start_k = len(self.current_vert.link_edges)
edge = self.current_loop.edge
vert = edge.other_vert(self.current_vert)
result = [vert.index]
for i in range(self.row_length-1):
self._step_backward()
k = len(self.current_vert.link_edges)
#info(f"< C[{i}]: {self.current_vert.index}, k={k}")
edge = self.current_loop.edge
vert = edge.other_vert(self.current_vert)
result.append(vert.index)
n = len(result)
if n == self.row_length - 1:
result.append(self.current_vert.index)
elif self.row_length != n:
raise Exception(f"(Last) Row has length {n} ({result}) instead of {self.row_length}")
return result
def _next_row(self):
# Go to the next row of vertices.
# Return True if this row is the last one.
self.current_loop = self.row_start_loop
start_face_idx = self.current_loop.face.index
loop = self.current_loop.link_loop_prev.link_loop_prev.link_loop_radial_next
#info(f"N/: F {loop.face.index}, V {loop.vert.index}, E {self._show_loop(loop)}")
self.current_loop = loop
self.current_vert = self.current_loop.vert
last = loop.face.index == start_face_idx
return last
def get_verts_sequence(self, flat=False):
"""
Return list of lists of vertices indexes,
sorted row-by-row.
"""
result = []
last = False
while not last:
row = self._walk_row_forward()
result.append(row)
last = self._next_row()
row = self._walk_row_backward()
result.append(row)
if flat:
result = sum(result, [])
self._reset()
return result
def get_ordered_verts(self, flat=False):
"""
Return list of lists of vertices coordinates,
sorted row by row.
If flat==True, then return flat list of vertices.
"""
result = []
for row_idxs in self.get_verts_sequence():
row = [tuple(self.bmesh.verts[i].co) for i in row_idxs]
if flat:
result.extend(row)
else:
result.append(row)
return result
Classes
class SvQuadGridParser (bm)
-
Parse a "bmesh" object, which contains a "quad grid", i.e. mesh consisting of quads only, in topology similar to subdivided plane (m x n vertices). Return vertices ordered row-by-row. Raise exception if the given mesh has invalid topology.
Expand source code
class SvQuadGridParser(object): """ Parse a "bmesh" object, which contains a "quad grid", i.e. mesh consisting of quads only, in topology similar to subdivided plane (m x n vertices). Return vertices ordered row-by-row. Raise exception if the given mesh has invalid topology. """ def __init__(self, bm): self.bmesh = bm self._init_check() self._init() def _init_check(self): # Run some checks that the mesh has correct topology. # Not all necessary conditions are checked here, only # simplest ones. for face in self.bmesh.faces: if len(face.verts) != 4: raise Exception(f"One of faces has {len(face.verts)} vertices instead of 4") corners_cnt = 0 edges_cnt = 0 for vert in self.bmesh.verts: if vert.is_wire: raise Exception("One of verts is a wire") k = len(vert.link_edges) if k not in {2,3,4}: raise Exception(f"One of vertices has {k} adjacent edges instead of 2,3,4") if k == 4 and vert.is_boundary: raise Exception("Vertex with 4 adjacent edges is boundary") if k != 4 and not vert.is_boundary: raise Exception(f"Vertex with {k} adjacent edges is not boundary") if k == 2: corners_cnt += 1 if k == 3: edges_cnt += 1 if corners_cnt != 4: raise Exception(f"Mesh has {corners_cnt} corners instead of 4") if edges_cnt % 2 != 0: raise Exception("Mesh has {edges_cnt} edge vertices, which does not divide by 2") for edge in self.bmesh.edges: k = len(edge.link_faces) if k not in {1,2}: raise Exception(f"One of edges has {k} adjacent faces instead of 1,2") if k == 2 and edge.is_boundary: raise Exception("Edge with 2 adjacent faces is boundary!?") if k == 1 and not edge.is_boundary: raise Exception("Edge with 1 adjacent face is not boundary!?") def _init(self): # Find a corner to start with. # We are searching for such a vertex and it's loop, # so the edge is pointing in positive X direction. # If we can't find such, then search for positive Y... self.row_length = None self.y_direction = False good_x = [] good_y = [] for vert in self.bmesh.verts: if len(vert.link_edges) == 2: for loop in vert.link_loops: other_vert = loop.edge.other_vert(vert) #info(f"V#{vert.index}: x {vert.co.x} => #{other_vert.index} {other_vert.co.x}") if other_vert.co.x > vert.co.x: good_x.append(loop) if other_vert.co.y > vert.co.y: good_y.append(loop) if good_x: loop = good_x[0] elif good_y: loop = good_y[0] self.y_direction = True else: raise Exception("Could not find a vertex to start from") vert = loop.vert #info(f"Start with v.{vert.index}, loop {self._show_loop(loop)}") self.current_loop = loop self.current_vert = vert self.start_loop = loop self.start_vert = vert def _reset(self): self.current_loop = self.start_loop self.current_vert = self.start_vert def _show_loop(self, loop): # Debug utility vert = loop.vert other_vert = loop.edge.other_vert(vert) return f"[{vert.index}->{other_vert.index}]({loop.face.index})" def _next_loop(self): # Find the next loop in "forward" direction start_k = len(self.current_vert.link_edges) next_loop = self.current_loop.link_loop_next k = len(next_loop.vert.link_edges) #info(f"> from {self._show_loop(self.current_loop)} => {self._show_loop(next_loop)}, k={k} (for v.{next_loop.vert.index}) of {self.row_start_k}") if k == self.row_start_k: loop = next_loop else: loop = next_loop.link_loop_radial_next.link_loop_next #info(f"> to {self._show_loop(loop)}") return loop def _prev_loop(self): # Find the next loop in "backward" direction. # This method is called only when processing the last row of vertices. start_k = len(self.current_vert.link_edges) next_loop = self.current_loop.link_loop_prev other_vert = next_loop.edge.other_vert(next_loop.vert) k = len(other_vert.link_edges) #info(f"< from {self._show_loop(self.current_loop)} => {self._show_loop(next_loop)}, k={k} for v.{other_vert.index}") if k == 2: loop = next_loop else: loop = next_loop.link_loop_radial_prev.link_loop_prev #info(f"< to {self._show_loop(loop)}") return loop def _step_forward(self): # Step to the next loop in row. self.current_loop = self._next_loop() self.current_vert = self.current_loop.vert def _step_backward(self): # Step to the previous loop in row. # This method is called only when processing the last loop of vertices. self.current_loop = self._prev_loop() self.current_vert = self.current_loop.vert def _walk_row_forward(self): # Walk along the row of vertices in "forward" direction. self.row_start_loop = self.current_loop self.row_start_vert = self.current_vert self.row_start_k = len(self.current_vert.link_edges) result = [self.current_vert.index] while True: self._step_forward() k = len(self.current_vert.link_edges) #info(f"> C: {self.current_vert.index}, k={k}") result.append(self.current_vert.index) if self.current_vert.is_boundary and k == self.row_start_k: break n = len(result) if self.row_length is None: self.row_length = n elif self.row_length != n: raise Exception(f"Row has length {n} instead of {self.row_length}") return result def _walk_row_backward(self): # Walk along the row of vertices in "backward" direction. # This method is called only when processing the last loop of vertices. if self.row_length is None: raise Exception("_walk_row_backward was called before setting row_length") self.row_start_loop = self.current_loop self.row_start_vert = self.current_vert self.row_start_k = len(self.current_vert.link_edges) edge = self.current_loop.edge vert = edge.other_vert(self.current_vert) result = [vert.index] for i in range(self.row_length-1): self._step_backward() k = len(self.current_vert.link_edges) #info(f"< C[{i}]: {self.current_vert.index}, k={k}") edge = self.current_loop.edge vert = edge.other_vert(self.current_vert) result.append(vert.index) n = len(result) if n == self.row_length - 1: result.append(self.current_vert.index) elif self.row_length != n: raise Exception(f"(Last) Row has length {n} ({result}) instead of {self.row_length}") return result def _next_row(self): # Go to the next row of vertices. # Return True if this row is the last one. self.current_loop = self.row_start_loop start_face_idx = self.current_loop.face.index loop = self.current_loop.link_loop_prev.link_loop_prev.link_loop_radial_next #info(f"N/: F {loop.face.index}, V {loop.vert.index}, E {self._show_loop(loop)}") self.current_loop = loop self.current_vert = self.current_loop.vert last = loop.face.index == start_face_idx return last def get_verts_sequence(self, flat=False): """ Return list of lists of vertices indexes, sorted row-by-row. """ result = [] last = False while not last: row = self._walk_row_forward() result.append(row) last = self._next_row() row = self._walk_row_backward() result.append(row) if flat: result = sum(result, []) self._reset() return result def get_ordered_verts(self, flat=False): """ Return list of lists of vertices coordinates, sorted row by row. If flat==True, then return flat list of vertices. """ result = [] for row_idxs in self.get_verts_sequence(): row = [tuple(self.bmesh.verts[i].co) for i in row_idxs] if flat: result.extend(row) else: result.append(row) return result
Methods
def get_ordered_verts(self, flat=False)
-
Return list of lists of vertices coordinates, sorted row by row. If flat==True, then return flat list of vertices.
Expand source code
def get_ordered_verts(self, flat=False): """ Return list of lists of vertices coordinates, sorted row by row. If flat==True, then return flat list of vertices. """ result = [] for row_idxs in self.get_verts_sequence(): row = [tuple(self.bmesh.verts[i].co) for i in row_idxs] if flat: result.extend(row) else: result.append(row) return result
def get_verts_sequence(self, flat=False)
-
Return list of lists of vertices indexes, sorted row-by-row.
Expand source code
def get_verts_sequence(self, flat=False): """ Return list of lists of vertices indexes, sorted row-by-row. """ result = [] last = False while not last: row = self._walk_row_forward() result.append(row) last = self._next_row() row = self._walk_row_backward() result.append(row) if flat: result = sum(result, []) self._reset() return result