diff options
author | Tamito Kajiyama <rd6t-kjym@asahi-net.or.jp> | 2014-06-24 08:52:12 +0400 |
---|---|---|
committer | Tamito Kajiyama <rd6t-kjym@asahi-net.or.jp> | 2014-06-24 08:53:14 +0400 |
commit | ce729677db3e9934e2e2c56a9397a868c7546ee9 (patch) | |
tree | 611fd266ddd031b3b7ead67e59d59cd39ab9cb13 /release/scripts | |
parent | f37ec65f8011ec7b642887b6103b2e5d932f127d (diff) |
D319: Freestyle Python scripts update.
This revision is meant to update Freestyle's Python scripts to make full usage
of the new features of Python and Freestyle's Python API.
Freestyle's Python scripts are pretty old already, and were never given much
attention. With the 2.7x generation of Blender coming up, this is an excellent
time to update Freestyle's Python scripts, hopefully adding some new features
and achieving some speed improvements on the way.
Main goals:
* use for loops where possible
* general cleanup, making use of more recent python features (generators,
ternary operator, ect.)
* update the documentation on the way (it's lacking atm)
Differential revision: https://developer.blender.org/D319
Author: flokkievids (Folkert de Vries)
Reviewed by: kjym3 (Tamito Kajiyama)
Diffstat (limited to 'release/scripts')
5 files changed, 1310 insertions, 1546 deletions
diff --git a/release/scripts/freestyle/modules/freestyle/chainingiterators.py b/release/scripts/freestyle/modules/freestyle/chainingiterators.py index ae0a8a04294..080919f6bc3 100644 --- a/release/scripts/freestyle/modules/freestyle/chainingiterators.py +++ b/release/scripts/freestyle/modules/freestyle/chainingiterators.py @@ -23,6 +23,20 @@ rules. Also intended to be a collection of examples for defining chaining iterators in Python """ +__all__ = ( + "pyChainSilhouetteIterator", + "pyChainSilhouetteGenericIterator", + "pyExternalContourChainingIterator", + "pySketchyChainSilhouetteIterator", + "pySketchyChainingIterator", + "pyFillOcclusionsRelativeChainingIterator", + "pyFillOcclusionsAbsoluteChainingIterator", + "pyFillOcclusionsAbsoluteAndRelativeChainingIterator", + "pyFillQi0AbsoluteAndRelativeChainingIterator", + "pyNoIdChainSilhouetteIterator", + ) + + # module members from _freestyle import ( ChainPredicateIterator, @@ -41,11 +55,30 @@ from freestyle.predicates import ( ) from freestyle.utils import ( ContextFunctions as CF, - stroke_normal, + get_chain_length, + find_matching_vertex, ) + import bpy +NATURES = ( + Nature.SILHOUETTE, + Nature.BORDER, + Nature.CREASE, + Nature.MATERIAL_BOUNDARY, + Nature.EDGE_MARK, + Nature.SUGGESTIVE_CONTOUR, + Nature.VALLEY, + Nature.RIDGE + ) + + +def nature_in_preceding(nature, index): + """ Returns True if given nature appears before index, else False """ + return any(nature & nat for nat in NATURES[:index]) + + class pyChainSilhouetteIterator(ChainingIterator): """Natural chaining iterator @@ -61,43 +94,28 @@ class pyChainSilhouetteIterator(ChainingIterator): 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.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + return find_matching_vertex(mate.id, it) + ## case of NonTVertex + winner = None + for i, nat in enumerate(NATURES): + if (nat & self.current_edge.nature): + for ve in it: + ve_nat = ve.nature + if (ve_nat & nat): + # search for matches in previous natures. if match -> break + if nat != ve_nat and nature_in_preceding(ve_nat, index=i): + break + # a second match must be an error + if winner is not None: + return None + # assign winner + winner = ve + return winner class pyChainSilhouetteGenericIterator(ChainingIterator): @@ -120,47 +138,30 @@ class pyChainSilhouetteGenericIterator(ChainingIterator): 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.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + return find_matching_vertex(mate.id, it) + ## case of NonTVertex + winner = None + for i, nat in enumerate(NATURES): + if (nat & self.current_edge.nature): + for ve in it: + ve_nat = ve.nature + if ve.id == self.current_edge.id: + continue + if (ve_nat & nat): + if nat != ve_nat and nature_in_preceding(ve_nat, index=i): + break + + if winner is not None: + return None + + winner = ve + return winner + return None class pyExternalContourChainingIterator(ChainingIterator): @@ -168,49 +169,40 @@ class pyExternalContourChainingIterator(ChainingIterator): def __init__(self): ChainingIterator.__init__(self, False, True, None, True) - self._isExternalContour = ExternalContourUP1D() + self.ExternalContour = 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 True - it.increment() - if bpy.app.debug_freestyle: + vertex = (ve.first_viewvertex if orientation else + ve.last_viewvertex) + + it = AdjacencyIterator(vertex, True, True) + result = any(self.ExternalContour(ave) for ave in it) + # report if there is no result (that's bad) + if not result and bpy.app.debug_freestyle: print("pyExternalContourChainingIterator : didn't find next edge") - return False + + return result def traverse(self, iter): winner = None + self._nEdges += 1 + 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() + time_stamp = CF.get_time_stamp() + + for ve in it: + if self.ExternalContour(ve) and ve.time_stamp == time_stamp: + winner = ve - 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: + for ve in it: + if self.checkViewEdge(ve, not it.is_incoming): winner = ve - it.increment() + return winner @@ -227,58 +219,49 @@ 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._timeStamp = CF.get_time_stamp() + nRounds self._nRounds = nRounds def init(self): - self._timeStamp = CF.get_time_stamp()+self._nRounds + self._timeStamp = CF.get_time_stamp() + self._nRounds + + # keeping this local saves passing a reference to 'self' around + def make_sketchy(self, ve): + """ + Creates the skeychy effect by causing the chain to run from + the start again. (loop over itself again) + """ + if ve is None: + ve = self.current_edge + if ve.chaining_time_stamp == self._timeStamp: + return None + return ve 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.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + return self.make_sketchy(find_matching_vertex(mate.id, it)) + ## case of NonTVertex + winner = None + for i, nat in enumerate(NATURES): + if (nat & self.current_edge.nature): + for ve in it: + if ve.id == self.current_edge.id: + continue + ve_nat = ve.nature + if (ve_nat & nat): + if nat != ve_nat and nature_in_preceding(ve_nat, i): + break + + if winner is not None: + return self.make_sketchy(None) + + winner = ve + break + return self.make_sketchy(winner) class pySketchyChainingIterator(ChainingIterator): @@ -289,30 +272,30 @@ 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._timeStamp = CF.get_time_stamp() + nRounds self._nRounds = nRounds + self.t = False def init(self): - self._timeStamp = CF.get_time_stamp()+self._nRounds + 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: + + for ve in AdjacencyIterator(iter): + if self.current_edge.id == ve.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: @@ -330,97 +313,61 @@ class pyFillOcclusionsRelativeChainingIterator(ChainingIterator): def __init__(self, percent): ChainingIterator.__init__(self, False, True, None, True) - self._length = 0 + self._length = 0.0 self._percent = float(percent) + self.timestamp = CF.get_time_stamp() def init(self): # A chain's length should preferably be evaluated only once. # Therefore, the chain length is reset here. - self._length = 0 + self._length = 0.0 def traverse(self, iter): winner = None - winnerOrientation = False - 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 - winnerOrientation = not it.is_incoming - break - it.increment() + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + winner = find_matching_vertex(mate.id, it) + winnerOrientation = not it.is_incoming if not it.is_end else False + ## case of NonTVertex else: - ## case of NonTVertex - natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + for nat in NATURES: + if (self.current_edge.nature & nat): + for ve in it: + if (ve.nature & nat): + if winner is not None: + return None winner = ve winnerOrientation = not it.is_incoming - 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(False, False) - _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(False, False) - _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 + # check timestamp to see if this edge was part of the selection + if winner is not None and winner.time_stamp != self.timestamp: + # if the edge wasn't part of the selection, let's see + # whether it's short enough (with respect to self.percent) + # to be included. + if self._length == 0.0: + self._length = get_chain_length(winner, winnerOrientation) + + # check if the gap can be bridged + connexl = 0.0 + _cit = pyChainSilhouetteGenericIterator(False, False) + _cit.begin = winner + _cit.current_edge = winner + _cit.orientation = winnerOrientation + _cit.init() + + while (not _cit.is_end) and _cit.object.time_stamp != self.timestamp: + connexl += _cit.object.length_2d + _cit.increment() + if _cit.is_begin: break + + if connexl > self._percent * self._length: + return None + return winner @@ -433,6 +380,7 @@ class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator): def __init__(self, length): ChainingIterator.__init__(self, False, True, None, True) self._length = float(length) + self.timestamp = CF.get_time_stamp() def init(self): pass @@ -440,53 +388,41 @@ class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator): def traverse(self, iter): winner = None winnerOrientation = False - #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 - winnerOrientation = not it.is_incoming - break - it.increment() + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + winner = find_matching_vertex(mate.id, it) + winnerOrientation = not it.is_incoming if not it.is_end else False + ## case of NonTVertex else: - ## case of NonTVertex - natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + for nat in NATURES: + if (self.current_edge.nature & nat): + for ve in it: + if (ve.nature & nat): + if winner is not None: + return None winner = ve winnerOrientation = not it.is_incoming - 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(False, False) - _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 + + if winner is not None and winner.time_stamp != self.timestamp: + connexl = 0.0 + _cit = pyChainSilhouetteGenericIterator(False, False) + _cit.begin = winner + _cit.current_edge = winner + _cit.orientation = winnerOrientation + _cit.init() + + while (not _cit.is_end) and _cit.object.time_stamp != self.timestamp: + connexl += _cit.object.length_2d + _cit.increment() + if _cit.is_begin: break + + if connexl > self._length: + return None + return winner @@ -500,7 +436,7 @@ class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator): """ def __init__(self, percent, l): ChainingIterator.__init__(self, False, True, None, True) - self._length = 0 + self._length = 0.0 self._absLength = l self._percent = float(percent) @@ -508,88 +444,48 @@ class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator): # each time we're evaluating a chain length # we try to do it once. Thus we reinit # the chain length here: - self._length = 0 + self._length = 0.0 def traverse(self, iter): winner = None winnerOrientation = False - #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 - winnerOrientation = not it.is_incoming - break - it.increment() + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + winner = find_matching_vertex(mate.id, it) + winnerOrientation = not it.is_incoming if not it.is_end else False + ## case of NonTVertex else: - ## case of NonTVertex - natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + for nat in NATURES: + if (self.current_edge.nature & nat): + for ve in it: + if (ve.nature & nat): + if winner is not None: + return None winner = ve winnerOrientation = not it.is_incoming - 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(False, False) - _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 + + if winner is not None and winner.time_stamp != CF.get_time_stamp(): + + if self._length == 0.0: + self._length = get_chain_length(winner, winnerOrientation) + + connexl = 0.0 _cit = pyChainSilhouetteGenericIterator(False, False) _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 + while (not _cit.is_end) and _cit.object.time_stamp != CF.get_time_stamp(): + connexl += _cit.object.length_2d _cit.increment() + if _cit.is_begin: break + if (connexl > self._percent * self._length) or (connexl > self._absLength): - winner = None + return None return winner @@ -603,96 +499,55 @@ class pyFillQi0AbsoluteAndRelativeChainingIterator(ChainingIterator): """ def __init__(self, percent, l): ChainingIterator.__init__(self, False, True, None, True) - self._length = 0 + self._length = 0.0 self._absLength = l - self._percent = float(percent) + self._percent = percent def init(self): # A chain's length should preverably be evaluated only once. # Therefore, the chain length is reset here. - self._length = 0 + self._length = 0.0 def traverse(self, iter): winner = None winnerOrientation = False - - #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 - winnerOrientation = not it.is_incoming - break - it.increment() + ## case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + mate = vertex.get_mate(self.current_edge) + winner = find_matching_vertex(mate.id, it) + winnerOrientation = not it.is_incoming if not it.is_end else False + ## case of NonTVertex else: - ## case of NonTVertex - natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + for nat in NATURES: + if (self.current_edge.nature & nat): + for ve in it: + if (ve.nature & nat): + if winner is not None: + return None winner = ve winnerOrientation = not it.is_incoming - 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(False, False) - _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: + + if winner is not None and winner.qi: + + + if self._length == 0.0: + self._length = get_chain_length(winner, winnerOrientation) + connexl = 0 _cit = pyChainSilhouetteGenericIterator(False, False) _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 + while (not _cit.is_end) and _cit.object.qi != 0: + connexl += _cit.object.length_2d _cit.increment() + if _cit.is_begin: break if (connexl > self._percent * self._length) or (connexl > self._absLength): - winner = None + return None return winner @@ -717,63 +572,44 @@ class pyNoIdChainSilhouetteIterator(ChainingIterator): 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 + # case of TVertex + vertex = self.next_vertex + if type(vertex) is TVertex: + for ve in it: + # case one + vA = self.current_edge.last_fedge.second_svertex + vB = ve.first_fedge.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 + return ve + # case two + vA = self.current_edge.first_fedge.first_svertex + vB = ve.last_fedge.second_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 + return ve + # case three + vA = self.current_edge.last_fedge.second_svertex + vB = ve.last_fedge.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 + return ve + # case four + vA = self.current_edge.first_fedge.first_svertex + vB = ve.first_fedge.first_svertex if vA.id.first == vB.id.first: - winner = ve - break - it.increment() + return ve + return None + ## case of NonTVertex else: - ## case of NonTVertex - natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK, - 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 + for i, nat in enumerate(NATURES): + if (nat & self.current_edge.nature): + for ve in it: + ve_nat = ve.nature + if (ve_nat & nat): + if (nat != ve_nat) and any(n & ve_nat for n in NATURES[:i]): + break + + if winner is not None: + return + + winner = ve + return winner + return None diff --git a/release/scripts/freestyle/modules/freestyle/functions.py b/release/scripts/freestyle/modules/freestyle/functions.py index 379e933862c..773d04ddeab 100644 --- a/release/scripts/freestyle/modules/freestyle/functions.py +++ b/release/scripts/freestyle/modules/freestyle/functions.py @@ -91,8 +91,8 @@ from freestyle.utils import integrate from mathutils import Vector -## Functions for 0D elements (vertices) -####################################### + +# -- Functions for 0D elements (vertices) -- # class CurveMaterialF0D(UnaryFunction0DMaterial): @@ -104,7 +104,7 @@ class CurveMaterialF0D(UnaryFunction0DMaterial): cp = inter.object assert(isinstance(cp, CurvePoint)) fe = cp.first_svertex.get_fedge(cp.second_svertex) - assert(fe is not None) + assert(fe is not None), "CurveMaterialF0D: fe is None" return fe.material if fe.is_smooth else fe.material_left @@ -140,11 +140,7 @@ class pyDensityAnisotropyF0D(UnaryFunction0DDouble): c_3 = self.d3Density(inter) cMax = max(max(c_0,c_1), max(c_2,c_3)) cMin = min(min(c_0,c_1), min(c_2,c_3)) - if c_iso == 0: - v = 0 - else: - v = (cMax-cMin)/c_iso - return v + return 0 if (c_iso == 0) else (cMax-cMin) / c_iso class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f): @@ -161,9 +157,9 @@ class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f): def __call__(self, iter): p = iter.object.point_2d gx = CF.read_complete_view_map_pixel(self._l, int(p.x+self._step), int(p.y)) - \ - CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) + CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y+self._step)) - \ - CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) + CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) return Vector((gx, gy)) @@ -171,19 +167,18 @@ class pyViewMapGradientNormF0D(UnaryFunction0DDouble): def __init__(self, l): UnaryFunction0DDouble.__init__(self) self._l = l - self._step = pow(2,self._l) + self._step = pow(2, self._l) def __call__(self, iter): p = iter.object.point_2d - gx = CF.read_complete_view_map_pixel(self._l, int(p.x+self._step), int(p.y)) - \ - CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) - gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y+self._step)) - \ - CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) - grad = Vector((gx, gy)) - return grad.length + gx = CF.read_complete_view_map_pixel(self._l, int(p.x + self._step), int(p.y)) - \ + CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) + gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y + self._step)) - \ + CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y)) + return Vector((gx, gy)).length + -## Functions for 1D elements (curves) -##################################### +# -- Functions for 1D elements (curves) -- # class pyGetInverseProjectedZF1D(UnaryFunction1DDouble): diff --git a/release/scripts/freestyle/modules/freestyle/predicates.py b/release/scripts/freestyle/modules/freestyle/predicates.py index ce6f0a35ffe..fede3e3e2da 100644 --- a/release/scripts/freestyle/modules/freestyle/predicates.py +++ b/release/scripts/freestyle/modules/freestyle/predicates.py @@ -51,6 +51,7 @@ from freestyle.types import ( TVertex, UnaryPredicate0D, UnaryPredicate1D, + Id, ) from freestyle.functions import ( Curvature2DAngleF0D, @@ -70,14 +71,15 @@ from freestyle.functions import ( pyDensityAnisotropyF1D, pyViewMapGradientNormF1D, ) + import random -## Unary predicates for 0D elements (vertices) -############################################## +# -- Unary predicates for 0D elements (vertices) -- # + class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D): - def __init__(self,a): + def __init__(self, a): UnaryPredicate0D.__init__(self) self._a = a @@ -88,15 +90,15 @@ class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D): class pyUEqualsUP0D(UnaryPredicate0D): - def __init__(self,u, w): + def __init__(self, u, w): UnaryPredicate0D.__init__(self) self._u = u self._w = w + self._func = pyCurvilinearLengthF0D() def __call__(self, inter): - func = pyCurvilinearLengthF0D() - u = func(inter) - return (u > (self._u-self._w)) and (u < (self._u+self._w)) + u = self._func(inter) + return (u > (self._u - self._w)) and (u < (self._u + self._w)) class pyVertexNatureUP0D(UnaryPredicate0D): @@ -105,26 +107,23 @@ class pyVertexNatureUP0D(UnaryPredicate0D): self._nature = nature def __call__(self, inter): - v = inter.object - return (v.nature & self._nature) != 0 + return bool(inter.object.nature & self._nature) -## check whether an Interface0DIterator -## is a TVertex and is the one that is -## hidden (inferred from the context) class pyBackTVertexUP0D(UnaryPredicate0D): + """ + Check whether an Interface0DIterator + references a TVertex and is the one that is + hidden (inferred from the context) + """ def __init__(self): UnaryPredicate0D.__init__(self) self._getQI = QuantitativeInvisibilityF0D() def __call__(self, iter): - if (iter.object.nature & Nature.T_VERTEX) == 0: + if not (iter.object.nature & Nature.T_VERTEX) or iter.is_end: return False - if iter.is_end: - return False - if self._getQI(iter) != 0: - return True - return False + return self._getQI(iter) != 0 class pyParameterUP0DGoodOne(UnaryPredicate0D): @@ -135,7 +134,7 @@ class pyParameterUP0DGoodOne(UnaryPredicate0D): def __call__(self, inter): u = inter.u - return ((u>=self._m) and (u<=self._M)) + return ((u >= self._m) and (u <= self._M)) class pyParameterUP0D(UnaryPredicate0D): @@ -143,36 +142,39 @@ class pyParameterUP0D(UnaryPredicate0D): UnaryPredicate0D.__init__(self) self._m = pmin self._M = pmax + self._func = Curvature2DAngleF0D() def __call__(self, inter): - func = Curvature2DAngleF0D() - c = func(inter) - b1 = (c>0.1) + c = self._func(inter) + b1 = (c > 0.1) u = inter.u - b = ((u>=self._m) and (u<=self._M)) - return b and b1 + b = ((u >= self._m) and (u <= self._M)) + return (b and b1) + + +# -- Unary predicates for 1D elements (curves) -- # -## Unary predicates for 1D elements (curves) -############################################ class AndUP1D(UnaryPredicate1D): - def __init__(self, pred1, pred2): + def __init__(self, *predicates): UnaryPredicate1D.__init__(self) - self.__pred1 = pred1 - self.__pred2 = pred2 + self.predicates = predicates + if len(self.predicates) < 2: + raise ValueError("Expected two or more UnaryPredicate1D") def __call__(self, inter): - return self.__pred1(inter) and self.__pred2(inter) + return all(pred(inter) for pred in self.predicates) class OrUP1D(UnaryPredicate1D): - def __init__(self, pred1, pred2): + def __init__(self, *predicates): UnaryPredicate1D.__init__(self) - self.__pred1 = pred1 - self.__pred2 = pred2 + self.predicates = predicates + if len(self.predicates) < 2: + raise ValueError("Expected two or more UnaryPredicate1D") def __call__(self, inter): - return self.__pred1(inter) or self.__pred2(inter) + return any(pred(inter) for pred in self.predicates) class NotUP1D(UnaryPredicate1D): @@ -184,6 +186,29 @@ class NotUP1D(UnaryPredicate1D): return not self.__pred(inter) +class ObjectNamesUP1D(UnaryPredicate1D): + def __init__(self, names, negative=False): + UnaryPredicate1D.__init__(self) + self._names = names + self._negative = negative + + def __call__(self, viewEdge): + found = viewEdge.viewshape.name in self._names + return found if not self._negative else not found + + +class QuantitativeInvisibilityRangeUP1D(UnaryPredicate1D): + def __init__(self, qi_start, qi_end): + UnaryPredicate1D.__init__(self) + self.__getQI = QuantitativeInvisibilityF1D() + self.__qi_start = qi_start + self.__qi_end = qi_end + + def __call__(self, inter): + qi = self.__getQI(inter) + return (self.__qi_start <= qi <= self.__qi_end) + + class pyNFirstUP1D(UnaryPredicate1D): def __init__(self, n): UnaryPredicate1D.__init__(self) @@ -191,14 +216,12 @@ class pyNFirstUP1D(UnaryPredicate1D): self.__count = 0 def __call__(self, inter): - self.__count = self.__count + 1 - if self.__count <= self.__n: - return True - return False + self.__count += 1 + return (self.__count <= self.__n) class pyHigherLengthUP1D(UnaryPredicate1D): - def __init__(self,l): + def __init__(self, l): UnaryPredicate1D.__init__(self) self._l = l @@ -213,28 +236,20 @@ class pyNatureUP1D(UnaryPredicate1D): self._getNature = CurveNatureF1D() def __call__(self, inter): - if(self._getNature(inter) & self._nature): - return True - return False + return bool(self._getNature(inter) & self._nature) class pyHigherNumberOfTurnsUP1D(UnaryPredicate1D): - def __init__(self,n,a): + def __init__(self, n, a): UnaryPredicate1D.__init__(self) self._n = n self._a = a def __call__(self, inter): - count = 0 func = Curvature2DAngleF0D() it = inter.vertices_begin() - while not it.is_end: - if func(it) > self._a: - count = count+1 - if count > self._n: - return True - it.increment() - return False + # sum the turns, check against n + return sum(1 for ve in it if func(it) > self._a) > self._n class pyDensityUP1D(UnaryPredicate1D): @@ -278,9 +293,7 @@ class pyHighSteerableViewMapDensityUP1D(UnaryPredicate1D): def __init__(self, threshold, level, integration=IntegrationType.MEAN): UnaryPredicate1D.__init__(self) self._threshold = threshold - self._level = level - self._integration = integration - self._func = GetSteerableViewMapDensityF1D(self._level, self._integration) + self._func = GetSteerableViewMapDensityF1D(level, integration) def __call__(self, inter): return (self._func(inter) > self._threshold) @@ -290,24 +303,17 @@ class pyHighDirectionalViewMapDensityUP1D(UnaryPredicate1D): def __init__(self, threshold, orientation, level, integration=IntegrationType.MEAN, sampling=2.0): UnaryPredicate1D.__init__(self) self._threshold = threshold - self._orientation = orientation - self._level = level - self._integration = integration - self._sampling = sampling + self._func = GetDirectionalViewMapDensityF1D(orientation, level, integration, sampling) def __call__(self, inter): - func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration, self._sampling) - return (func(inter) > self._threshold) + return (self.func(inter) > self._threshold) class pyHighViewMapDensityUP1D(UnaryPredicate1D): def __init__(self, threshold, level, integration=IntegrationType.MEAN, sampling=2.0): UnaryPredicate1D.__init__(self) self._threshold = threshold - self._level = level - self._integration = integration - self._sampling = sampling - self._func = GetCompleteViewMapDensityF1D(self._level, self._integration, self._sampling) # 2.0 is the smpling + self._func = GetCompleteViewMapDensityF1D(level, integration, sampling) def __call__(self, inter): return (self._func(inter) > self._threshold) @@ -316,67 +322,56 @@ class pyHighViewMapDensityUP1D(UnaryPredicate1D): class pyDensityFunctorUP1D(UnaryPredicate1D): def __init__(self, wsize, threshold, functor, funcmin=0.0, funcmax=1.0, integration=IntegrationType.MEAN): UnaryPredicate1D.__init__(self) - self._wsize = wsize self._threshold = float(threshold) self._functor = functor self._funcmin = float(funcmin) self._funcmax = float(funcmax) - self._integration = integration + self._func = DensityF1D(wsize, integration) def __call__(self, inter): - func = DensityF1D(self._wsize, self._integration) res = self._functor(inter) - k = (res-self._funcmin)/(self._funcmax-self._funcmin) + k = (res - self._funcmin) / (self._funcmax - self._funcmin) return (func(inter) < (self._threshold * k)) class pyZSmallerUP1D(UnaryPredicate1D): - def __init__(self,z, integration=IntegrationType.MEAN): + def __init__(self, z, integration=IntegrationType.MEAN): UnaryPredicate1D.__init__(self) self._z = z - self._integration = integration + self.func = GetProjectedZF1D(integration) def __call__(self, inter): - func = GetProjectedZF1D(self._integration) - return (func(inter) < self._z) + return (self.func(inter) < self._z) class pyIsOccludedByUP1D(UnaryPredicate1D): def __init__(self,id): UnaryPredicate1D.__init__(self) + if not isinstance(id, Id): + raise TypeError("pyIsOccludedByUP1D expected freestyle.types.Id, not " + type(id).__name__) self._id = id def __call__(self, inter): - func = GetShapeF1D() - shapes = func(inter) - for s in shapes: - if(s.id == self._id): - return False + shapes = GetShapeF1D()(inter) + if any(s.id == self._id for s in shapes): + return False + + # construct iterators it = inter.vertices_begin() itlast = inter.vertices_end() itlast.decrement() - v = it.object - vlast = itlast.object - tvertex = v.viewvertex - if type(tvertex) is TVertex: - #print("TVertex: [ ", tvertex.id.first, ",", tvertex.id.second," ]") - eit = tvertex.edges_begin() - while not eit.is_end: - ve, incoming = eit.object - if ve.id == self._id: - return True - #print("-------", ve.id.first, "-", ve.id.second) - eit.increment() - tvertex = vlast.viewvertex - if type(tvertex) is TVertex: - #print("TVertex: [ ", tvertex.id.first, ",", tvertex.id.second," ]") + + vertex = next(it) + if type(vertex) is TVertex: + eit = vertex.edges_begin() + if any(ve.id == self._id for (ve, incoming) in eit): + return True + + vertex = next(itlast) + if type(vertex) is TVertex: eit = tvertex.edges_begin() - while not eit.is_end: - ve, incoming = eit.object - if ve.id == self._id: - return True - #print("-------", ve.id.first, "-", ve.id.second) - eit.increment() + if any(ve.id == self._id for (ve, incoming) in eit): + return True return False @@ -386,12 +381,8 @@ class pyIsInOccludersListUP1D(UnaryPredicate1D): self._id = id def __call__(self, inter): - func = GetOccludersF1D() - occluders = func(inter) - for a in occluders: - if a.id == self._id: - return True - return False + occluders = GetOccludersF1D()(inter) + return any(a.id == self._id for a in occluders) class pyIsOccludedByItselfUP1D(UnaryPredicate1D): @@ -403,11 +394,7 @@ class pyIsOccludedByItselfUP1D(UnaryPredicate1D): def __call__(self, inter): lst1 = self.__func1(inter) lst2 = self.__func2(inter) - for vs1 in lst1: - for vs2 in lst2: - if vs1.id == vs2.id: - return True - return False + return any(vs1.id == vs2.id for vs1 in lst1 for vs2 in lst2) class pyIsOccludedByIdListUP1D(UnaryPredicate1D): @@ -417,27 +404,17 @@ class pyIsOccludedByIdListUP1D(UnaryPredicate1D): self.__func1 = GetOccludersF1D() def __call__(self, inter): - lst1 = self.__func1(inter) - for vs1 in lst1: - for _id in self._idlist: - if vs1.id == _id: - return True - return False + lst1 = self.__func1(inter.object) + return any(vs1.id == _id for vs1 in lst1 for _id in self._idlist) class pyShapeIdListUP1D(UnaryPredicate1D): def __init__(self,idlist): UnaryPredicate1D.__init__(self) - self._idlist = idlist - self._funcs = [] - for _id in idlist: - self._funcs.append(ShapeUP1D(_id.first, _id.second)) + self._funcs = tuple(ShapeUP1D(_id, 0) for _id in idlist) def __call__(self, inter): - for func in self._funcs: - if func(inter) == 1: - return True - return False + return any(func(inter) for func in self._funcs) ## deprecated @@ -447,12 +424,8 @@ class pyShapeIdUP1D(UnaryPredicate1D): self._id = _id def __call__(self, inter): - func = GetShapeF1D() - shapes = func(inter) - for a in shapes: - if a.id == self._id: - return True - return False + shapes = GetShapeF1D()(inter) + return any(a.id == self._id for a in shapes) class pyHighDensityAnisotropyUP1D(UnaryPredicate1D): @@ -473,7 +446,6 @@ class pyHighViewMapGradientNormUP1D(UnaryPredicate1D): def __call__(self, inter): gn = self._GetGradient(inter) - #print(gn) return (gn > self._threshold) @@ -503,53 +475,50 @@ class pyClosedCurveUP1D(UnaryPredicate1D): it = inter.vertices_begin() itlast = inter.vertices_end() itlast.decrement() - vlast = itlast.object - v = it.object - #print(v.id.first, v.id.second) - #print(vlast.id.first, vlast.id.second) - if v.id == vlast.id: - return True - return False + return (next(it).id == next(itlast).id) + + +# -- Binary predicates for 1D elements (curves) -- # -## Binary predicates for 1D elements (curves) -############################################# class AndBP1D(BinaryPredicate1D): - def __init__(self, pred1, pred2): + def __init__(self, *predicates): BinaryPredicate1D.__init__(self) - self.__pred1 = pred1 - self.__pred2 = pred2 + self._predicates = predicates + if len(self.predicates) < 2: + raise ValueError("Expected two or more BinaryPredicate1D") - def __call__(self, inter1, inter2): - return self.__pred1(inter1, inter2) and self.__pred2(inter1, inter2) + def __call__(self, i1, i2): + return all(pred(i1, i2) for pred in self._predicates) class OrBP1D(BinaryPredicate1D): - def __init__(self, pred1, pred2): + def __init__(self, *predicates): BinaryPredicate1D.__init__(self) - self.__pred1 = pred1 - self.__pred2 = pred2 + self._predicates = predicates + if len(self.predicates) < 2: + raise ValueError("Expected two or more BinaryPredicate1D") - def __call__(self, inter1, inter2): - return self.__pred1(inter1, inter2) or self.__pred2(inter1, inter2) + def __call__(self, i1, i2): + return any(pred(i1, i2) for pred in self._predicates) class NotBP1D(BinaryPredicate1D): - def __init__(self, pred): + def __init__(self, predicate): BinaryPredicate1D.__init__(self) - self.__pred = pred + self._predicate = predicate - def __call__(self, inter1, inter2): - return not self.__pred(inter1, inter2) + def __call__(self, i1, i2): + return (not self._precicate(i1, i2)) class pyZBP1D(BinaryPredicate1D): def __init__(self, iType=IntegrationType.MEAN): BinaryPredicate1D.__init__(self) - self._GetZ = GetZF1D(iType) + self.func = GetZF1D(iType) def __call__(self, i1, i2): - return (self._GetZ(i1) > self._GetZ(i2)) + return (self.func(i1) > self.func(i2)) class pyZDiscontinuityBP1D(BinaryPredicate1D): @@ -569,10 +538,10 @@ class pyLengthBP1D(BinaryPredicate1D): class pySilhouetteFirstBP1D(BinaryPredicate1D): def __call__(self, inter1, inter2): bpred = SameShapeIdBP1D() - if (bpred(inter1, inter2) != 1): + if (not bpred(inter1, inter2)): return False if (inter1.nature & Nature.SILHOUETTE): - return (inter2.nature & Nature.SILHOUETTE) != 0 + return bool(inter2.nature & Nature.SILHOUETTE) return (inter1.nature == inter2.nature) @@ -587,16 +556,13 @@ class pyViewMapGradientNormBP1D(BinaryPredicate1D): self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN) def __call__(self, i1,i2): - #print("compare gradient") return (self._GetGradient(i1) > self._GetGradient(i2)) class pyShuffleBP1D(BinaryPredicate1D): def __init__(self): BinaryPredicate1D.__init__(self) - random.seed(1) + random.seed = 1 def __call__(self, inter1, inter2): - r1 = random.uniform(0,1) - r2 = random.uniform(0,1) - return (r1<r2) + return (random.uniform(0,1) < random.uniform(0,1)) diff --git a/release/scripts/freestyle/modules/freestyle/shaders.py b/release/scripts/freestyle/modules/freestyle/shaders.py index 8e9d77915ef..8a9faf26017 100644 --- a/release/scripts/freestyle/modules/freestyle/shaders.py +++ b/release/scripts/freestyle/modules/freestyle/shaders.py @@ -62,6 +62,7 @@ from freestyle.types import ( StrokeAttribute, StrokeShader, StrokeVertexIterator, + StrokeVertex, ) from freestyle.functions import ( Curvature2DAngleF0D, @@ -74,16 +75,27 @@ from freestyle.functions import ( ) from freestyle.predicates import ( pyVertexNatureUP0D, + pyUEqualsUP0D, ) + +from freestyle.utils import ( + bound, + bounding_box, + phase_to_direction, + ) + from freestyle.utils import ContextFunctions as CF -from math import atan, cos, pi, pow, sin, sinh, sqrt +import bpy +import random + +from math import atan, cos, pi, sin, sinh, sqrt from mathutils import Vector from random import randint -## thickness modifiers -###################### +# -- Thickness Stroke Shaders -- # + class pyDepthDiscontinuityThicknessShader(StrokeShader): """ @@ -92,21 +104,16 @@ class pyDepthDiscontinuityThicknessShader(StrokeShader): """ def __init__(self, min, max): StrokeShader.__init__(self) - self.__min = float(min) - self.__max = float(max) - self.__func = ZDiscontinuityF0D() + self.a = max - min + self.b = min + self.func = ZDiscontinuityF0D() def shade(self, stroke): - z_min=0.0 - z_max=1.0 - a = (self.__max - self.__min)/(z_max-z_min) - b = (self.__min*z_max-self.__max*z_min)/(z_max-z_min) - it = stroke.stroke_vertices_begin() - while not it.is_end: - z = self.__func(Interface0DIterator(it)) - thickness = a*z+b - it.object.attribute.thickness = (thickness, thickness) - it.increment() + it = Interface0DIterator(stroke) + for svert in it: + z = self.func(it) + thickness = self.a * z + self.b + svert.attribute.thickness = (thickness, thickness) class pyConstantThicknessShader(StrokeShader): @@ -115,14 +122,11 @@ class pyConstantThicknessShader(StrokeShader): """ def __init__(self, thickness): StrokeShader.__init__(self) - self._thickness = thickness + self._thickness = thickness / 2.0 def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - t = self._thickness/2.0 - it.object.attribute.thickness = (t, t) - it.increment() + for svert in stroke: + svert.attribute.thickness = (self._thickness, self._thickness) class pyFXSVaryingThicknessWithDensityShader(StrokeShader): @@ -131,28 +135,22 @@ class pyFXSVaryingThicknessWithDensityShader(StrokeShader): """ def __init__(self, wsize, threshold_min, threshold_max, thicknessMin, thicknessMax): StrokeShader.__init__(self) - self.wsize= wsize - self.threshold_min= threshold_min - self.threshold_max= threshold_max + self._func = DensityF0D(wsize) + self.threshold_min = threshold_min + self.threshold_max = threshold_max self._thicknessMin = thicknessMin self._thicknessMax = thicknessMax def shade(self, stroke): - n = stroke.stroke_vertices_size() - i = 0 - it = stroke.stroke_vertices_begin() - func = DensityF0D(self.wsize) - while not it.is_end: - c = func(Interface0DIterator(it)) - if c < self.threshold_min: - c = self.threshold_min - if c > self.threshold_max: - c = self.threshold_max -## t = (c - self.threshold_min)/(self.threshold_max - self.threshold_min)*(self._thicknessMax-self._thicknessMin) + self._thicknessMin - t = (self.threshold_max - c )/(self.threshold_max - self.threshold_min)*(self._thicknessMax-self._thicknessMin) + self._thicknessMin - it.object.attribute.thickness = (t/2.0, t/2.0) - i = i+1 - it.increment() + it = Interface0DIterator(stroke) + delta_threshold = self.threshold_max - self.threshold_min + delta_thickness = self._thicknessMax - self._thicknessMin + + for svert in it: + c = self._func(it) + c = bound(self.threshold_min, c, self.threshold_max) + t = (self.threshold_max - c) / delta_threshold * delta_thickness + self._thicknessMin + svert.attribute.thickness = (t / 2.0, t / 2.0) class pyIncreasingThicknessShader(StrokeShader): @@ -165,18 +163,14 @@ class pyIncreasingThicknessShader(StrokeShader): self._thicknessMax = thicknessMax def shade(self, stroke): - n = stroke.stroke_vertices_size() - i = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - c = float(i)/float(n) - if i < float(n)/2.0: - t = (1.0 - c)*self._thicknessMin + c * self._thicknessMax + n = len(stroke) + for i, svert in enumerate(stroke): + c = i / n + if i < (n * 0.5): + t = (1.0 - c) * self._thicknessMin + c * self._thicknessMax else: - t = (1.0 - c)*self._thicknessMax + c * self._thicknessMin - it.object.attribute.thickness = (t/2.0, t/2.0) - i = i+1 - it.increment() + t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin + svert.attribute.thickness = (t / 2.0, t / 2.0) class pyConstrainedIncreasingThicknessShader(StrokeShader): @@ -191,28 +185,20 @@ class pyConstrainedIncreasingThicknessShader(StrokeShader): self._ratio = ratio def shade(self, stroke): - slength = stroke.length_2d - tmp = self._ratio*slength - maxT = 0.0 - if tmp < self._thicknessMax: - maxT = tmp - else: - maxT = self._thicknessMax - n = stroke.stroke_vertices_size() - i = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - att = it.object.attribute - c = float(i)/float(n) - if i < float(n)/2.0: - t = (1.0 - c)*self._thicknessMin + c * maxT + n = len(stroke) + maxT = min(self._ratio * stroke.length_2d, self._thicknessMax) + + for i, svert in enumerate(stroke): + c = i / n + if i < (n * 0.5): + t = (1.0 - c) * self._thicknessMin + c * maxT else: - t = (1.0 - c)*maxT + c * self._thicknessMin - att.thickness = (t/2.0, t/2.0) - if i == n-1: - att.thickness = (self._thicknessMin/2.0, self._thicknessMin/2.0) - i = i+1 - it.increment() + t = (1.0 - c) * maxT + c * self._thicknessMin + + if i == (n - 1): + svert.attribute.thickness = (self._thicknessMin / 2.0, self._thicknessMin / 2.0) + else: + svert.attribute.thickness = (t / 2.0, t / 2.0) class pyDecreasingThicknessShader(StrokeShader): @@ -226,21 +212,14 @@ class pyDecreasingThicknessShader(StrokeShader): def shade(self, stroke): l = stroke.length_2d - tMax = self._thicknessMax - if self._thicknessMax > 0.33*l: - tMax = 0.33*l - tMin = self._thicknessMin - if self._thicknessMin > 0.1*l: - tMin = 0.1*l - n = stroke.stroke_vertices_size() - i = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - c = float(i)/float(n) - t = (1.0 - c)*tMax +c*tMin - it.object.attribute.thickness = (t/2.0, t/2.0) - i = i+1 - it.increment() + n = len(stroke) + tMax = min(self._thicknessMax, 0.33 * l) + tMin = min(self._thicknessMin, 0.10 * l) + + for i, svert in enumerate(stroke): + c = i / n + t = (1.0 - c) * tMax + c * tMin + svert.attribute.thickness = (t / 2.0, t / 2.0) class pyNonLinearVaryingThicknessShader(StrokeShader): @@ -248,28 +227,18 @@ class pyNonLinearVaryingThicknessShader(StrokeShader): Assigns thickness to a stroke based on an exponential function """ def __init__(self, thicknessExtremity, thicknessMiddle, exponent): - StrokeShader.__init__(self) self._thicknessMin = thicknessMiddle self._thicknessMax = thicknessExtremity - self._exponent = exponent + self._exp = exponent + StrokeShader.__init__(self) def shade(self, stroke): - n = stroke.stroke_vertices_size() - i = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - if i < float(n)/2.0: - c = float(i)/float(n) - else: - c = float(n-i)/float(n) - c = self.smoothC(c, self._exponent) - t = (1.0 - c)*self._thicknessMax + c * self._thicknessMin - it.object.attribute.thickness = (t/2.0, t/2.0) - i = i+1 - it.increment() - - def smoothC(self, a, exp): - return pow(float(a), exp) * pow(2.0, exp) + n = len(stroke) + for i, svert in enumerate(stroke): + c = (i / n) if (i < n / 2.0) else ((n - i) / n) + c = pow(c, self._exp) * pow(2.0, self._exp) + t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin + svert.attribute.thickness = (t / 2.0, t / 2.0) class pySLERPThicknessShader(StrokeShader): @@ -280,29 +249,23 @@ class pySLERPThicknessShader(StrokeShader): StrokeShader.__init__(self) self._thicknessMin = thicknessMin self._thicknessMax = thicknessMax - self._omega = omega + self.omega = omega def shade(self, stroke): - slength = stroke.length_2d - tmp = 0.33*slength - maxT = self._thicknessMax - if tmp < self._thicknessMax: - maxT = tmp - n = stroke.stroke_vertices_size() - i = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - c = float(i)/float(n) - if i < float(n)/2.0: - t = sin((1-c)*self._omega)/sinh(self._omega)*self._thicknessMin + sin(c*self._omega)/sinh(self._omega) * maxT + n = len(stroke) + maxT = min(self._thicknessMax, 0.33 * stroke.length_2d) + omega = self.omega + sinhyp = sinh(omega) + for i, svert in enumerate(stroke): + c = i / n + if i < (n * 0.5): + t = sin((1-c) * omega) / sinhyp * self._thicknessMin + sin(c * omega) / sinhyp * maxT else: - t = sin((1-c)*self._omega)/sinh(self._omega)*maxT + sin(c*self._omega)/sinh(self._omega) * self._thicknessMin - it.object.attribute.thickness = (t/2.0, t/2.0) - i = i+1 - it.increment() + t = sin((1-c) * omega) / sinhyp * maxT + sin(c * omega) / sinhyp * self._thicknessMin + svert.attribute.thickness = (t / 2.0, t / 2.0) -class pyTVertexThickenerShader(StrokeShader): ## FIXME +class pyTVertexThickenerShader(StrokeShader): """ Thickens TVertices (visual intersections between two edges) """ @@ -312,46 +275,22 @@ class pyTVertexThickenerShader(StrokeShader): ## FIXME self._n = n def shade(self, stroke): - it = stroke.stroke_vertices_begin() - predTVertex = pyVertexNatureUP0D(Nature.T_VERTEX) - while not it.is_end: - if predTVertex(it) == 1: - it2 = StrokeVertexIterator(it) - it2.increment() - if not (it.is_begin or it2.is_end): - it.increment() - continue - n = self._n - a = self._a - if it.is_begin: - it3 = StrokeVertexIterator(it) - count = 0 - while (not it3.is_end) and count < n: - att = it3.object.attribute - (tr, tl) = att.thickness - r = (a-1.0)/float(n-1)*(float(n)/float(count+1) - 1) + 1 - #r = (1.0-a)/float(n-1)*count + a - att.thickness = (r*tr, r*tl) - it3.increment() - count = count + 1 - if it2.is_end: - it4 = StrokeVertexIterator(it) - count = 0 - while (not it4.is_begin) and count < n: - att = it4.object.attribute - (tr, tl) = att.thickness - r = (a-1.0)/float(n-1)*(float(n)/float(count+1) - 1) + 1 - #r = (1.0-a)/float(n-1)*count + a - att.thickness = (r*tr, r*tl) - it4.decrement() - count = count + 1 - if it4.is_begin: - att = it4.object.attribute - (tr, tl) = att.thickness - r = (a-1.0)/float(n-1)*(float(n)/float(count+1) - 1) + 1 - #r = (1.0-a)/float(n-1)*count + a - att.thickness = (r*tr, r*tl) - it.increment() + n = self._n + a = self._a + + term = (a - 1.0) / (n - 1.0) + + if (stroke[0].nature & Nature.T_VERTEX): + for count, svert in zip(range(n), stroke): + r = term * (n / (count + 1.0) - 1.0) + 1.0 + (tr, tl) = svert.attribute.thickness + svert.attribute.thickness = (r * tr, r * tl) + + if (stroke[-1].nature & Nature.T_VERTEX): + for count, svert in zip(range(n), reversed(stroke)): + r = term * (n / (count + 1.0) - 1.0) + 1.0 + (tr, tl) = svert.attribute.thickness + svert.attribute.thickness = (r * tr, r * tl) class pyImportance2DThicknessShader(StrokeShader): @@ -362,26 +301,18 @@ class pyImportance2DThicknessShader(StrokeShader): """ def __init__(self, x, y, w, kmin, kmax): StrokeShader.__init__(self) - self._x = x - self._y = y - self._w = float(w) - self._kmin = float(kmin) - self._kmax = float(kmax) + self._origin = Vector((x, y)) + self._w = w + self._kmin, self._kmax = kmin, kmax def shade(self, stroke): - origin = Vector((self._x, self._y)) - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - d = (v.point_2d - self._origin).length - if d > self._w: - k = self._kmin - else: - k = (self._kmax*(self._w-d) + self._kmin*d)/self._w - att = v.attribute - (tr, tl) = att.thickness - att.thickness = (k*tr/2.0, k*tl/2.0) - it.increment() + for svert in stroke: + d = (svert.point_2d - self._origin).length + k = (self._kmin if (d > self._w) else + (self._kmax * (self._w-d) + self._kmin * d) / self._w) + + (tr, tl) = svert.attribute.thickness + svert.attribute.thickness = (k*tr/2.0, k*tl/2.0) class pyImportance3DThicknessShader(StrokeShader): @@ -390,28 +321,18 @@ class pyImportance3DThicknessShader(StrokeShader): """ def __init__(self, x, y, z, w, kmin, kmax): StrokeShader.__init__(self) - self._x = x - self._y = y - self._z = z - self._w = float(w) - self._kmin = float(kmin) - self._kmax = float(kmax) + self._origin = Vector((x, y, z)) + self._w = w + self._kmin, self._kmax = kmin, kmax def shade(self, stroke): - origin = Vector((self._x, self._y, self._z)) - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - p = v.point_3d - d = (p-origin).length - if d > self._w: - k = self._kmin - else: - k = (self._kmax*(self._w-d) + self._kmin*d)/self._w - att = v.attribute - (tr, tl) = att.thickness - att.thickness = (k*tr/2.0, k*tl/2.0) - it.increment() + for svert in stroke: + d = (svert.point_3d - self._origin).length + k = (self._kmin if (d > self._w) else + (self._kmax * (self._w-d) + self._kmin * d) / self._w) + + (tr, tl) = svert.attribute.thickness + svert.attribute.thickness = (k*tr/2.0, k*tl/2.0) class pyZDependingThicknessShader(StrokeShader): @@ -423,49 +344,35 @@ class pyZDependingThicknessShader(StrokeShader): StrokeShader.__init__(self) self.__min = min self.__max = max - self.__func = GetProjectedZF0D() + self.func = GetProjectedZF0D() def shade(self, stroke): - it = stroke.stroke_vertices_begin() - z_min = 1 - z_max = 0 - while not it.is_end: - z = self.__func(Interface0DIterator(it)) - if z < z_min: - z_min = z - if z > z_max: - z_max = z - it.increment() + it = Interface0DIterator(stroke) + z_indices = tuple(self.func(it) for _ in it) + z_min, z_max = min(1, *z_indices), max(0, *z_indices) z_diff = 1 / (z_max - z_min) - it = stroke.stroke_vertices_begin() - while not it.is_end: - z = (self.__func(Interface0DIterator(it)) - z_min) * z_diff + + for svert, z_index in zip(stroke, z_indices): + z = (z_index - z_min) * z_diff thickness = (1 - z) * self.__max + z * self.__min - it.object.attribute.thickness = (thickness, thickness) - it.increment() + svert.attribute.thickness = (thickness, thickness) + +# -- Color & Alpha Stroke Shaders -- # -## color modifiers -################## class pyConstantColorShader(StrokeShader): """ Assigns a constant color to the stroke """ - def __init__(self, r, g, b, a=1): + def __init__(self,r,g,b, a = 1): StrokeShader.__init__(self) - self._r = r - self._g = g - self._b = b + self._color = (r, g, b) self._a = a - def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - att = it.object.attribute - att.color = (self._r, self._g, self._b) - att.alpha = self._a - it.increment() + for svert in stroke: + svert.attribute.color = self._color + svert.attribute.alpha = self._a class pyIncreasingColorShader(StrokeShader): @@ -474,23 +381,18 @@ class pyIncreasingColorShader(StrokeShader): """ def __init__(self,r1,g1,b1,a1, r2,g2,b2,a2): StrokeShader.__init__(self) - self._c1 = [r1,g1,b1,a1] - self._c2 = [r2,g2,b2,a2] + # use 4d vector to simplify math + self._c1 = Vector((r1, g1 ,b1, a1)) + self._c2 = Vector((r2, g2, b2, a2)) def shade(self, stroke): - n = stroke.stroke_vertices_size() - 1 - inc = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - att = it.object.attribute - c = float(inc) / float(n) - - att.color = ((1.0 - c) * self._c1[0] + c * self._c2[0], - (1.0 - c) * self._c1[1] + c * self._c2[1], - (1.0 - c) * self._c1[2] + c * self._c2[2]) - att.alpha = (1.0 - c) * self._c1[3] + c * self._c2[3] - inc = inc + 1 - it.increment() + n = len(stroke) - 1 + + for i, svert in enumerate(stroke): + c = i / n + color = (1 - c) * self._c1 + c * self._c2 + svert.attribute.color = color[:3] + svert.attribute.alpha = color[3] class pyInterpolateColorShader(StrokeShader): @@ -499,23 +401,32 @@ class pyInterpolateColorShader(StrokeShader): """ def __init__(self,r1,g1,b1,a1, r2,g2,b2,a2): StrokeShader.__init__(self) - self._c1 = [r1,g1,b1,a1] - self._c2 = [r2,g2,b2,a2] + # use 4d vector to simplify math + self._c1 = Vector((r1, g1 ,b1, a1)) + self._c2 = Vector((r2, g2, b2, a2)) def shade(self, stroke): - n = stroke.stroke_vertices_size() - 1 - inc = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - att = it.object.attribute - u = float(inc) / float(n) - c = 1.0 - 2.0 * abs(u - 0.5) - att.color = ((1.0 - c) * self._c1[0] + c * self._c2[0], - (1.0 - c) * self._c1[1] + c * self._c2[1], - (1.0 - c) * self._c1[2] + c * self._c2[2]) - att.alpha = (1.0-c) * self._c1[3] + c * self._c2[3] - inc = inc+1 - it.increment() + n = len(stroke) - 1 + for i, svert in enumerate(stroke): + c = 1.0 - 2.0 * abs((i / n) - 0.5) + color = (1.0 - c) * self._c1 + c * self._c2 + svert.attribute.color = color[:3] + svert.attribute.alpha = color[3] + + +class pyModulateAlphaShader(StrokeShader): + """ + Limits the stroke's alpha between a min and max value. + """ + def __init__(self, min=0, max=1): + StrokeShader.__init__(self) + self.__min = min + self.__max = max + def shade(self, stroke): + for svert in stroke: + alpha = svert.attribute.alpha + alpha = bound(self.__min, alpha * svert.point.y * 0.0025, self.__max) + svert.attribute.alpha = alpha class pyMaterialColorShader(StrokeShader): @@ -525,61 +436,59 @@ class pyMaterialColorShader(StrokeShader): def __init__(self, threshold=50): StrokeShader.__init__(self) self._threshold = threshold + self._func = MaterialF0D() def shade(self, stroke): - it = stroke.stroke_vertices_begin() - func = MaterialF0D() xn = 0.312713 yn = 0.329016 Yn = 1.0 - un = 4.* xn / (-2.*xn + 12.*yn + 3.) - vn= 9.* yn / (-2.*xn + 12.*yn +3.) - while not it.is_end: - mat = func(Interface0DIterator(it)) - - r = mat.diffuse[0] - g = mat.diffuse[1] - b = mat.diffuse[2] - - X = 0.412453*r + 0.35758 *g + 0.180423*b - Y = 0.212671*r + 0.71516 *g + 0.072169*b - Z = 0.019334*r + 0.119193*g + 0.950227*b - - if (X, Y, Z) == (0, 0, 0): - X = 0.01 - Y = 0.01 - Z = 0.01 - u = 4.*X / (X + 15.*Y + 3.*Z) - v = 9.*Y / (X + 15.*Y + 3.*Z) - - L= 116. * pow((Y/Yn),(1./3.)) -16 + un = 4.0 * xn / (-2.0 * xn + 12.0 * yn + 3.0) + vn = 9.0 * yn / (-2.0 * xn + 12.0 * yn + 3.0) + + it = Interface0DIterator(stroke) + for svert in it: + mat = self._func(it) + + r, g, b, *_ = mat.diffuse + + X = 0.412453 * r + 0.35758 * g + 0.180423 * b + Y = 0.212671 * r + 0.71516 * g + 0.072169 * b + Z = 0.019334 * r + 0.11919 * g + 0.950227 * b + + if not any((X, Y, Z)): + X = Y = Z = 0.01 + + u = 4.0 * X / (X + 15.0 * Y + 3.0 * Z) + v = 9.0 * Y / (X + 15.0 * Y + 3.0 * Z) + + L= 116. * pow((Y/Yn),(1./3.)) - 16 U = 13. * L * (u - un) V = 13. * L * (v - vn) if L > self._threshold: - L = L/1.3 - U = U+10 + L /= 1.3 + U += 10. else: - L = L +2.5*(100-L)/5. - U = U/3.0 - V = V/3.0 - u = U / (13. * L) + un - v = V / (13. * L) + vn + L = L + 2.5 * (100-L) * 0.2 + U /= 3.0 + V /= 3.0 + + u = U / (13.0 * L) + un + v = V / (13.0 * L) + vn Y = Yn * pow(((L+16.)/116.), 3.) - X = -9.0 * Y * u / ((u - 4.0) * v - u * v) - Z = (9.0 * Y - 15.0 * v * Y - v * X) / (3.0 * v) + X = -9. * Y * u / ((u - 4.)* v - u * v) + Z = (9. * Y - 15*v*Y - v*X) /( 3. * v) r = 3.240479 * X - 1.53715 * Y - 0.498535 * Z g = -0.969256 * X + 1.875991 * Y + 0.041556 * Z b = 0.055648 * X - 0.204043 * Y + 1.057311 * Z - r = max(0,r) - g = max(0,g) - b = max(0,b) + r = max(0, r) + g = max(0, g) + b = max(0, b) - it.object.attribute.color = (r, g, b) - it.increment() + svert.attribute.color = (r, g, b) class pyRandomColorShader(StrokeShader): @@ -588,18 +497,14 @@ class pyRandomColorShader(StrokeShader): """ def __init__(self, s=1): StrokeShader.__init__(self) - random.seed(s) + random.seed = s def shade(self, stroke): - ## pick a random color - c0 = float(random.uniform(15,75))/100.0 - c1 = float(random.uniform(15,75))/100.0 - c2 = float(random.uniform(15,75))/100.0 - #print(c0, c1, c2) - it = stroke.stroke_vertices_begin() - while not it.is_end: - it.object.attribute.color = (c0,c1,c2) - it.increment() + c = (random.uniform(15, 75) * 0.01, + random.uniform(15, 75) * 0.01, + random.uniform(15, 75) * 0.01) + for svert in stroke: + svert.attribute.color = c class py2DCurvatureColorShader(StrokeShader): @@ -608,15 +513,14 @@ class py2DCurvatureColorShader(StrokeShader): A higher curvature will yield a brighter color """ def shade(self, stroke): - it = stroke.stroke_vertices_begin() func = Curvature2DAngleF0D() - while not it.is_end: - c = func(Interface0DIterator(it)) - if c < 0: - print("negative 2D curvature") - color = 10.0 * c/3.1415 - it.object.attribute.color = (color, color, color) - it.increment() + it = Interface0DIterator(stroke) + for svert in it: + c = func(it) + if c < 0 and bpy.app.debug_freestyle: + print("py2DCurvatureColorShader: negative 2D curvature") + color = 10.0 * c / pi + svert.attribute.color = (color, color, color) class pyTimeColorShader(StrokeShader): @@ -627,13 +531,13 @@ class pyTimeColorShader(StrokeShader): def __init__(self, step=0.01): StrokeShader.__init__(self) self._step = step - def shade(self, stroke): - for i, svert in enumerate(iter(stroke)): + for i, svert in enumerate(stroke): c = i * self._step - svert.attribute.color = (c,c,c) + svert.attribute.color = (c, c, c) -## geometry modifiers + +# -- Geometry Stroke Shaders -- # class pySamplingShader(StrokeShader): @@ -659,89 +563,55 @@ class pyBackboneStretcherShader(StrokeShader): self._l = l def shade(self, stroke): - it0 = stroke.stroke_vertices_begin() - it1 = StrokeVertexIterator(it0) - it1.increment() - itn = stroke.stroke_vertices_end() - itn.decrement() - itn_1 = StrokeVertexIterator(itn) - itn_1.decrement() - v0 = it0.object - v1 = it1.object - vn_1 = itn_1.object - vn = itn.object - p0 = v0.point_2d - pn = vn.point_2d - p1 = v1.point_2d - pn_1 = vn_1.point_2d - d1 = (p0 - p1).normalized() - dn = (pn - pn_1).normalized() - newFirst = p0+d1*float(self._l) - newLast = pn+dn*float(self._l) - v0.point = newFirst - vn.point = newLast + # get start and end points + v0, vn = stroke[0], stroke[-1] + p0, pn = v0.point, vn.point + # get the direction + d1 = (p0 - stroke[ 1].point).normalized() + dn = (pn - stroke[-2].point).normalized() + v0.point += d1 * self._l + vn.point += dn * self._l stroke.update_length() class pyLengthDependingBackboneStretcherShader(StrokeShader): """ Stretches the stroke's backbone proportional to the stroke's length + NOTE: you'll probably want an l somewhere between (0.5 - 0). A value that + is too high may yield unexpected results. """ def __init__(self, l): StrokeShader.__init__(self) self._l = l - def shade(self, stroke): - l = stroke.length_2d - stretch = self._l*l - it0 = stroke.stroke_vertices_begin() - it1 = StrokeVertexIterator(it0) - it1.increment() - itn = stroke.stroke_vertices_end() - itn.decrement() - itn_1 = StrokeVertexIterator(itn) - itn_1.decrement() - v0 = it0.object - v1 = it1.object - vn_1 = itn_1.object - vn = itn.object - p0 = v0.point_2d - pn = vn.point_2d - p1 = v1.point_2d - pn_1 = vn_1.point_2d - d1 = (p0 - p1).normalized() - dn = (pn - pn_1).normalized() - newFirst = p0+d1*float(stretch) - newLast = pn+dn*float(stretch) - v0.point = newFirst - vn.point = newLast + # get start and end points + v0, vn = stroke[0], stroke[-1] + p0, pn = v0.point, vn.point + # get the direction + d1 = (p0 - stroke[ 1].point).normalized() + dn = (pn - stroke[-2].point).normalized() + v0.point += d1 * self._l * stroke.length_2d + vn.point += dn * self._l * stroke.length_2d stroke.update_length() - class pyGuidingLineShader(StrokeShader): - """ - Replaces the stroke by its corresponding tangent - """ def shade(self, stroke): - it = stroke.stroke_vertices_begin() ## get the first vertex - itlast = stroke.stroke_vertices_end() ## - itlast.decrement() ## get the last one - t = itlast.object.point - it.object.point ## tangent direction - itmiddle = StrokeVertexIterator(it) ## - while itmiddle.object.u < 0.5: ## look for the stroke middle vertex - itmiddle.increment() ## - it = StrokeVertexIterator(itmiddle) - it.increment() - while not it.is_end: ## position all the vertices along the tangent for the right part - it.object.point = itmiddle.object.point+t*(it.object.u-itmiddle.object.u) - it.increment() + # get the tangent direction + t = stroke[-1].point - stroke[0].point + # look for the stroke middle vertex + itmiddle = iter(stroke) + while itmiddle.object.u < 0.5: + itmiddle.increment() + center_vertex = itmiddle.object + # position all the vertices along the tangent for the right part it = StrokeVertexIterator(itmiddle) - it.decrement() - while not it.is_begin: ## position all the vertices along the tangent for the left part - it.object.point = itmiddle.object.point-t*(itmiddle.object.u-it.object.u) - it.decrement() - it.object.point = itmiddle.object.point-t*itmiddle.object.u ## first vertex + for svert in it: + svert.point = center_vertex.point + t * (svert.u - center_vertex.u) + # position all the vertices along the tangent for the left part + it = StrokeVertexIterator(itmiddle).reversed() + for svert in it: + svert.point = center_vertex.point - t * (center_vertex.u - svert.u) stroke.update_length() @@ -754,25 +624,18 @@ class pyBackboneStretcherNoCuspShader(StrokeShader): self._l = l def shade(self, stroke): - it0 = stroke.stroke_vertices_begin() - it1 = StrokeVertexIterator(it0) - it1.increment() - itn = stroke.stroke_vertices_end() - itn.decrement() - itn_1 = StrokeVertexIterator(itn) - itn_1.decrement() - v0 = it0.object - v1 = it1.object - if (v0.nature & Nature.CUSP) == 0 and (v1.nature & Nature.CUSP) == 0: + + v0, v1 = stroke[0], stroke[1] + vn, vn_1 = stroke[-1], stroke[-2] + + if not (v0.nature & v1.nature & Nature.CUSP): d1 = (v0.point - v1.point).normalized() - newFirst = v0.point+d1*float(self._l) - v0.point = newFirst - vn_1 = itn_1.object - vn = itn.object - if (vn.nature & Nature.CUSP) == 0 and (vn_1.nature & Nature.CUSP) == 0: + v0.point += d1 * self._l + + if not (vn.nature & vn_1.nature & Nature.CUSP): dn = (vn.point - vn_1.point).normalized() - newLast = vn.point + dn * float(self._l) - vn.point = newLast + vn.point += dn * self._l + stroke.update_length() @@ -792,13 +655,9 @@ class pyDiffusion2Shader(StrokeShader): def shade(self, stroke): for i in range (1, self._nbIter): - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - p1 = v.point - p2 = self._normalInfo(Interface0DIterator(it))*self._lambda*self._curvatureInfo(Interface0DIterator(it)) - v.point = p1+p2 - it.increment() + it = Interface0DIterator(stroke) + for svert in it: + svert.point += self._normalInfo(it) * self._lambda * self._curvatureInfo(it) stroke.update_length() @@ -810,33 +669,36 @@ class pyTipRemoverShader(StrokeShader): StrokeShader.__init__(self) self._l = l + @staticmethod + def check_vertex(v, length): + """ + Returns True if the given strokevertex is less than self._l away + from the stroke's tip and therefore should be removed. + """ + return (v.curvilinear_abscissa < length or v.stroke_length-v.curvilinear_abscissa < length) + def shade(self, stroke): - originalSize = stroke.stroke_vertices_size() - if originalSize < 4: + n = len(stroke) + if n < 4: return - verticesToRemove = [] - oldAttributes = [] - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - if v.curvilinear_abscissa < self._l or v.stroke_length-v.curvilinear_abscissa < self._l: - verticesToRemove.append(v) - oldAttributes.append(StrokeAttribute(v.attribute)) - it.increment() - if originalSize-len(verticesToRemove) < 2: + + verticesToRemove = tuple(svert for svert in stroke if self.check_vertex(svert, self._l)) + # explicit conversion to StrokeAttribute is needed + oldAttributes = (StrokeAttribute(svert.attribute) for svert in stroke) + + if n - len(verticesToRemove) < 2: return + for sv in verticesToRemove: stroke.remove_vertex(sv) + stroke.update_length() - stroke.resample(originalSize) - if stroke.stroke_vertices_size() != originalSize: + stroke.resample(n) + if len(stroke) != n and bpy.app.debug_freestyle: print("pyTipRemover: Warning: resampling problem") - it = stroke.stroke_vertices_begin() - for a in oldAttributes: - if it.is_end: - break - it.object.attribute = a - it.increment() + + for svert, a in zip(stroke, oldAttributes): + svert.attribute = a stroke.update_length() @@ -845,28 +707,15 @@ class pyTVertexRemoverShader(StrokeShader): Removes t-vertices from the stroke """ def shade(self, stroke): - if stroke.stroke_vertices_size() <= 3: + if len(stroke) < 4: return - predTVertex = pyVertexNatureUP0D(Nature.T_VERTEX) - it = stroke.stroke_vertices_begin() - itlast = stroke.stroke_vertices_end() - itlast.decrement() - if predTVertex(it): - stroke.remove_vertex(it.object) - if predTVertex(itlast): - stroke.remove_vertex(itlast.object) - stroke.update_length() - -#class pyExtremitiesOrientationShader(StrokeShader): -# def __init__(self, x1,y1,x2=0,y2=0): -# StrokeShader.__init__(self) -# self._v1 = Vector((x1,y1)) -# self._v2 = Vector((x2,y2)) -# def shade(self, stroke): -# #print(self._v1.x,self._v1.y) -# stroke.setBeginningOrientation(self._v1.x,self._v1.y) -# stroke.setEndingOrientation(self._v2.x,self._v2.y) + v0, vn = stroke[0], stroke[-1] + if (v0.nature & Nature.T_VERTEX): + stroke.remove_vertex(v0) + if (vn.nature & Nature.T_VERTEX): + stroke.remove_vertex(vn) + stroke.update_length() class pyHLRShader(StrokeShader): @@ -875,115 +724,14 @@ class pyHLRShader(StrokeShader): based on hidden line removal (HLR) """ def shade(self, stroke): - originalSize = stroke.stroke_vertices_size() - if originalSize < 4: + if len(stroke) < 4: return - it = stroke.stroke_vertices_begin() - invisible = 0 - it2 = StrokeVertexIterator(it) - it2.increment() - fe = self.get_fedge(it.object, it2.object) - if fe.viewedge.qi != 0: - invisible = 1 - while not it2.is_end: - v = it.object - vnext = it2.object - if (v.nature & Nature.VIEW_VERTEX) != 0: - #if (v.nature & Nature.T_VERTEX) != 0: - fe = self.get_fedge(v, vnext) - qi = fe.viewedge.qi - if qi != 0: - invisible = 1 - else: - invisible = 0 - if invisible: - v.attribute.visible = False - it.increment() - it2.increment() - - def get_fedge(self, it1, it2): - return it1.get_fedge(it2) - -# broken and a mess -class pyTVertexOrientationShader(StrokeShader): - def __init__(self): - StrokeShader.__init__(self) - self._Get2dDirection = Orientation2DF1D() - ## finds the TVertex orientation from the TVertex and - ## the previous or next edge - - def findOrientation(self, tv, ve): - mateVE = tv.get_mate(ve) - if ve.qi != 0 or mateVE.qi != 0: - ait = AdjacencyIterator(tv,1,0) - winner = None - incoming = True - while not ait.is_end: - ave = ait.object - if ave.id != ve.id and ave.id != mateVE.id: - winner = ait.object - if not ait.isIncoming(): # FIXME - incoming = False - break - ait.increment() - if winner is not None: - if not incoming: - direction = self._Get2dDirection(winner.last_fedge) - else: - direction = self._Get2dDirection(winner.first_fedge) - return direction - return None - - def castToTVertex(self, cp): - if cp.t2d() == 0.0: - return cp.first_svertex.viewvertex - elif cp.t2d() == 1.0: - return cp.second_svertex.viewvertex - return None - - def shade(self, stroke): - it = stroke.stroke_vertices_begin() - it2 = StrokeVertexIterator(it) - it2.increment() - ## case where the first vertex is a TVertex - v = it.object - if (v.nature & Nature.T_VERTEX) != 0: - tv = self.castToTVertex(v) - if tv is not None: - ve = self.get_fedge(v, it2.object).viewedge - dir = self.findOrientation(tv, ve) - if dir is not None: - #print(dir.x, dir.y) - v.attribute.set_attribute_vec2("orientation", dir) - while not it2.is_end: - vprevious = it.object - v = it2.object - if (v.nature & Nature.T_VERTEX) != 0: - tv = self.castToTVertex(v) - if tv is not None: - ve = self.get_fedge(vprevious, v).viewedge - dir = self.findOrientation(tv, ve) - if dir is not None: - #print(dir.x, dir.y) - v.attribute.set_attribute_vec2("orientation", dir) - it.increment() - it2.increment() - ## case where the last vertex is a TVertex - v = it.object - if (v.nature & Nature.T_VERTEX) != 0: - itPrevious = StrokeVertexIterator(it) - itPrevious.decrement() - tv = self.castToTVertex(v) - if tv is not None: - ve = self.get_fedge(itPrevious.object, v).viewedge - dir = self.findOrientation(tv, ve) - if dir is not None: - #print(dir.x, dir.y) - v.attribute.set_attribute_vec2("orientation", dir) - - def get_fedge(self, it1, it2): - return it1.get_fedge(it2) + it = iter(stroke) + for v1, v2 in zip(it, it.incremented()): + if (v1.nature & Nature.VIEW_VERTEX): + visible = (v1.get_fedge(v2).viewedge.qi != 0) + v1.attribute.visible = not visible class pySinusDisplacementShader(StrokeShader): @@ -997,19 +745,12 @@ class pySinusDisplacementShader(StrokeShader): self._getNormal = Normal2DF0D() def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - #print(self._getNormal.name) - n = self._getNormal(Interface0DIterator(it)) - p = v.point - u = v.u - a = self._a*(1-2*(abs(u-0.5))) - n = n*a*cos(self._f*u*6.28) - #print(n.x, n.y) - v.point = p+n - #v.point = v.point+n*a*cos(f*v.u) - it.increment() + it = Interface0DIterator(stroke) + for svert in it: + normal = self._getNormal(it) + a = self._a * (1 - 2 * (abs(svert.u - 0.5))) + n = normal * a * cos(self._f * svert.u * 6.28) + svert.point += n stroke.update_length() @@ -1027,13 +768,10 @@ class pyPerlinNoise1DShader(StrokeShader): self.__oct = oct def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - i = v.projected_x + v.projected_y - nres = self.__noise.turbulence1(i, self.__freq, self.__amp, self.__oct) - v.point = (v.projected_x + nres, v.projected_y + nres) - it.increment() + for svert in stroke: + s = svert.projected_x + svert.projected_y + nres = self.__noise.turbulence1(s, self.__freq, self.__amp, self.__oct) + svert.point = (svert.projected_x + nres, svert.projected_y + nres) stroke.update_length() @@ -1053,12 +791,9 @@ class pyPerlinNoise2DShader(StrokeShader): self.__oct = oct def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - v = it.object - nres = self.__noise.turbulence2(v.point_2d, self.__freq, self.__amp, self.__oct) - v.point = (v.projected_x + nres, v.projected_y + nres) - it.increment() + for svert in stroke: + nres = self.__noise.turbulence2(svert.point_2d, self.__freq, self.__amp, self.__oct) + svert.point = (svert.projected_x + nres, svert.projected_y + nres) stroke.update_length() @@ -1073,66 +808,52 @@ class pyBluePrintCirclesShader(StrokeShader): self.__random_radius = random_radius def shade(self, stroke): - it = stroke.stroke_vertices_begin() - if it.is_end: - return - p_min = it.object.point.copy() - p_max = it.object.point.copy() - while not it.is_end: - p = it.object.point - if p.x < p_min.x: - p_min.x = p.x - if p.x > p_max.x: - p_max.x = p.x - if p.y < p_min.y: - p_min.y = p.y - if p.y > p_max.y: - p_max.y = p.y - it.increment() + # get minimum and maximum coordinates + p_min, p_max = bounding_box(stroke) + stroke.resample(32 * self.__turns) - sv_nb = stroke.stroke_vertices_size() -# print("min :", p_min.x, p_min.y) # DEBUG -# print("mean :", p_sum.x, p_sum.y) # DEBUG -# print("max :", p_max.x, p_max.y) # DEBUG -# print("----------------------") # DEBUG -####################################################### - sv_nb = sv_nb // self.__turns + sv_nb = len(stroke) // self.__turns center = (p_min + p_max) / 2 radius = (center.x - p_min.x + center.y - p_min.y) / 2 - p_new = Vector((0.0, 0.0)) -####################################################### R = self.__random_radius C = self.__random_center - i = 0 - it = stroke.stroke_vertices_begin() + + # The directions (and phases) are calculated using a seperate + # function decorated with an lru-cache. This guarantees that + # the directions (involving sin and cos) are calculated as few + # times as possible. + # + # This works because the phases and directions are only + # dependant on the stroke length, and the chance that + # stroke.resample() above produces strokes of the same length + # is quite high. + # + # In tests, the amount of calls to sin() and cos() went from + # over 21000 to just 32 times, yielding a speedup of over 100% + directions = phase_to_direction(sv_nb) + + it = iter(stroke) + for j in range(self.__turns): prev_radius = radius prev_center = center - radius = radius + randint(-R, R) - center = center + Vector((randint(-C, C), randint(-C, C))) - while i < sv_nb and not it.is_end: - t = float(i) / float(sv_nb - 1) - r = prev_radius + (radius - prev_radius) * t - c = prev_center + (center - prev_center) * t - p_new.x = c.x + r * cos(2 * pi * t) - p_new.y = c.y + r * sin(2 * pi * t) - it.object.point = p_new - i = i + 1 - it.increment() - i = 1 - verticesToRemove = [] - while not it.is_end: - verticesToRemove.append(it.object) + radius += randint(-R, R) + center += Vector((randint(-C, C), randint(-C, C))) + + for (phase, direction), svert in zip(directions, it): + r = prev_radius + (radius - prev_radius) * phase + c = prev_center + (center - prev_center) * phase + svert.point = c + r * direction + + if not it.is_end: it.increment() - for sv in verticesToRemove: - stroke.remove_vertex(sv) + for sv in tuple(it): + stroke.remove_vertex(sv) + stroke.update_length() class pyBluePrintEllipsesShader(StrokeShader): - """ - Draws the silhouette of the object as an ellips - """ def __init__(self, turns=1, random_radius=3, random_center=5): StrokeShader.__init__(self) self.__turns = turns @@ -1140,311 +861,300 @@ class pyBluePrintEllipsesShader(StrokeShader): self.__random_radius = random_radius def shade(self, stroke): - it = stroke.stroke_vertices_begin() - if it.is_end: - return - p_min = it.object.point.copy() - p_max = it.object.point.copy() - while not it.is_end: - p = it.object.point - if p.x < p_min.x: - p_min.x = p.x - if p.x > p_max.x: - p_max.x = p.x - if p.y < p_min.y: - p_min.y = p.y - if p.y > p_max.y: - p_max.y = p.y - it.increment() + p_min, p_max = bounding_box(stroke) + stroke.resample(32 * self.__turns) - sv_nb = stroke.stroke_vertices_size() - sv_nb = sv_nb // self.__turns + sv_nb = len(stroke) // self.__turns + center = (p_min + p_max) / 2 radius = center - p_min - p_new = Vector((0.0, 0.0)) -####################################################### + R = self.__random_radius C = self.__random_center - i = 0 - it = stroke.stroke_vertices_begin() + + # for description of the line below, see pyBluePrintCirclesShader + directions = phase_to_direction(sv_nb) + it = iter(stroke) for j in range(self.__turns): prev_radius = radius prev_center = center radius = radius + Vector((randint(-R, R), randint(-R, R))) center = center + Vector((randint(-C, C), randint(-C, C))) - while i < sv_nb and not it.is_end: - t = float(i) / float(sv_nb - 1) - r = prev_radius + (radius - prev_radius) * t - c = prev_center + (center - prev_center) * t - p_new.x = c.x + r.x * cos(2 * pi * t) - p_new.y = c.y + r.y * sin(2 * pi * t) - it.object.point = p_new - i = i + 1 - it.increment() - i = 1 - verticesToRemove = [] - while not it.is_end: - verticesToRemove.append(it.object) + + for (phase, direction), svert in zip(directions, it): + r = prev_radius + (radius - prev_radius) * phase + c = prev_center + (center - prev_center) * phase + svert.point = (c.x + r.x * direction.x, c.y + r.y * direction.y) + + # remove exessive vertices + if not it.is_end: it.increment() - for sv in verticesToRemove: - stroke.remove_vertex(sv) + for sv in tuple(it): + stroke.remove_vertex(sv) + stroke.update_length() class pyBluePrintSquaresShader(StrokeShader): - """ - Draws the silhouette of the object as a square - """ def __init__(self, turns=1, bb_len=10, bb_rand=0): StrokeShader.__init__(self) - self.__turns = turns + self.__turns = turns # does not have any effect atm self.__bb_len = bb_len self.__bb_rand = bb_rand def shade(self, stroke): - it = stroke.stroke_vertices_begin() - if it.is_end: + # this condition will lead to errors later, end now + if len(stroke) < 1: return - p_min = it.object.point.copy() - p_max = it.object.point.copy() - while not it.is_end: - p = it.object.point - if p.x < p_min.x: - p_min.x = p.x - if p.x > p_max.x: - p_max.x = p.x - if p.y < p_min.y: - p_min.y = p.y - if p.y > p_max.y: - p_max.y = p.y - it.increment() + + # get minimum and maximum coordinates + p_min, p_max = bounding_box(stroke) + stroke.resample(32 * self.__turns) - sv_nb = stroke.stroke_vertices_size() -####################################################### - sv_nb = sv_nb // self.__turns - first = sv_nb // 4 - second = 2 * first - third = 3 * first - fourth = sv_nb - p_first = Vector((p_min.x - self.__bb_len, p_min.y)) - p_first_end = Vector((p_max.x + self.__bb_len, p_min.y)) - p_second = Vector((p_max.x, p_min.y - self.__bb_len)) - p_second_end = Vector((p_max.x, p_max.y + self.__bb_len)) - p_third = Vector((p_max.x + self.__bb_len, p_max.y)) - p_third_end = Vector((p_min.x - self.__bb_len, p_max.y)) - p_fourth = Vector((p_min.x, p_max.y + self.__bb_len)) - p_fourth_end = Vector((p_min.x, p_min.y - self.__bb_len)) -####################################################### - R = self.__bb_rand - r = self.__bb_rand // 2 - it = stroke.stroke_vertices_begin() - visible = True + num_segments = len(stroke) // self.__turns + f = num_segments // 4 + # indices of the vertices that will form corners + first, second, third, fourth = (f, f * 2, f * 3, num_segments) + + # construct points of the backbone + bb_len = self.__bb_len + points = ( + Vector((p_min.x - bb_len, p_min.y)), + Vector((p_max.x + bb_len, p_min.y)), + Vector((p_max.x, p_min.y - bb_len)), + Vector((p_max.x, p_max.y + bb_len)), + Vector((p_max.x + bb_len, p_max.y)), + Vector((p_min.x - bb_len, p_max.y)), + Vector((p_min.x, p_max.y + bb_len)), + Vector((p_min.x, p_min.y - bb_len)), + ) + + # add randomization to the points (if needed) + if self.__bb_rand: + R, r = self.__bb_rand, self.__bb_rand // 2 + + randomization_mat = ( + Vector((randint(-R, R), randint(-r, r))), + Vector((randint(-R, R), randint(-r, r))), + Vector((randint(-r, r), randint(-R, R))), + Vector((randint(-r, r), randint(-R, R))), + Vector((randint(-R, R), randint(-r, r))), + Vector((randint(-R, R), randint(-r, r))), + Vector((randint(-r, r), randint(-R, R))), + Vector((randint(-r, r), randint(-R, R))), + ) + + # combine both tuples + points = tuple(p + rand for (p, rand) in zip(points, randomization_mat)) + + + # substract even from uneven; result is length four tuple of vectors + it = iter(points) + old_vecs = tuple(next(it) - current for current in it) + + it = iter(stroke) + verticesToRemove = list() for j in range(self.__turns): - p_first = p_first + Vector((randint(-R, R), randint(-r, r))) - p_first_end = p_first_end + Vector((randint(-R, R), randint(-r, r))) - p_second = p_second + Vector((randint(-r, r), randint(-R, R))) - p_second_end = p_second_end + Vector((randint(-r, r), randint(-R, R))) - p_third = p_third + Vector((randint(-R, R), randint(-r, r))) - p_third_end = p_third_end + Vector((randint(-R, R), randint(-r, r))) - p_fourth = p_fourth + Vector((randint(-r, r), randint(-R, R))) - p_fourth_end = p_fourth_end + Vector((randint(-r, r), randint(-R, R))) - vec_first = p_first_end - p_first - vec_second = p_second_end - p_second - vec_third = p_third_end - p_third - vec_fourth = p_fourth_end - p_fourth - i = 0 - while i < sv_nb and not it.is_end: + for i, svert in zip(range(num_segments), it): if i < first: - p_new = p_first + vec_first * float(i)/float(first - 1) - if i == first - 1: - visible = False + svert.point = points[0] + old_vecs[0] * i / (first - 1) + svert.attribute.visible = (i != first - 1) elif i < second: - p_new = p_second + vec_second * float(i - first)/float(second - first - 1) - if i == second - 1: - visible = False + svert.point = points[2] + old_vecs[1] * (i - first) / (second - first - 1) + svert.attribute.visible = (i != second - 1) elif i < third: - p_new = p_third + vec_third * float(i - second)/float(third - second - 1) - if i == third - 1: - visible = False + svert.point = points[4] + old_vecs[2] * (i - second) / (third - second - 1) + svert.attribute.visible = (i != third - 1) + elif i < fourth: + svert.point = points[6] + old_vecs[3] * (i - third) / (fourth - third - 1) + svert.attribute.visible = (i != fourth - 1) else: - p_new = p_fourth + vec_fourth * float(i - third)/float(fourth - third - 1) - if i == fourth - 1: - visible = False - if it.object is None: - i = i + 1 - it.increment() - if not visible: - visible = True - continue - it.object.point = p_new - it.object.attribute.visible = visible - if not visible: - visible = True - i = i + 1 - it.increment() - verticesToRemove = [] - while not it.is_end: - verticesToRemove.append(it.object) + # special case; remove these vertices + verticesToRemove.append(svert) + + # remove exessive vertices (if any) + if not it.is_end: it.increment() - for sv in verticesToRemove: - stroke.remove_vertex(sv) + verticesToRemove += [svert for svert in it] + for sv in verticesToRemove: + stroke.remove_vertex(sv) stroke.update_length() -# needs a docstring class pyBluePrintDirectedSquaresShader(StrokeShader): + """ + Replaces the stroke with a directed square + """ def __init__(self, turns=1, bb_len=10, mult=1): StrokeShader.__init__(self) self.__mult = mult self.__turns = turns - self.__bb_len = 1 + float(bb_len) / 100 + self.__bb_len = 1 + bb_len * 0.01 def shade(self, stroke): stroke.resample(32 * self.__turns) - p_mean = Vector((0.0, 0.0)) - it = stroke.stroke_vertices_begin() - while not it.is_end: - p = it.object.point - p_mean = p_mean + p - it.increment() - sv_nb = stroke.stroke_vertices_size() - p_mean = p_mean / sv_nb - p_var_xx = 0 - p_var_yy = 0 - p_var_xy = 0 - it = stroke.stroke_vertices_begin() - while not it.is_end: - p = it.object.point - p_var_xx = p_var_xx + pow(p.x - p_mean.x, 2) - p_var_yy = p_var_yy + pow(p.y - p_mean.y, 2) - p_var_xy = p_var_xy + (p.x - p_mean.x) * (p.y - p_mean.y) - it.increment() - p_var_xx = p_var_xx / sv_nb - p_var_yy = p_var_yy / sv_nb - p_var_xy = p_var_xy / sv_nb -## print(p_var_xx, p_var_yy, p_var_xy) - trace = p_var_xx + p_var_yy - det = p_var_xx * p_var_yy - p_var_xy * p_var_xy + n = len(stroke) + + p_mean = (1 / n) * sum((svert.point for svert in stroke), Vector((0.0, 0.0))) + p_var = Vector((0, 0)) + p_var_xy = 0.0 + for d in (svert.point - p_mean for svert in stroke): + p_var += Vector((d.x ** 2, d.y ** 2)) + p_var_xy += d.x * d.y + + # divide by number of vertices + p_var /= n + p_var_xy /= n + trace = p_var.x + p_var.y + det = p_var.x * p_var.y - pow(p_var_xy, 2) + sqrt_coeff = sqrt(trace * trace - 4 * det) - lambda1 = (trace + sqrt_coeff) / 2 - lambda2 = (trace - sqrt_coeff) / 2 -## print(lambda1, lambda2) - theta = atan(2 * p_var_xy / (p_var_xx - p_var_yy)) / 2 -## print(theta) - if p_var_yy > p_var_xx: + lambda1, lambda2 = (trace + sqrt_coeff) / 2, (trace - sqrt_coeff) / 2 + # make sure those numers aren't to small, if they are, rooting them will yield complex numbers + lambda1, lambda2 = max(1e-12, lambda1), max(1e-12, lambda2) + theta = atan(2 * p_var_xy / (p_var.x - p_var.y)) / 2 + + if p_var.y > p_var.x: e1 = Vector((cos(theta + pi / 2), sin(theta + pi / 2))) * sqrt(lambda1) * self.__mult - e2 = Vector((cos(theta + pi), sin(theta + pi))) * sqrt(lambda2) * self.__mult + e2 = Vector((cos(theta + pi ), sin(theta + pi ))) * sqrt(lambda2) * self.__mult else: - e1 = Vector((cos(theta), sin(theta))) * sqrt(lambda1) * self.__mult + e1 = Vector((cos(theta), sin(theta))) * sqrt(lambda1) * self.__mult e2 = Vector((cos(theta + pi / 2), sin(theta + pi / 2))) * sqrt(lambda2) * self.__mult -####################################################### - sv_nb = sv_nb // self.__turns - first = sv_nb // 4 - second = 2 * first - third = 3 * first - fourth = sv_nb + + # partition the stroke + num_segments = len(stroke) // self.__turns + f = num_segments // 4 + # indices of the vertices that will form corners + first, second, third, fourth = (f, f * 2, f * 3, num_segments) + bb_len1 = self.__bb_len bb_len2 = 1 + (bb_len1 - 1) * sqrt(lambda1 / lambda2) - p_first = p_mean - e1 - e2 * bb_len2 - p_second = p_mean - e1 * bb_len1 + e2 - p_third = p_mean + e1 + e2 * bb_len2 - p_fourth = p_mean + e1 * bb_len1 - e2 - vec_first = e2 * bb_len2 * 2 - vec_second = e1 * bb_len1 * 2 - vec_third = vec_first * -1 - vec_fourth = vec_second * -1 -####################################################### - it = stroke.stroke_vertices_begin() - visible = True + points = ( + p_mean - e1 - e2 * bb_len2, + p_mean - e1 * bb_len1 + e2, + p_mean + e1 + e2 * bb_len2, + p_mean + e1 * bb_len1 - e2, + ) + + old_vecs = ( + e2 * bb_len2 * 2, + e1 * bb_len1 * 2, + -e2 * bb_len2 * 2, + -e1 * bb_len1 * 2, + ) + + it = iter(stroke) + verticesToRemove = list() for j in range(self.__turns): - i = 0 - while i < sv_nb: + for i, svert in zip(range(num_segments), it): if i < first: - p_new = p_first + vec_first * float(i)/float(first - 1) - if i == first - 1: - visible = False + svert.point = points[0] + old_vecs[0] * i / (first - 1) + svert.attribute.visible = (i != first - 1) elif i < second: - p_new = p_second + vec_second * float(i - first)/float(second - first - 1) - if i == second - 1: - visible = False + svert.point = points[1] + old_vecs[1] * (i - first) / (second - first - 1) + svert.attribute.visible = (i != second - 1) elif i < third: - p_new = p_third + vec_third * float(i - second)/float(third - second - 1) - if i == third - 1: - visible = False + svert.point = points[2] + old_vecs[2] * (i - second) / (third - second - 1) + svert.attribute.visible = (i != third - 1) + elif i < fourth: + svert.point = points[3] + old_vecs[3] * (i - third) / (fourth - third - 1) + svert.attribute.visible = (i != fourth - 1) else: - p_new = p_fourth + vec_fourth * float(i - third)/float(fourth - third - 1) - if i == fourth - 1: - visible = False - it.object.point = p_new - it.object.attribute.visible = visible - if not visible: - visible = True - i = i + 1 - it.increment() - verticesToRemove = [] - while not it.is_end: - verticesToRemove.append(it.object) + # special case; remove these vertices + verticesToRemove.append(svert) + + # remove exessive vertices + if not it.is_end: it.increment() - for sv in verticesToRemove: - stroke.remove_vertex(sv) + verticesToRemove += [svert for svert in it] + for sv in verticesToRemove: + stroke.remove_vertex(sv) stroke.update_length() -class pyModulateAlphaShader(StrokeShader): - """ - Limits the stroke's alpha between a min and max value - """ - def __init__(self, min=0, max=1): - StrokeShader.__init__(self) - self.__min = min - self.__max = max +# -- various (used in the parameter editor) -- # - def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - alpha = it.object.attribute.alpha - p = it.object.point - alpha = alpha * p.y / 400 - if alpha < self.__min: - alpha = self.__min - elif alpha > self.__max: - alpha = self.__max - it.object.attribute.alpha = alpha - it.increment() +class RoundCapShader(StrokeShader): + def round_cap_thickness(self, x): + x = max(0.0, min(x, 1.0)) + return pow(1.0 - (x ** 2.0), 0.5) -## various -class pyDummyShader(StrokeShader): def shade(self, stroke): - it = stroke.stroke_vertices_begin() - while not it.is_end: - toto = Interface0DIterator(it) - att = it.object.attribute - att.color = (0.3, 0.4, 0.4) - att.thickness = (0, 5) - it.increment() + # save the location and attribute of stroke vertices + buffer = tuple((Vector(sv.point), StrokeAttribute(sv.attribute)) for sv in stroke) + nverts = len(buffer) + if nverts < 2: + return + # calculate the number of additional vertices to form caps + thickness_beg = sum(stroke[0].attribute.thickness) + caplen_beg = thickness_beg / 2.0 + nverts_beg = max(5, int(thickness_beg)) + + thickness_end = sum(stroke[-1].attribute.thickness) + caplen_end = (thickness_end) / 2.0 + nverts_end = max(5, int(thickness_end)) + + # adjust the total number of stroke vertices + stroke.resample(nverts + nverts_beg + nverts_end) + # restore the location and attribute of the original vertices + for i, (p, attr) in enumerate(buffer): + stroke[nverts_beg + i].point = p + stroke[nverts_beg + i].attribute = attr + # reshape the cap at the beginning of the stroke + q, attr = buffer[1] + p, attr = buffer[0] + direction = (p - q).normalized() * caplen_beg + n = 1.0 / nverts_beg + R, L = attr.thickness + for t, svert in zip(range(nverts_beg, 0, -1), stroke): + r = self.round_cap_thickness((t + 1) * n) + svert.point = p + direction * t * n + svert.attribute = attr + svert.attribute.thickness = (R * r, L * r) + # reshape the cap at the end of the stroke + q, attr = buffer[-2] + p, attr = buffer[-1] + direction = (p - q).normalized() * caplen_beg + n = 1.0 / nverts_end + R, L = attr.thickness + for t, svert in zip(range(nverts_end, 0, -1), reversed(stroke)): + r = self.round_cap_thickness((t + 1) * n) + svert.point = p + direction * t * n + svert.attribute = attr + svert.attribute.thickness = (R * r, L * r) + # update the curvilinear 2D length of each vertex + stroke.update_length() -class pyDebugShader(StrokeShader): +class SquareCapShader(StrokeShader): def shade(self, stroke): - fe = CF.get_selected_fedge() - id1 = fe.first_svertex.id - id2 = fe.second_svertex.id - #print(id1.first, id1.second) - #print(id2.first, id2.second) - it = stroke.stroke_vertices_begin() - found = True - foundfirst = True - foundsecond = False - while not it.is_end: - cp = it.object - if cp.first_svertex.id == id1 or cp.second_svertex.id == id1: - foundfirst = True - if cp.first_svertex.id == id2 or cp.second_svertex.id == id2: - foundsecond = True - if foundfirst and foundsecond: - found = True - break - it.increment() - if found: - print("The selected Stroke id is: ", stroke.id.first, stroke.id.second) + # save the location and attribute of stroke vertices + buffer = tuple((Vector(sv.point), StrokeAttribute(sv.attribute)) for sv in stroke) + nverts = len(buffer) + if nverts < 2: + return + # calculate the number of additional vertices to form caps + caplen_beg = sum(stroke[0].attribute.thickness) / 2.0 + nverts_beg = 1 + + caplen_end = sum(stroke[-1].attribute.thickness) / 2.0 + nverts_end = 1 + # adjust the total number of stroke vertices + stroke.resample(nverts + nverts_beg + nverts_end) + # restore the location and attribute of the original vertices + for i, (p, attr) in zip(range(nverts), buffer): + stroke[nverts_beg + i].point = p + stroke[nverts_beg + i].attribute = attr + # reshape the cap at the beginning of the stroke + q, attr = buffer[1] + p, attr = buffer[0] + stroke[0].point += (p - q).normalized() * caplen_beg + stroke[0].attribute = attr + # reshape the cap at the end of the stroke + q, attr = buffer[-2] + p, attr = buffer[-1] + stroke[-1].point += (p - q).normalized() * caplen_end + stroke[-1].attribute = attr + # update the curvilinear 2D length of each vertex + stroke.update_length() diff --git a/release/scripts/freestyle/modules/freestyle/utils.py b/release/scripts/freestyle/modules/freestyle/utils.py index 24ff86d5ef6..1b576791e9b 100644 --- a/release/scripts/freestyle/modules/freestyle/utils.py +++ b/release/scripts/freestyle/modules/freestyle/utils.py @@ -27,11 +27,275 @@ from _freestyle import ( integrate, ) -# constructs for definition of helper functions in Python -from freestyle.types import ( - StrokeVertexIterator, - ) -import mathutils +from mathutils import Vector +from functools import lru_cache +from math import cos, sin, pi + + +# -- real utility functions -- # + + +def rgb_to_bw(r, g, b): + """ Method to convert rgb to a bw intensity value. """ + return 0.35 * r + 0.45 * g + 0.2 * b + + +def bound(lower, x, higher): + """ Returns x bounded by a maximum and minimum value. equivalent to: + return min(max(x, lower), higher) + """ + # this is about 50% quicker than min(max(x, lower), higher) + return (lower if x <= lower else higher if x >= higher else x) + + +def bounding_box(stroke): + """ + Returns the maximum and minimum coordinates (the bounding box) of the stroke's vertices + """ + x, y = zip(*(svert.point for svert in stroke)) + return (Vector((min(x), min(y))), Vector((max(x), max(y)))) + + +# -- General helper functions -- # + + +@lru_cache(maxsize=32) +def phase_to_direction(length): + """ + Returns a list of tuples each containing: + - the phase + - a Vector with the values of the cosine and sine of 2pi * phase (the direction) + """ + results = list() + for i in range(length): + phase = i / (length - 1) + results.append((phase, Vector((cos(2 * pi * phase), sin(2 * pi * phase))))) + return results + + +# -- helper functions for chaining -- # + + +def get_chain_length(ve, orientation): + """Returns the 2d length of a given ViewEdge """ + from freestyle.chainingiterators import pyChainSilhouetteGenericIterator + length = 0.0 + # setup iterator + _it = pyChainSilhouetteGenericIterator(False, False) + _it.begin = ve + _it.current_edge = ve + _it.orientation = orientation + _it.init() + + # run iterator till end of chain + while not (_it.is_end): + length += _it.object.length_2d + if (_it.is_begin): + # _it has looped back to the beginning; + # break to prevent infinite loop + break + _it.increment() + + # reset iterator + _it.begin = ve + _it.current_edge = ve + _it.orientation = orientation + + # run iterator till begin of chain + if not _it.is_begin: + _it.decrement() + while not (_it.is_end or _it.is_begin): + length += _it.object.length_2d + _it.decrement() + + return length + + +def find_matching_vertex(id, it): + """Finds the matching vertexn, or returns None """ + return next((ve for ve in it if ve.id == id), None) + + +# -- helper functions for iterating -- # + + +def iter_current_previous(stroke): + """ + iterates over the given iterator. yields a tuple of the form + (it, prev, current) + """ + prev = stroke[0] + it = Interface0DIterator(stroke) + for current in it: + yield (it, prev, current) + + +def iter_t2d_along_stroke(stroke): + """ + Yields the distance between two stroke vertices + relative to the total stroke length. + """ + total = stroke.length_2d + distance = 0.0 + for it, prev, svert in iter_current_previous(stroke): + distance += (prev.point - svert.point).length + t = min(distance / total, 1.0) if total > 0.0 else 0.0 + yield (it, t) + + +def iter_distance_from_camera(stroke, range_min, range_max): + """ + Yields the distance to the camera relative to the maximum + possible distance for every stroke vertex, constrained by + given minimum and maximum values. + """ + normfac = range_max - range_min # normalization factor + it = Interface0DIterator(stroke) + for svert in it: + distance = svert.point_3d.length # in the camera coordinate + if distance < range_min: + t = 0.0 + elif distance > range_max: + t = 1.0 + else: + t = (distance - range_min) / normfac + yield (it, t) + + +def iter_distance_from_object(stroke, object, range_min, range_max): + """ + yields the distance to the given object relative to the maximum + possible distance for every stroke vertex, constrained by + given minimum and maximum values. + """ + scene = getCurrentScene() + mv = scene.camera.matrix_world.copy().inverted() # model-view matrix + loc = mv * object.location # loc in the camera coordinate + normfac = range_max - range_min # normalization factor + it = Interface0DIterator(stroke) + for svert in it: + distance = (svert.point_3d - loc).length # in the camera coordinate + if distance < range_min: + t = 0.0 + elif distance > range_max: + t = 1.0 + else: + t = (distance - range_min) / normfac + yield (it, t) + + +def iter_material_color(stroke, material_attribute): + """ + yields the specified material attribute for every stroke vertex. + the material is taken from the object behind the vertex. + """ + func = CurveMaterialF0D() + it = Interface0DIterator(stroke) + for inter in it: + material = func(it) + if material_attribute == 'DIFF': + color = material.diffuse[0:3] + elif material_attribute == 'SPEC': + color = material.specular[0:3] + else: + raise ValueError("unexpected material attribute: " + material_attribute) + yield (it, color) + + +def iter_material_value(stroke, material_attribute): + """ + yields a specific material attribute + from the vertex' underlying material. + """ + func = CurveMaterialF0D() + it = Interface0DIterator(stroke) + for svert in it: + material = func(it) + if material_attribute == 'DIFF': + t = rgb_to_bw(*material.diffuse[0:3]) + elif material_attribute == 'DIFF_R': + t = material.diffuse[0] + elif material_attribute == 'DIFF_G': + t = material.diffuse[1] + elif material_attribute == 'DIFF_B': + t = material.diffuse[2] + elif material_attribute == 'SPEC': + t = rgb_to_bw(*material.specular[0:3]) + elif material_attribute == 'SPEC_R': + t = material.specular[0] + elif material_attribute == 'SPEC_G': + t = material.specular[1] + elif material_attribute == 'SPEC_B': + t = material.specular[2] + elif material_attribute == 'SPEC_HARDNESS': + t = material.shininess + elif material_attribute == 'ALPHA': + t = material.diffuse[3] + else: + raise ValueError("unexpected material attribute: " + material_attribute) + yield (it, t) + + +def iter_distance_along_stroke(stroke): + """ + yields the absolute distance between + the current and preceding vertex. + """ + distance = 0.0 + prev = stroke[0] + it = Interface0DIterator(stroke) + for svert in it: + p = svert.point + distance += (prev - p).length + prev = p.copy() # need a copy because the point can be altered + yield it, distance + + +def iter_triplet(it): + """ + Iterates over it, yielding a tuple containing + the current vertex and its immediate neighbors + """ + prev = next(it) + current = next(it) + for succ in it: + yield prev, current, succ + prev, current = current, succ + + +# -- mathmatical operations -- # + + +def stroke_curvature(it): + """ + Compute the 2D curvature at the stroke vertex pointed by the iterator 'it'. + K = 1 / R + where R is the radius of the circle going through the current vertex and its neighbors + """ + + if it.is_end or it.is_begin: + return 0.0 + + next = it.incremented().point + prev = it.decremented().point + current = it.object.point + + + ab = (current - prev) + bc = (next - current) + ac = (prev - next) + + a, b, c = ab.length, bc.length, ac.length + + try: + area = 0.5 * ab.cross(ac) + K = (4 * area) / (a * b * c) + K = bound(0.0, K, 1.0) + + except ZeroDivisionError: + K = 0.0 + + return K def stroke_normal(it): @@ -42,28 +306,21 @@ def stroke_normal(it): they have already been modified by stroke geometry modifiers. """ # first stroke segment - it_next = StrokeVertexIterator(it) - it_next.increment() + it_next = it.incremented() if it.is_begin: e = it_next.object.point_2d - it.object.point_2d - n = mathutils.Vector((e[1], -e[0])) - n.normalize() - return n + n = Vector((e[1], -e[0])) + return n.normalized() # last stroke segment - it_prev = StrokeVertexIterator(it) - it_prev.decrement() + it_prev = it.decremented() if it_next.is_end: e = it.object.point_2d - it_prev.object.point_2d - n = mathutils.Vector((e[1], -e[0])) - n.normalize() - return n + n = Vector((e[1], -e[0])) + return n.normalized() # two subsequent stroke segments e1 = it_next.object.point_2d - it.object.point_2d e2 = it.object.point_2d - it_prev.object.point_2d - n1 = mathutils.Vector((e1[1], -e1[0])) - n2 = mathutils.Vector((e2[1], -e2[0])) - n1.normalize() - n2.normalize() - n = n1 + n2 - n.normalize() - return n + n1 = Vector((e1[1], -e1[0])).normalized() + n2 = Vector((e2[1], -e2[0])).normalized() + n = (n1 + n2) + return n.normalized() |