# ##### BEGIN GPL LICENSE BLOCK ##### # # 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 2 # of the License, or (at your option) 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, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### # Filename : ChainingIterators.py # Author : Stephane Grabli # Date : 04/08/2005 # Purpose : Chaining Iterators to be used with chaining operators from freestyle import AdjacencyIterator, ChainingIterator, ExternalContourUP1D, Nature, TVertex from freestyle import ContextFunctions as CF import bpy ## the natural chaining iterator ## It follows the edges of same nature following the topology of ## objects with preseance on silhouettes, then borders, ## then suggestive contours, then everything else. It doesn't chain the same ViewEdge twice ## You can specify whether to stay in the selection or not. class pyChainSilhouetteIterator(ChainingIterator): def __init__(self, stayInSelection=True): ChainingIterator.__init__(self, stayInSelection, True, None, True) def init(self): pass def traverse(self, iter): winner = None it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for i in range(len(natures)): currentNature = self.current_edge.nature if (natures[i] & currentNature) != 0: count=0 while not it.is_end: visitNext = 0 oNature = it.object.nature if (oNature & natures[i]) != 0: if natures[i] != oNature: for j in range(i): if (natures[j] & oNature) != 0: visitNext = 1 break if visitNext != 0: break count = count+1 winner = it.object it.increment() if count != 1: winner = None break return winner ## the natural chaining iterator ## It follows the edges of same nature on the same ## objects with preseance on silhouettes, then borders, ## then suggestive contours, then everything else. It doesn't chain the same ViewEdge twice ## You can specify whether to stay in the selection or not. ## You can specify whether to chain iterate over edges that were ## already visited or not. class pyChainSilhouetteGenericIterator(ChainingIterator): def __init__(self, stayInSelection=True, stayInUnvisited=True): ChainingIterator.__init__(self, stayInSelection, stayInUnvisited, None, True) def init(self): pass def traverse(self, iter): winner = None it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for i in range(len(natures)): currentNature = self.current_edge.nature if (natures[i] & currentNature) != 0: count=0 while not it.is_end: visitNext = 0 oNature = it.object.nature ve = it.object if ve.id == self.current_edge.id: it.increment() continue if (oNature & natures[i]) != 0: if natures[i] != oNature: for j in range(i): if (natures[j] & oNature) != 0: visitNext = 1 break if visitNext != 0: break count = count+1 winner = ve it.increment() if count != 1: winner = None break return winner class pyExternalContourChainingIterator(ChainingIterator): def __init__(self): ChainingIterator.__init__(self, False, True, None, True) self._isExternalContour = ExternalContourUP1D() def init(self): self._nEdges = 0 self._isInSelection = 1 def checkViewEdge(self, ve, orientation): if orientation != 0: vertex = ve.second_svertex() else: vertex = ve.first_svertex() it = AdjacencyIterator(vertex,1,1) while not it.is_end: ave = it.object if self._isExternalContour(ave): return 1 it.increment() print("pyExternlContourChainingIterator : didn't find next edge") return 0 def traverse(self, iter): winner = None it = AdjacencyIterator(iter) while not it.is_end: ve = it.object if self._isExternalContour(ve): if ve.time_stamp == CF.get_time_stamp(): winner = ve it.increment() self._nEdges = self._nEdges+1 if winner is None: orient = 1 it = AdjacencyIterator(iter) while not it.is_end: ve = it.object if it.is_incoming: orient = 0 good = self.checkViewEdge(ve,orient) if good != 0: winner = ve it.increment() return winner ## the natural chaining iterator ## with a sketchy multiple touch class pySketchyChainSilhouetteIterator(ChainingIterator): def __init__(self, nRounds=3,stayInSelection=True): ChainingIterator.__init__(self, stayInSelection, False, None, True) self._timeStamp = CF.get_time_stamp()+nRounds self._nRounds = nRounds def init(self): self._timeStamp = CF.get_time_stamp()+self._nRounds def traverse(self, iter): winner = None it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for i in range(len(natures)): currentNature = self.current_edge.nature if (natures[i] & currentNature) != 0: count=0 while not it.is_end: visitNext = 0 oNature = it.object.nature ve = it.object if ve.id == self.current_edge.id: it.increment() continue if (oNature & natures[i]) != 0: if (natures[i] != oNature) != 0: for j in range(i): if (natures[j] & oNature) != 0: visitNext = 1 break if visitNext != 0: break count = count+1 winner = ve it.increment() if count != 1: winner = None break if winner is None: winner = self.current_edge if winner.chaining_time_stamp == self._timeStamp: winner = None return winner # Chaining iterator designed for sketchy style. # can chain several times the same ViewEdge # in order to produce multiple strokes per ViewEdge. class pySketchyChainingIterator(ChainingIterator): def __init__(self, nRounds=3, stayInSelection=True): ChainingIterator.__init__(self, stayInSelection, False, None, True) self._timeStamp = CF.get_time_stamp()+nRounds self._nRounds = nRounds def init(self): self._timeStamp = CF.get_time_stamp()+self._nRounds def traverse(self, iter): winner = None found = False it = AdjacencyIterator(iter) while not it.is_end: ve = it.object if ve.id == self.current_edge.id: found = True it.increment() continue winner = ve it.increment() if not found: # This is a fatal error condition: self.current_edge must be found # among the edges seen by the AdjacencyIterator [bug #35695]. if bpy.app.debug_freestyle: print('pySketchyChainingIterator: current edge not found') return None if winner is None: winner = self.current_edge if winner.chaining_time_stamp == self._timeStamp: return None return winner ## Chaining iterator that fills small occlusions ## percent ## The max length of the occluded part ## expressed in % of the total chain length class pyFillOcclusionsRelativeChainingIterator(ChainingIterator): def __init__(self, percent): ChainingIterator.__init__(self, False, True, None, True) self._length = 0 self._percent = float(percent) def init(self): # each time we're evaluating a chain length # we try to do it once. Thus we reinit # the chain length here: self._length = 0 def traverse(self, iter): winner = None winnerOrientation = 0 #print(self.current_edge.id.first, self.current_edge.id.second) it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for nat in natures: if (self.current_edge.nature & nat) != 0: count=0 while not it.is_end: ve = it.object if (ve.nature & nat) != 0: count = count+1 winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 it.increment() if count != 1: winner = None break if winner is not None: # check whether this edge was part of the selection if winner.time_stamp != CF.get_time_stamp(): #print("---", winner.id.first, winner.id.second) # if not, let's check whether it's short enough with # respect to the chain made without staying in the selection #------------------------------------------------------------ # Did we compute the prospective chain length already ? if self._length == 0: #if not, let's do it _it = pyChainSilhouetteGenericIterator(0,0) _it.begin = winner _it.current_edge = winner _it.orientation = winnerOrientation _it.init() while not _it.is_end: ve = _it.object #print("--------", ve.id.first, ve.id.second) self._length = self._length + ve.length_2d _it.increment() if _it.is_begin: break; _it.begin = winner _it.current_edge = winner _it.orientation = winnerOrientation if not _it.is_begin: _it.decrement() while (not _it.is_end) and (not _it.is_begin): ve = _it.object #print("--------", ve.id.first, ve.id.second) self._length = self._length + ve.length_2d _it.decrement() # let's do the comparison: # nw let's compute the length of this connex non selected part: connexl = 0 _cit = pyChainSilhouetteGenericIterator(0,0) _cit.begin = winner _cit.current_edge = winner _cit.orientation = winnerOrientation _cit.init() while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp(): ve = _cit.object #print("-------- --------", ve.id.first, ve.id.second) connexl = connexl + ve.length_2d _cit.increment() if connexl > self._percent * self._length: winner = None return winner ## Chaining iterator that fills small occlusions ## size ## The max length of the occluded part ## expressed in pixels class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator): def __init__(self, length): ChainingIterator.__init__(self, False, True, None, True) self._length = float(length) def init(self): pass def traverse(self, iter): winner = None winnerOrientation = 0 #print(self.current_edge.id.first, self.current_edge.id.second) it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for nat in natures: if (self.current_edge.nature & nat) != 0: count=0 while not it.is_end: ve = it.object if (ve.nature & nat) != 0: count = count+1 winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 it.increment() if count != 1: winner = None break if winner is not None: # check whether this edge was part of the selection if winner.time_stamp != CF.get_time_stamp(): #print("---", winner.id.first, winner.id.second) # nw let's compute the length of this connex non selected part: connexl = 0 _cit = pyChainSilhouetteGenericIterator(0,0) _cit.begin = winner _cit.current_edge = winner _cit.orientation = winnerOrientation _cit.init() while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp(): ve = _cit.object #print("-------- --------", ve.id.first, ve.id.second) connexl = connexl + ve.length_2d _cit.increment() if connexl > self._length: winner = None return winner ## Chaining iterator that fills small occlusions ## percent ## The max length of the occluded part ## expressed in % of the total chain length class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator): def __init__(self, percent, l): ChainingIterator.__init__(self, False, True, None, True) self._length = 0 self._absLength = l self._percent = float(percent) def init(self): # each time we're evaluating a chain length # we try to do it once. Thus we reinit # the chain length here: self._length = 0 def traverse(self, iter): winner = None winnerOrientation = 0 #print(self.current_edge.id.first, self.current_edge.id.second) it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for nat in natures: if (self.current_edge.nature & nat) != 0: count=0 while not it.is_end: ve = it.object if (ve.nature & nat) != 0: count = count+1 winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 it.increment() if count != 1: winner = None break if winner is not None: # check whether this edge was part of the selection if winner.time_stamp != CF.get_time_stamp(): #print("---", winner.id.first, winner.id.second) # if not, let's check whether it's short enough with # respect to the chain made without staying in the selection #------------------------------------------------------------ # Did we compute the prospective chain length already ? if self._length == 0: #if not, let's do it _it = pyChainSilhouetteGenericIterator(0,0) _it.begin = winner _it.current_edge = winner _it.orientation = winnerOrientation _it.init() while not _it.is_end: ve = _it.object #print("--------", ve.id.first, ve.id.second) self._length = self._length + ve.length_2d _it.increment() if _it.is_begin: break; _it.begin = winner _it.current_edge = winner _it.orientation = winnerOrientation if not _it.is_begin: _it.decrement() while (not _it.is_end) and (not _it.is_begin): ve = _it.object #print("--------", ve.id.first, ve.id.second) self._length = self._length + ve.length_2d _it.decrement() # let's do the comparison: # nw let's compute the length of this connex non selected part: connexl = 0 _cit = pyChainSilhouetteGenericIterator(0,0) _cit.begin = winner _cit.current_edge = winner _cit.orientation = winnerOrientation _cit.init() while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp(): ve = _cit.object #print("-------- --------", ve.id.first, ve.id.second) connexl = connexl + ve.length_2d _cit.increment() if (connexl > self._percent * self._length) or (connexl > self._absLength): winner = None return winner ## Chaining iterator that fills small occlusions without caring about the ## actual selection ## percent ## The max length of the occluded part ## expressed in % of the total chain length class pyFillQi0AbsoluteAndRelativeChainingIterator(ChainingIterator): def __init__(self, percent, l): ChainingIterator.__init__(self, False, True, None, True) self._length = 0 self._absLength = l self._percent = float(percent) def init(self): # each time we're evaluating a chain length # we try to do it once. Thus we reinit # the chain length here: self._length = 0 def traverse(self, iter): winner = None winnerOrientation = 0 #print(self.current_edge.id.first, self.current_edge.id.second) it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object if ve.id == mateVE.id: winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for nat in natures: if (self.current_edge.nature & nat) != 0: count=0 while not it.is_end: ve = it.object if (ve.nature & nat) != 0: count = count+1 winner = ve if not it.is_incoming: winnerOrientation = 1 else: winnerOrientation = 0 it.increment() if count != 1: winner = None break if winner is not None: # check whether this edge was part of the selection if winner.qi != 0: #print("---", winner.id.first, winner.id.second) # if not, let's check whether it's short enough with # respect to the chain made without staying in the selection #------------------------------------------------------------ # Did we compute the prospective chain length already ? if self._length == 0: #if not, let's do it _it = pyChainSilhouetteGenericIterator(0,0) _it.begin = winner _it.current_edge = winner _it.orientation = winnerOrientation _it.init() while not _it.is_end: ve = _it.object #print("--------", ve.id.first, ve.id.second) self._length = self._length + ve.length_2d _it.increment() if _it.is_begin: break; _it.begin = winner _it.current_edge = winner _it.orientation = winnerOrientation if not _it.is_begin: _it.decrement() while (not _it.is_end) and (not _it.is_begin): ve = _it.object #print("--------", ve.id.first, ve.id.second) self._length = self._length + ve.length_2d _it.decrement() # let's do the comparison: # nw let's compute the length of this connex non selected part: connexl = 0 _cit = pyChainSilhouetteGenericIterator(0,0) _cit.begin = winner _cit.current_edge = winner _cit.orientation = winnerOrientation _cit.init() while not _cit.is_end and _cit.object.qi != 0: ve = _cit.object #print("-------- --------", ve.id.first, ve.id.second) connexl = connexl + ve.length_2d _cit.increment() if (connexl > self._percent * self._length) or (connexl > self._absLength): winner = None return winner ## the natural chaining iterator ## It follows the edges of same nature on the same ## objects with preseance on silhouettes, then borders, ## then suggestive contours, then everything else. It doesn't chain the same ViewEdge twice ## You can specify whether to stay in the selection or not. class pyNoIdChainSilhouetteIterator(ChainingIterator): def __init__(self, stayInSelection=True): ChainingIterator.__init__(self, stayInSelection, True, None, True) def init(self): pass def traverse(self, iter): winner = None it = AdjacencyIterator(iter) tvertex = self.next_vertex if type(tvertex) is TVertex: mateVE = tvertex.get_mate(self.current_edge) while not it.is_end: ve = it.object feB = self.current_edge.last_fedge feA = ve.first_fedge vB = feB.second_svertex vA = feA.first_svertex if vA.id.first == vB.id.first: winner = ve break feA = self.current_edge.first_fedge feB = ve.last_fedge vB = feB.second_svertex vA = feA.first_svertex if vA.id.first == vB.id.first: winner = ve break feA = self.current_edge.last_fedge feB = ve.last_fedge vB = feB.second_svertex vA = feA.second_svertex if vA.id.first == vB.id.first: winner = ve break feA = self.current_edge.first_fedge feB = ve.first_fedge vB = feB.first_svertex vA = feA.first_svertex if vA.id.first == vB.id.first: winner = ve break it.increment() else: ## case of NonTVertex natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] for i in range(len(natures)): currentNature = self.current_edge.nature if (natures[i] & currentNature) != 0: count=0 while not it.is_end: visitNext = 0 oNature = it.object.nature if (oNature & natures[i]) != 0: if natures[i] != oNature: for j in range(i): if (natures[j] & oNature) != 0: visitNext = 1 break if visitNext != 0: break count = count+1 winner = it.object it.increment() if count != 1: winner = None break return winner