diff options
Diffstat (limited to 'release')
61 files changed, 6531 insertions, 133 deletions
diff --git a/release/datafiles/startup.blend b/release/datafiles/startup.blend Binary files differindex e5d781e06e2..bec18dff39e 100644 --- a/release/datafiles/startup.blend +++ b/release/datafiles/startup.blend diff --git a/release/scripts/freestyle/data/env_map/brown00.png b/release/scripts/freestyle/data/env_map/brown00.png Binary files differnew file mode 100644 index 00000000000..855f06f4fb9 --- /dev/null +++ b/release/scripts/freestyle/data/env_map/brown00.png diff --git a/release/scripts/freestyle/data/env_map/gray00.png b/release/scripts/freestyle/data/env_map/gray00.png Binary files differnew file mode 100644 index 00000000000..7c9b1a8149e --- /dev/null +++ b/release/scripts/freestyle/data/env_map/gray00.png diff --git a/release/scripts/freestyle/data/env_map/gray01.png b/release/scripts/freestyle/data/env_map/gray01.png Binary files differnew file mode 100644 index 00000000000..06542908e6b --- /dev/null +++ b/release/scripts/freestyle/data/env_map/gray01.png diff --git a/release/scripts/freestyle/data/env_map/gray02.png b/release/scripts/freestyle/data/env_map/gray02.png Binary files differnew file mode 100644 index 00000000000..0208f4920d9 --- /dev/null +++ b/release/scripts/freestyle/data/env_map/gray02.png diff --git a/release/scripts/freestyle/data/env_map/gray03.png b/release/scripts/freestyle/data/env_map/gray03.png Binary files differnew file mode 100644 index 00000000000..aab9b957c21 --- /dev/null +++ b/release/scripts/freestyle/data/env_map/gray03.png diff --git a/release/scripts/freestyle/style_modules/ChainingIterators.py b/release/scripts/freestyle/style_modules/ChainingIterators.py new file mode 100644 index 00000000000..f0dfc468adb --- /dev/null +++ b/release/scripts/freestyle/style_modules/ChainingIterators.py @@ -0,0 +1,703 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : ChainingIterators.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Chaining Iterators to be used with chaining operators + +from Freestyle import AdjacencyIterator, ChainingIterator, ExternalContourUP1D, Nature, TVertex +from Freestyle import ContextFunctions as CF + +## the natural chaining iterator +## It follows the edges of same nature following the topology of +## objects with preseance on silhouettes, then borders, +## then suggestive contours, then everything else. It doesn't chain the same ViewEdge twice +## You can specify whether to stay in the selection or not. +class pyChainSilhouetteIterator(ChainingIterator): + def __init__(self, stayInSelection=True): + ChainingIterator.__init__(self, stayInSelection, True, None, True) + def init(self): + pass + def traverse(self, iter): + winner = None + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for i in range(len(natures)): + currentNature = self.current_edge.nature + if (natures[i] & currentNature) != 0: + count=0 + while not it.is_end: + visitNext = 0 + oNature = it.object.nature + if (oNature & natures[i]) != 0: + if natures[i] != oNature: + for j in range(i): + if (natures[j] & oNature) != 0: + visitNext = 1 + break + if visitNext != 0: + break + count = count+1 + winner = it.object + it.increment() + if count != 1: + winner = None + break + return winner + +## the natural chaining iterator +## It follows the edges of same nature on the same +## objects with preseance on silhouettes, then borders, +## then suggestive contours, then everything else. It doesn't chain the same ViewEdge twice +## You can specify whether to stay in the selection or not. +## You can specify whether to chain iterate over edges that were +## already visited or not. +class pyChainSilhouetteGenericIterator(ChainingIterator): + def __init__(self, stayInSelection=True, stayInUnvisited=True): + ChainingIterator.__init__(self, stayInSelection, stayInUnvisited, None, True) + def init(self): + pass + def traverse(self, iter): + winner = None + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for i in range(len(natures)): + currentNature = self.current_edge.nature + if (natures[i] & currentNature) != 0: + count=0 + while not it.is_end: + visitNext = 0 + oNature = it.object.nature + ve = it.object + if ve.id == self.current_edge.id: + it.increment() + continue + if (oNature & natures[i]) != 0: + if natures[i] != oNature: + for j in range(i): + if (natures[j] & oNature) != 0: + visitNext = 1 + break + if visitNext != 0: + break + count = count+1 + winner = ve + it.increment() + if count != 1: + winner = None + break + return winner + +class pyExternalContourChainingIterator(ChainingIterator): + def __init__(self): + ChainingIterator.__init__(self, False, True, None, True) + self._isExternalContour = ExternalContourUP1D() + def init(self): + self._nEdges = 0 + self._isInSelection = 1 + def checkViewEdge(self, ve, orientation): + if orientation != 0: + vertex = ve.second_svertex() + else: + vertex = ve.first_svertex() + it = AdjacencyIterator(vertex,1,1) + while not it.is_end: + ave = it.object + if self._isExternalContour(ave): + return 1 + it.increment() + print("pyExternlContourChainingIterator : didn't find next edge") + return 0 + def traverse(self, iter): + winner = None + it = AdjacencyIterator(iter) + while not it.is_end: + ve = it.object + if self._isExternalContour(ve): + if ve.time_stamp == CF.get_time_stamp(): + winner = ve + it.increment() + + self._nEdges = self._nEdges+1 + if winner is None: + orient = 1 + it = AdjacencyIterator(iter) + while not it.is_end: + ve = it.object + if it.is_incoming: + orient = 0 + good = self.checkViewEdge(ve,orient) + if good != 0: + winner = ve + it.increment() + return winner + +## the natural chaining iterator +## with a sketchy multiple touch +class pySketchyChainSilhouetteIterator(ChainingIterator): + def __init__(self, nRounds=3,stayInSelection=True): + ChainingIterator.__init__(self, stayInSelection, False, None, True) + self._timeStamp = CF.get_time_stamp()+nRounds + self._nRounds = nRounds + def init(self): + self._timeStamp = CF.get_time_stamp()+self._nRounds + def traverse(self, iter): + winner = None + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for i in range(len(natures)): + currentNature = self.current_edge.nature + if (natures[i] & currentNature) != 0: + count=0 + while not it.is_end: + visitNext = 0 + oNature = it.object.nature + ve = it.object + if ve.id == self.current_edge.id: + it.increment() + continue + if (oNature & natures[i]) != 0: + if (natures[i] != oNature) != 0: + for j in range(i): + if (natures[j] & oNature) != 0: + visitNext = 1 + break + if visitNext != 0: + break + count = count+1 + winner = ve + it.increment() + if count != 1: + winner = None + break + if winner is None: + winner = self.current_edge + if winner.chaining_time_stamp == self._timeStamp: + winner = None + return winner + + +# Chaining iterator designed for sketchy style. +# can chain several times the same ViewEdge +# in order to produce multiple strokes per ViewEdge. +class pySketchyChainingIterator(ChainingIterator): + def __init__(self, nRounds=3, stayInSelection=True): + ChainingIterator.__init__(self, stayInSelection, False, None, True) + self._timeStamp = CF.get_time_stamp()+nRounds + self._nRounds = nRounds + def init(self): + self._timeStamp = CF.get_time_stamp()+self._nRounds + def traverse(self, iter): + winner = None + it = AdjacencyIterator(iter) + while not it.is_end: + ve = it.object + if ve.id == self.current_edge.id: + it.increment() + continue + winner = ve + it.increment() + if winner is None: + winner = self.current_edge + if winner.chaining_time_stamp == self._timeStamp: + return None + return winner + + +## Chaining iterator that fills small occlusions +## percent +## The max length of the occluded part +## expressed in % of the total chain length +class pyFillOcclusionsRelativeChainingIterator(ChainingIterator): + def __init__(self, percent): + ChainingIterator.__init__(self, False, True, None, True) + self._length = 0 + self._percent = float(percent) + def init(self): + # each time we're evaluating a chain length + # we try to do it once. Thus we reinit + # the chain length here: + self._length = 0 + def traverse(self, iter): + winner = None + winnerOrientation = 0 + print(self.current_edge.id.first, self.current_edge.id.second) + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for nat in natures: + if (self.current_edge.nature & nat) != 0: + count=0 + while not it.is_end: + ve = it.object + if (ve.nature & nat) != 0: + count = count+1 + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + it.increment() + if count != 1: + winner = None + break + if winner is not None: + # check whether this edge was part of the selection + if winner.time_stamp != CF.get_time_stamp(): + #print("---", winner.id.first, winner.id.second) + # if not, let's check whether it's short enough with + # respect to the chain made without staying in the selection + #------------------------------------------------------------ + # Did we compute the prospective chain length already ? + if self._length == 0: + #if not, let's do it + _it = pyChainSilhouetteGenericIterator(0,0) + _it.begin = winner + _it.current_edge = winner + _it.orientation = winnerOrientation + _it.init() + while not _it.is_end: + ve = _it.object + #print("--------", ve.id.first, ve.id.second) + self._length = self._length + ve.length_2d + _it.increment() + if _it.is_begin: + break; + _it.begin = winner + _it.current_edge = winner + _it.orientation = winnerOrientation + if not _it.is_begin: + _it.decrement() + while (not _it.is_end) and (not _it.is_begin): + ve = _it.object + #print("--------", ve.id.first, ve.id.second) + self._length = self._length + ve.length_2d + _it.decrement() + + # let's do the comparison: + # nw let's compute the length of this connex non selected part: + connexl = 0 + _cit = pyChainSilhouetteGenericIterator(0,0) + _cit.begin = winner + _cit.current_edge = winner + _cit.orientation = winnerOrientation + _cit.init() + while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp(): + ve = _cit.object + #print("-------- --------", ve.id.first, ve.id.second) + connexl = connexl + ve.length_2d + _cit.increment() + if connexl > self._percent * self._length: + winner = None + return winner + +## Chaining iterator that fills small occlusions +## size +## The max length of the occluded part +## expressed in pixels +class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator): + def __init__(self, length): + ChainingIterator.__init__(self, False, True, None, True) + self._length = float(length) + def init(self): + pass + def traverse(self, iter): + winner = None + winnerOrientation = 0 + #print(self.current_edge.id.first, self.current_edge.id.second) + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for nat in natures: + if (self.current_edge.nature & nat) != 0: + count=0 + while not it.is_end: + ve = it.object + if (ve.nature & nat) != 0: + count = count+1 + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + it.increment() + if count != 1: + winner = None + break + if winner is not None: + # check whether this edge was part of the selection + if winner.time_stamp != CF.get_time_stamp(): + #print("---", winner.id.first, winner.id.second) + # nw let's compute the length of this connex non selected part: + connexl = 0 + _cit = pyChainSilhouetteGenericIterator(0,0) + _cit.begin = winner + _cit.current_edge = winner + _cit.orientation = winnerOrientation + _cit.init() + while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp(): + ve = _cit.object + #print("-------- --------", ve.id.first, ve.id.second) + connexl = connexl + ve.length_2d + _cit.increment() + if connexl > self._length: + winner = None + return winner + + +## Chaining iterator that fills small occlusions +## percent +## The max length of the occluded part +## expressed in % of the total chain length +class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator): + def __init__(self, percent, l): + ChainingIterator.__init__(self, False, True, None, True) + self._length = 0 + self._absLength = l + self._percent = float(percent) + def init(self): + # each time we're evaluating a chain length + # we try to do it once. Thus we reinit + # the chain length here: + self._length = 0 + def traverse(self, iter): + winner = None + winnerOrientation = 0 + print(self.current_edge.id.first, self.current_edge.id.second) + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for nat in natures: + if (self.current_edge.nature & nat) != 0: + count=0 + while not it.is_end: + ve = it.object + if (ve.nature & nat) != 0: + count = count+1 + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + it.increment() + if count != 1: + winner = None + break + if winner is not None: + # check whether this edge was part of the selection + if winner.time_stamp != CF.get_time_stamp(): + #print("---", winner.id.first, winner.id.second) + # if not, let's check whether it's short enough with + # respect to the chain made without staying in the selection + #------------------------------------------------------------ + # Did we compute the prospective chain length already ? + if self._length == 0: + #if not, let's do it + _it = pyChainSilhouetteGenericIterator(0,0) + _it.begin = winner + _it.current_edge = winner + _it.orientation = winnerOrientation + _it.init() + while not _it.is_end: + ve = _it.object + #print("--------", ve.id.first, ve.id.second) + self._length = self._length + ve.length_2d + _it.increment() + if _it.is_begin: + break; + _it.begin = winner + _it.current_edge = winner + _it.orientation = winnerOrientation + if not _it.is_begin: + _it.decrement() + while (not _it.is_end) and (not _it.is_begin): + ve = _it.object + #print("--------", ve.id.first, ve.id.second) + self._length = self._length + ve.length_2d + _it.decrement() + + # let's do the comparison: + # nw let's compute the length of this connex non selected part: + connexl = 0 + _cit = pyChainSilhouetteGenericIterator(0,0) + _cit.begin = winner + _cit.current_edge = winner + _cit.orientation = winnerOrientation + _cit.init() + while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp(): + ve = _cit.object + #print("-------- --------", ve.id.first, ve.id.second) + connexl = connexl + ve.length_2d + _cit.increment() + if (connexl > self._percent * self._length) or (connexl > self._absLength): + winner = None + return winner + +## Chaining iterator that fills small occlusions without caring about the +## actual selection +## percent +## The max length of the occluded part +## expressed in % of the total chain length +class pyFillQi0AbsoluteAndRelativeChainingIterator(ChainingIterator): + def __init__(self, percent, l): + ChainingIterator.__init__(self, False, True, None, True) + self._length = 0 + self._absLength = l + self._percent = float(percent) + def init(self): + # each time we're evaluating a chain length + # we try to do it once. Thus we reinit + # the chain length here: + self._length = 0 + def traverse(self, iter): + winner = None + winnerOrientation = 0 + print(self.current_edge.id.first, self.current_edge.id.second) + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + if ve.id == mateVE.id: + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for nat in natures: + if (self.current_edge.nature & nat) != 0: + count=0 + while not it.is_end: + ve = it.object + if (ve.nature & nat) != 0: + count = count+1 + winner = ve + if not it.is_incoming: + winnerOrientation = 1 + else: + winnerOrientation = 0 + it.increment() + if count != 1: + winner = None + break + if winner is not None: + # check whether this edge was part of the selection + if winner.qi != 0: + #print("---", winner.id.first, winner.id.second) + # if not, let's check whether it's short enough with + # respect to the chain made without staying in the selection + #------------------------------------------------------------ + # Did we compute the prospective chain length already ? + if self._length == 0: + #if not, let's do it + _it = pyChainSilhouetteGenericIterator(0,0) + _it.begin = winner + _it.current_edge = winner + _it.orientation = winnerOrientation + _it.init() + while not _it.is_end: + ve = _it.object + #print("--------", ve.id.first, ve.id.second) + self._length = self._length + ve.length_2d + _it.increment() + if _it.is_begin: + break; + _it.begin = winner + _it.current_edge = winner + _it.orientation = winnerOrientation + if not _it.is_begin: + _it.decrement() + while (not _it.is_end) and (not _it.is_begin): + ve = _it.object + #print("--------", ve.id.first, ve.id.second) + self._length = self._length + ve.length_2d + _it.decrement() + + # let's do the comparison: + # nw let's compute the length of this connex non selected part: + connexl = 0 + _cit = pyChainSilhouetteGenericIterator(0,0) + _cit.begin = winner + _cit.current_edge = winner + _cit.orientation = winnerOrientation + _cit.init() + while not _cit.is_end and _cit.object.qi != 0: + ve = _cit.object + #print("-------- --------", ve.id.first, ve.id.second) + connexl = connexl + ve.length_2d + _cit.increment() + if (connexl > self._percent * self._length) or (connexl > self._absLength): + winner = None + return winner + + +## the natural chaining iterator +## It follows the edges of same nature on the same +## objects with preseance on silhouettes, then borders, +## then suggestive contours, then everything else. It doesn't chain the same ViewEdge twice +## You can specify whether to stay in the selection or not. +class pyNoIdChainSilhouetteIterator(ChainingIterator): + def __init__(self, stayInSelection=True): + ChainingIterator.__init__(self, stayInSelection, True, None, True) + def init(self): + pass + def traverse(self, iter): + winner = None + it = AdjacencyIterator(iter) + tvertex = self.next_vertex + if type(tvertex) is TVertex: + mateVE = tvertex.get_mate(self.current_edge) + while not it.is_end: + ve = it.object + feB = self.current_edge.last_fedge + feA = ve.first_fedge + vB = feB.second_svertex + vA = feA.first_svertex + if vA.id.first == vB.id.first: + winner = ve + break + feA = self.current_edge.first_fedge + feB = ve.last_fedge + vB = feB.second_svertex + vA = feA.first_svertex + if vA.id.first == vB.id.first: + winner = ve + break + feA = self.current_edge.last_fedge + feB = ve.last_fedge + vB = feB.second_svertex + vA = feA.second_svertex + if vA.id.first == vB.id.first: + winner = ve + break + feA = self.current_edge.first_fedge + feB = ve.first_fedge + vB = feB.first_svertex + vA = feA.first_svertex + if vA.id.first == vB.id.first: + winner = ve + break + it.increment() + else: + ## case of NonTVertex + natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE] + for i in range(len(natures)): + currentNature = self.current_edge.nature + if (natures[i] & currentNature) != 0: + count=0 + while not it.is_end: + visitNext = 0 + oNature = it.object.nature + if (oNature & natures[i]) != 0: + if natures[i] != oNature: + for j in range(i): + if (natures[j] & oNature) != 0: + visitNext = 1 + break + if visitNext != 0: + break + count = count+1 + winner = it.object + it.increment() + if count != 1: + winner = None + break + return winner + diff --git a/release/scripts/freestyle/style_modules/Functions0D.py b/release/scripts/freestyle/style_modules/Functions0D.py new file mode 100644 index 00000000000..b36961f3f91 --- /dev/null +++ b/release/scripts/freestyle/style_modules/Functions0D.py @@ -0,0 +1,105 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : Functions0D.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 30/06/2005 +# Purpose : Functions (functors) to be used for 0D elements + +from Freestyle import Curvature2DAngleF0D, CurvePoint, ReadCompleteViewMapPixelF0D, \ + ReadSteerableViewMapPixelF0D, UnaryFunction0DDouble, UnaryFunction0DMaterial, \ + UnaryFunction0DVec2f +from Freestyle import ContextFunctions as CF + +import math +import mathutils + +class CurveMaterialF0D(UnaryFunction0DMaterial): + # A replacement of the built-in MaterialF0D for stroke creation. + # MaterialF0D does not work with Curves and Strokes. + def __call__(self, inter): + cp = inter.object + assert(isinstance(cp, CurvePoint)) + fe = cp.first_svertex.get_fedge(cp.second_svertex) + assert(fe is not None) + return fe.material if fe.is_smooth else fe.material_left + +class pyInverseCurvature2DAngleF0D(UnaryFunction0DDouble): + def __call__(self, inter): + func = Curvature2DAngleF0D() + c = func(inter) + return (3.1415 - c) + +class pyCurvilinearLengthF0D(UnaryFunction0DDouble): + def __call__(self, inter): + cp = inter.object + assert(isinstance(cp, CurvePoint)) + return cp.t2d + +## estimate anisotropy of density +class pyDensityAnisotropyF0D(UnaryFunction0DDouble): + def __init__(self,level): + UnaryFunction0DDouble.__init__(self) + self.IsoDensity = ReadCompleteViewMapPixelF0D(level) + self.d0Density = ReadSteerableViewMapPixelF0D(0, level) + self.d1Density = ReadSteerableViewMapPixelF0D(1, level) + self.d2Density = ReadSteerableViewMapPixelF0D(2, level) + self.d3Density = ReadSteerableViewMapPixelF0D(3, level) + def __call__(self, inter): + c_iso = self.IsoDensity(inter) + c_0 = self.d0Density(inter) + c_1 = self.d1Density(inter) + c_2 = self.d2Density(inter) + 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 + +## Returns the gradient vector for a pixel +## l +## the level at which one wants to compute the gradient +class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f): + def __init__(self, l): + UnaryFunction0DVec2f.__init__(self) + self._l = l + self._step = math.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)) + return mathutils.Vector([gx, gy]) + +class pyViewMapGradientNormF0D(UnaryFunction0DDouble): + def __init__(self, l): + UnaryFunction0DDouble.__init__(self) + self._l = l + self._step = math.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 = mathutils.Vector([gx, gy]) + return grad.length diff --git a/release/scripts/freestyle/style_modules/Functions1D.py b/release/scripts/freestyle/style_modules/Functions1D.py new file mode 100644 index 00000000000..17b4f1922a6 --- /dev/null +++ b/release/scripts/freestyle/style_modules/Functions1D.py @@ -0,0 +1,58 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : Functions1D.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 08/04/2005 +# Purpose : Functions (functors) to be used for 1D elements + +from Freestyle import GetProjectedZF1D, IntegrationType, UnaryFunction1DDouble, integrate +from Functions0D import pyDensityAnisotropyF0D, pyViewMapGradientNormF0D +import string + +class pyGetInverseProjectedZF1D(UnaryFunction1DDouble): + def __call__(self, inter): + func = GetProjectedZF1D() + z = func(inter) + return (1.0 - z) + +class pyGetSquareInverseProjectedZF1D(UnaryFunction1DDouble): + def __call__(self, inter): + func = GetProjectedZF1D() + z = func(inter) + return (1.0 - z*z) + +class pyDensityAnisotropyF1D(UnaryFunction1DDouble): + def __init__(self,level, integrationType=IntegrationType.MEAN, sampling=2.0): + UnaryFunction1DDouble.__init__(self, integrationType) + self._func = pyDensityAnisotropyF0D(level) + self._integration = integrationType + self._sampling = sampling + def __call__(self, inter): + v = integrate(self._func, inter.pointsBegin(self._sampling), inter.pointsEnd(self._sampling), self._integration) + return v + +class pyViewMapGradientNormF1D(UnaryFunction1DDouble): + def __init__(self,l, integrationType, sampling=2.0): + UnaryFunction1DDouble.__init__(self, integrationType) + self._func = pyViewMapGradientNormF0D(l) + self._integration = integrationType + self._sampling = sampling + def __call__(self, inter): + v = integrate(self._func, inter.pointsBegin(self._sampling), inter.pointsEnd(self._sampling), self._integration) + return v diff --git a/release/scripts/freestyle/style_modules/PredicatesB1D.py b/release/scripts/freestyle/style_modules/PredicatesB1D.py new file mode 100644 index 00000000000..642ff5f9845 --- /dev/null +++ b/release/scripts/freestyle/style_modules/PredicatesB1D.py @@ -0,0 +1,73 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : PredicatesB1D.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 08/04/2005 +# Purpose : Binary predicates (functors) to be used for 1D elements + +from Freestyle import BinaryPredicate1D, GetZF1D, IntegrationType, Nature, SameShapeIdBP1D, ZDiscontinuityF1D +from Functions1D import pyViewMapGradientNormF1D + +import random + +class pyZBP1D(BinaryPredicate1D): + def __call__(self, i1, i2): + func = GetZF1D() + return (func(i1) > func(i2)) + +class pyZDiscontinuityBP1D(BinaryPredicate1D): + def __init__(self, iType = IntegrationType.MEAN): + BinaryPredicate1D.__init__(self) + self._GetZDiscontinuity = ZDiscontinuityF1D(iType) + def __call__(self, i1, i2): + return (self._GetZDiscontinuity(i1) > self._GetZDiscontinuity(i2)) + +class pyLengthBP1D(BinaryPredicate1D): + def __call__(self, i1, i2): + return (i1.length_2d > i2.length_2d) + +class pySilhouetteFirstBP1D(BinaryPredicate1D): + def __call__(self, inter1, inter2): + bpred = SameShapeIdBP1D() + if (bpred(inter1, inter2) != 1): + return 0 + if (inter1.nature & Nature.SILHOUETTE): + return (inter2.nature & Nature.SILHOUETTE) != 0 + return (inter1.nature == inter2.nature) + +class pyNatureBP1D(BinaryPredicate1D): + def __call__(self, inter1, inter2): + return (inter1.nature & inter2.nature) + +class pyViewMapGradientNormBP1D(BinaryPredicate1D): + def __init__(self,l, sampling=2.0): + BinaryPredicate1D.__init__(self) + 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) + def __call__(self, inter1, inter2): + r1 = random.uniform(0,1) + r2 = random.uniform(0,1) + return (r1<r2) diff --git a/release/scripts/freestyle/style_modules/PredicatesU0D.py b/release/scripts/freestyle/style_modules/PredicatesU0D.py new file mode 100644 index 00000000000..49675eb3c6a --- /dev/null +++ b/release/scripts/freestyle/style_modules/PredicatesU0D.py @@ -0,0 +1,96 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : PredicatesU0D.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 08/04/2005 +# Purpose : Unary predicates (functors) to be used for 0D elements + +from Freestyle import Curvature2DAngleF0D, Nature, QuantitativeInvisibilityF0D, UnaryPredicate0D +from Functions0D import pyCurvilinearLengthF0D + +class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D): + def __init__(self,a): + UnaryPredicate0D.__init__(self) + self._a = a + def __call__(self, inter): + func = Curvature2DAngleF0D() + a = func(inter) + return (a > self._a) + +class pyUEqualsUP0D(UnaryPredicate0D): + def __init__(self,u, w): + UnaryPredicate0D.__init__(self) + self._u = u + self._w = w + def __call__(self, inter): + func = pyCurvilinearLengthF0D() + u = func(inter) + return (u > (self._u-self._w)) and (u < (self._u+self._w)) + +class pyVertexNatureUP0D(UnaryPredicate0D): + def __init__(self,nature): + UnaryPredicate0D.__init__(self) + self._nature = nature + def __call__(self, inter): + v = inter.object + return (v.nature & self._nature) != 0 + +## check whether an Interface0DIterator +## is a TVertex and is the one that is +## hidden (inferred from the context) +class pyBackTVertexUP0D(UnaryPredicate0D): + def __init__(self): + UnaryPredicate0D.__init__(self) + self._getQI = QuantitativeInvisibilityF0D() + def __call__(self, iter): + if (iter.object.nature & Nature.T_VERTEX) == 0: + return 0 + if iter.is_end: + return 0 + if self._getQI(iter) != 0: + return 1 + return 0 + +class pyParameterUP0DGoodOne(UnaryPredicate0D): + def __init__(self,pmin,pmax): + UnaryPredicate0D.__init__(self) + self._m = pmin + self._M = pmax + #self.getCurvilinearAbscissa = GetCurvilinearAbscissaF0D() + def __call__(self, inter): + #s = self.getCurvilinearAbscissa(inter) + u = inter.u + #print(u) + return ((u>=self._m) and (u<=self._M)) + +class pyParameterUP0D(UnaryPredicate0D): + def __init__(self,pmin,pmax): + UnaryPredicate0D.__init__(self) + self._m = pmin + self._M = pmax + #self.getCurvilinearAbscissa = GetCurvilinearAbscissaF0D() + def __call__(self, inter): + func = Curvature2DAngleF0D() + c = func(inter) + b1 = (c>0.1) + #s = self.getCurvilinearAbscissa(inter) + u = inter.u + #print(u) + b = ((u>=self._m) and (u<=self._M)) + return b and b1 diff --git a/release/scripts/freestyle/style_modules/PredicatesU1D.py b/release/scripts/freestyle/style_modules/PredicatesU1D.py new file mode 100644 index 00000000000..5c48219e9f4 --- /dev/null +++ b/release/scripts/freestyle/style_modules/PredicatesU1D.py @@ -0,0 +1,342 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : PredicatesU1D.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 08/04/2005 +# Purpose : Unary predicates (functors) to be used for 1D elements + +from Freestyle import Curvature2DAngleF0D, CurveNatureF1D, DensityF1D, GetCompleteViewMapDensityF1D, \ + GetDirectionalViewMapDensityF1D, GetOccludersF1D, GetProjectedZF1D, GetShapeF1D, GetSteerableViewMapDensityF1D, \ + IntegrationType, ShapeUP1D, TVertex, UnaryPredicate1D +from Functions1D import pyDensityAnisotropyF1D, pyViewMapGradientNormF1D + +class pyNFirstUP1D(UnaryPredicate1D): + def __init__(self, n): + UnaryPredicate1D.__init__(self) + self.__n = n + self.__count = 0 + def __call__(self, inter): + self.__count = self.__count + 1 + if self.__count <= self.__n: + return 1 + return 0 + +class pyHigherLengthUP1D(UnaryPredicate1D): + def __init__(self,l): + UnaryPredicate1D.__init__(self) + self._l = l + def __call__(self, inter): + return (inter.length_2d > self._l) + +class pyNatureUP1D(UnaryPredicate1D): + def __init__(self,nature): + UnaryPredicate1D.__init__(self) + self._nature = nature + self._getNature = CurveNatureF1D() + def __call__(self, inter): + if(self._getNature(inter) & self._nature): + return 1 + return 0 + +class pyHigherNumberOfTurnsUP1D(UnaryPredicate1D): + 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 1 + it.increment() + return 0 + +class pyDensityUP1D(UnaryPredicate1D): + def __init__(self,wsize,threshold, integration = IntegrationType.MEAN, sampling=2.0): + UnaryPredicate1D.__init__(self) + self._wsize = wsize + self._threshold = threshold + self._integration = integration + self._func = DensityF1D(self._wsize, self._integration, sampling) + def __call__(self, inter): + if self._func(inter) < self._threshold: + return 1 + return 0 + +class pyLowSteerableViewMapDensityUP1D(UnaryPredicate1D): + def __init__(self,threshold, level,integration = IntegrationType.MEAN): + UnaryPredicate1D.__init__(self) + self._threshold = threshold + self._level = level + self._integration = integration + def __call__(self, inter): + func = GetSteerableViewMapDensityF1D(self._level, self._integration) + v = func(inter) + print(v) + if v < self._threshold: + return 1 + return 0 + +class pyLowDirectionalViewMapDensityUP1D(UnaryPredicate1D): + def __init__(self,threshold, orientation, level,integration = IntegrationType.MEAN): + UnaryPredicate1D.__init__(self) + self._threshold = threshold + self._orientation = orientation + self._level = level + self._integration = integration + def __call__(self, inter): + func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration) + v = func(inter) + #print(v) + if v < self._threshold: + return 1 + return 0 + +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) + def __call__(self, inter): + v = self._func(inter) + if v > self._threshold: + return 1 + return 0 + +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 + def __call__(self, inter): + func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration, self._sampling) + v = func(inter) + if v > self._threshold: + return 1 + return 0 + +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 + def __call__(self, inter): + #print("toto") + #print(func.name) + #print(inter.name) + v= self._func(inter) + if v > self._threshold: + return 1 + return 0 + +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 + def __call__(self, inter): + func = DensityF1D(self._wsize, self._integration) + res = self._functor(inter) + k = (res-self._funcmin)/(self._funcmax-self._funcmin) + if func(inter) < self._threshold*k: + return 1 + return 0 + +class pyZSmallerUP1D(UnaryPredicate1D): + def __init__(self,z, integration=IntegrationType.MEAN): + UnaryPredicate1D.__init__(self) + self._z = z + self._integration = integration + def __call__(self, inter): + func = GetProjectedZF1D(self._integration) + if func(inter) < self._z: + return 1 + return 0 + +class pyIsOccludedByUP1D(UnaryPredicate1D): + def __init__(self,id): + UnaryPredicate1D.__init__(self) + self._id = id + def __call__(self, inter): + func = GetShapeF1D() + shapes = func(inter) + for s in shapes: + if(s.id == self._id): + return 0 + 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 1 + 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," ]") + eit = tvertex.edges_begin() + while not eit.is_end: + ve, incoming = eit.object + if ve.id == self._id: + return 1 + print("-------", ve.id.first, "-", ve.id.second) + eit.increment() + return 0 + +class pyIsInOccludersListUP1D(UnaryPredicate1D): + def __init__(self,id): + UnaryPredicate1D.__init__(self) + self._id = id + def __call__(self, inter): + func = GetOccludersF1D() + occluders = func(inter) + for a in occluders: + if a.id == self._id: + return 1 + return 0 + +class pyIsOccludedByItselfUP1D(UnaryPredicate1D): + def __init__(self): + UnaryPredicate1D.__init__(self) + self.__func1 = GetOccludersF1D() + self.__func2 = GetShapeF1D() + 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 1 + return 0 + +class pyIsOccludedByIdListUP1D(UnaryPredicate1D): + def __init__(self, idlist): + UnaryPredicate1D.__init__(self) + self._idlist = idlist + 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 1 + return 0 + +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)) + def __call__(self, inter): + for func in self._funcs : + if func(inter) == 1: + return 1 + return 0 + +## deprecated +class pyShapeIdUP1D(UnaryPredicate1D): + def __init__(self, _id): + UnaryPredicate1D.__init__(self) + self._id = _id + def __call__(self, inter): + func = GetShapeF1D() + shapes = func(inter) + for a in shapes: + if a.id == self._id: + return 1 + return 0 + +class pyHighDensityAnisotropyUP1D(UnaryPredicate1D): + def __init__(self,threshold, level, sampling=2.0): + UnaryPredicate1D.__init__(self) + self._l = threshold + self.func = pyDensityAnisotropyF1D(level, IntegrationType.MEAN, sampling) + def __call__(self, inter): + return (self.func(inter) > self._l) + +class pyHighViewMapGradientNormUP1D(UnaryPredicate1D): + def __init__(self,threshold, l, sampling=2.0): + UnaryPredicate1D.__init__(self) + self._threshold = threshold + self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN) + def __call__(self, inter): + gn = self._GetGradient(inter) + #print(gn) + return (gn > self._threshold) + +class pyDensityVariableSigmaUP1D(UnaryPredicate1D): + def __init__(self,functor, sigmaMin,sigmaMax, lmin, lmax, tmin, tmax, integration = IntegrationType.MEAN, sampling=2.0): + UnaryPredicate1D.__init__(self) + self._functor = functor + self._sigmaMin = float(sigmaMin) + self._sigmaMax = float(sigmaMax) + self._lmin = float(lmin) + self._lmax = float(lmax) + self._tmin = tmin + self._tmax = tmax + self._integration = integration + self._sampling = sampling + def __call__(self, inter): + sigma = (self._sigmaMax-self._sigmaMin)/(self._lmax-self._lmin)*(self._functor(inter)-self._lmin) + self._sigmaMin + t = (self._tmax-self._tmin)/(self._lmax-self._lmin)*(self._functor(inter)-self._lmin) + self._tmin + if sigma < self._sigmaMin: + sigma = self._sigmaMin + self._func = DensityF1D(sigma, self._integration, self._sampling) + d = self._func(inter) + if d < t: + return 1 + return 0 + +class pyClosedCurveUP1D(UnaryPredicate1D): + def __call__(self, inter): + 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 1 + return 0 diff --git a/release/scripts/freestyle/style_modules/anisotropic_diffusion.py b/release/scripts/freestyle/style_modules/anisotropic_diffusion.py new file mode 100644 index 00000000000..bc1654475ac --- /dev/null +++ b/release/scripts/freestyle/style_modules/anisotropic_diffusion.py @@ -0,0 +1,44 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : anisotropic_diffusion.py +# Author : Fredo Durand +# Date : 12/08/2004 +# Purpose : Smoothes lines using an anisotropic diffusion scheme + +from Freestyle import ChainPredicateIterator, ConstantThicknessShader, ExternalContourUP1D, IncreasingColorShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, Stroke, StrokeTextureShader, TrueBP1D, TrueUP1D +from logical_operators import AndUP1D, NotUP1D +from shaders import pyDiffusion2Shader + +# pyDiffusion2Shader parameters +offset = 0.25 +nbIter = 30 + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ExternalContourUP1D()) +Operators.select(upred) +bpred = TrueBP1D() +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(upred)) +shaders_list = [ + ConstantThicknessShader(4), + StrokeTextureShader("smoothAlpha.bmp", Stroke.OPAQUE_MEDIUM, False), + SamplingShader(2), + pyDiffusion2Shader(offset, nbIter), + IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/apriori_and_causal_density.py b/release/scripts/freestyle/style_modules/apriori_and_causal_density.py new file mode 100644 index 00000000000..8284f7308f8 --- /dev/null +++ b/release/scripts/freestyle/style_modules/apriori_and_causal_density.py @@ -0,0 +1,39 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : apriori_and_causal_density.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Selects the lines with high a priori density and +# subjects them to the causal density so as to avoid +# cluttering + +from Freestyle import ChainPredicateIterator, ConstantColorShader, ConstantThicknessShader, IntegrationType, \ + Operators, QuantitativeInvisibilityUP1D, TrueBP1D +from PredicatesU1D import pyDensityUP1D, pyHighViewMapDensityUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), pyHighViewMapDensityUP1D(0.3, IntegrationType.LAST)) +Operators.select(upred) +bpred = TrueBP1D() +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + ConstantThicknessShader(2), + ConstantColorShader(0, 0, 0, 1), + ] +Operators.create(pyDensityUP1D(1, 0.1, IntegrationType.MEAN), shaders_list) diff --git a/release/scripts/freestyle/style_modules/apriori_density.py b/release/scripts/freestyle/style_modules/apriori_density.py new file mode 100644 index 00000000000..575f2b92a25 --- /dev/null +++ b/release/scripts/freestyle/style_modules/apriori_density.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : apriori_density.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws lines having a high a priori density + +from Freestyle import ChainPredicateIterator, ConstantColorShader, ConstantThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, TrueBP1D, TrueUP1D +from PredicatesU1D import pyHighViewMapDensityUP1D +from logical_operators import AndUP1D, NotUP1D + +Operators.select(AndUP1D(QuantitativeInvisibilityUP1D(0), pyHighViewMapDensityUP1D(0.1,5))) +bpred = TrueBP1D() +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), pyHighViewMapDensityUP1D(0.0007,5)) +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + ConstantThicknessShader(2), + ConstantColorShader(0.0, 0.0, 0.0,1) + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/backbone_stretcher.py b/release/scripts/freestyle/style_modules/backbone_stretcher.py new file mode 100644 index 00000000000..1e113bec368 --- /dev/null +++ b/release/scripts/freestyle/style_modules/backbone_stretcher.py @@ -0,0 +1,35 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : backbone_stretcher.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Stretches the geometry of visible lines + +from Freestyle import BackboneStretcherShader, ChainSilhouetteIterator, ConstantColorShader, \ + Operators, QuantitativeInvisibilityUP1D, TextureAssignerShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + TextureAssignerShader(4), + ConstantColorShader(0.5, 0.5, 0.5), + BackboneStretcherShader(20), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/blueprint_circles.py b/release/scripts/freestyle/style_modules/blueprint_circles.py new file mode 100644 index 00000000000..769615e791f --- /dev/null +++ b/release/scripts/freestyle/style_modules/blueprint_circles.py @@ -0,0 +1,42 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : blueprint_circles.py +# Author : Emmanuel Turquin +# Date : 04/08/2005 +# Purpose : Produces a blueprint using circular contour strokes + +from Freestyle import ChainPredicateIterator, ConstantThicknessShader, ContourUP1D, IncreasingColorShader, \ + Operators, QuantitativeInvisibilityUP1D, SameShapeIdBP1D, TextureAssignerShader, TrueUP1D +from PredicatesU1D import pyHigherLengthUP1D +from logical_operators import AndUP1D, NotUP1D +from shaders import pyBluePrintCirclesShader, pyPerlinNoise1DShader + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D()) +bpred = SameShapeIdBP1D() +Operators.select(upred) +Operators.bidirectional_chain(ChainPredicateIterator(upred,bpred), NotUP1D(upred)) +Operators.select(pyHigherLengthUP1D(200)) +shaders_list = [ + ConstantThicknessShader(5), + pyBluePrintCirclesShader(3), + pyPerlinNoise1DShader(0.1, 15, 8), + TextureAssignerShader(4), + IncreasingColorShader(0.8, 0.8, 0.3, 0.4, 0.3, 0.3, 0.3, 0.1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/blueprint_ellipses.py b/release/scripts/freestyle/style_modules/blueprint_ellipses.py new file mode 100644 index 00000000000..70c999fa2c7 --- /dev/null +++ b/release/scripts/freestyle/style_modules/blueprint_ellipses.py @@ -0,0 +1,42 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : blueprint_ellipses.py +# Author : Emmanuel Turquin +# Date : 04/08/2005 +# Purpose : Produces a blueprint using elliptic contour strokes + +from Freestyle import ChainPredicateIterator, ConstantThicknessShader, ContourUP1D, IncreasingColorShader, \ + Operators, QuantitativeInvisibilityUP1D, SameShapeIdBP1D, TextureAssignerShader, TrueUP1D +from PredicatesU1D import pyHigherLengthUP1D +from logical_operators import AndUP1D, NotUP1D +from shaders import pyBluePrintEllipsesShader, pyPerlinNoise1DShader + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D()) +bpred = SameShapeIdBP1D() +Operators.select(upred) +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(upred)) +Operators.select(pyHigherLengthUP1D(200)) +shaders_list = [ + ConstantThicknessShader(5), + pyBluePrintEllipsesShader(3), + pyPerlinNoise1DShader(0.1, 10, 8), + TextureAssignerShader(4), + IncreasingColorShader(0.6, 0.3, 0.3, 0.7, 0.3, 0.3, 0.3, 0.1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/blueprint_squares.py b/release/scripts/freestyle/style_modules/blueprint_squares.py new file mode 100644 index 00000000000..7193beb8de5 --- /dev/null +++ b/release/scripts/freestyle/style_modules/blueprint_squares.py @@ -0,0 +1,43 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : blueprint_squares.py +# Author : Emmanuel Turquin +# Date : 04/08/2005 +# Purpose : Produces a blueprint using square contour strokes + +from Freestyle import ChainPredicateIterator, ConstantThicknessShader, ContourUP1D, IncreasingColorShader, \ + Operators, QuantitativeInvisibilityUP1D, SameShapeIdBP1D, TextureAssignerShader, TrueUP1D +from PredicatesU1D import pyHigherLengthUP1D +from logical_operators import AndUP1D, NotUP1D +from shaders import pyBluePrintSquaresShader, pyPerlinNoise1DShader + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D()) +bpred = SameShapeIdBP1D() +Operators.select(upred) +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(upred)) +Operators.select(pyHigherLengthUP1D(200)) +shaders_list = [ + ConstantThicknessShader(8), + pyBluePrintSquaresShader(2, 20), + pyPerlinNoise1DShader(0.07, 10, 8), + TextureAssignerShader(4), + IncreasingColorShader(0.6, 0.3, 0.3, 0.7, 0.6, 0.3, 0.3, 0.3), + ConstantThicknessShader(4), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/cartoon.py b/release/scripts/freestyle/style_modules/cartoon.py new file mode 100644 index 00000000000..61bc11520a1 --- /dev/null +++ b/release/scripts/freestyle/style_modules/cartoon.py @@ -0,0 +1,38 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : cartoon.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws colored lines. The color is automatically +# infered from each object's material in a cartoon-like +# fashion. + +from Freestyle import BezierCurveShader, ChainSilhouetteIterator, ConstantThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, TrueUP1D +from logical_operators import NotUP1D +from shaders import pyMaterialColorShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + BezierCurveShader(3), + ConstantThicknessShader(4), + pyMaterialColorShader(0.8), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/contour.py b/release/scripts/freestyle/style_modules/contour.py new file mode 100644 index 00000000000..e4eb8c2c3ae --- /dev/null +++ b/release/scripts/freestyle/style_modules/contour.py @@ -0,0 +1,36 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : contour.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws each object's visible contour + +from Freestyle import ChainPredicateIterator, ConstantThicknessShader, ContourUP1D, IncreasingColorShader, \ + Operators, QuantitativeInvisibilityUP1D, SameShapeIdBP1D, TrueUP1D +from logical_operators import AndUP1D, NotUP1D + +Operators.select(AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D())) +bpred = SameShapeIdBP1D() +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D()) +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + ConstantThicknessShader(5.0), + IncreasingColorShader(0.8,0,0,1,0.1,0,0,1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/curvature2d.py b/release/scripts/freestyle/style_modules/curvature2d.py new file mode 100644 index 00000000000..57bc8382220 --- /dev/null +++ b/release/scripts/freestyle/style_modules/curvature2d.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : curvature2d.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : The stroke points are colored in gray levels and depending +# on the 2d curvature value + +from Freestyle import ChainSilhouetteIterator, ConstantThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, Stroke, StrokeTextureShader, TrueUP1D +from logical_operators import NotUP1D +from shaders import py2DCurvatureColorShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + StrokeTextureShader("smoothAlpha.bmp", Stroke.OPAQUE_MEDIUM, False), + ConstantThicknessShader(5), + py2DCurvatureColorShader() + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/external_contour.py b/release/scripts/freestyle/style_modules/external_contour.py new file mode 100644 index 00000000000..28bd28f2a7a --- /dev/null +++ b/release/scripts/freestyle/style_modules/external_contour.py @@ -0,0 +1,36 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : external_contour.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the external contour of the scene + +from Freestyle import ChainPredicateIterator, ConstantColorShader, ConstantThicknessShader, \ + ExternalContourUP1D, Operators, QuantitativeInvisibilityUP1D, TrueBP1D, TrueUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ExternalContourUP1D()) +Operators.select(upred) +bpred = TrueBP1D() +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(upred)) +shaders_list = [ + ConstantThicknessShader(3), + ConstantColorShader(0.0, 0.0, 0.0, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/external_contour_sketchy.py b/release/scripts/freestyle/style_modules/external_contour_sketchy.py new file mode 100644 index 00000000000..d85e9674e90 --- /dev/null +++ b/release/scripts/freestyle/style_modules/external_contour_sketchy.py @@ -0,0 +1,43 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : external_contour_sketchy.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the external contour of the scene using a sketchy +# chaining iterator (in particular each ViewEdge can be drawn +# several times + +from ChainingIterators import pySketchyChainingIterator +from Freestyle import ExternalContourUP1D, IncreasingColorShader, IncreasingThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, SmoothingShader, SpatialNoiseShader, \ + TextureAssignerShader, TrueUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ExternalContourUP1D()) +Operators.select(upred) +Operators.bidirectional_chain(pySketchyChainingIterator(), NotUP1D(upred)) +shaders_list = [ + SamplingShader(4), + SpatialNoiseShader(10, 150, 2, 1, 1), + IncreasingThicknessShader(4, 10), + SmoothingShader(400, 0.1, 0, 0.2, 0, 0, 0, 1), + IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + TextureAssignerShader(4), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/external_contour_smooth.py b/release/scripts/freestyle/style_modules/external_contour_smooth.py new file mode 100644 index 00000000000..f42f3bf3338 --- /dev/null +++ b/release/scripts/freestyle/style_modules/external_contour_smooth.py @@ -0,0 +1,39 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : external_contour_smooth.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws a smooth external contour + +from Freestyle import ChainPredicateIterator, ExternalContourUP1D, IncreasingColorShader, \ + IncreasingThicknessShader, Operators, QuantitativeInvisibilityUP1D, SamplingShader, \ + SmoothingShader, TrueBP1D, TrueUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ExternalContourUP1D()) +Operators.select(upred) +bpred = TrueBP1D() +Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(upred)) +shaders_list = [ + SamplingShader(2), + IncreasingThicknessShader(4,20), + IncreasingColorShader(1.0, 0.0, 0.5,1, 0.5,1, 0.3, 1), + SmoothingShader(100, 0.05, 0, 0.2, 0, 0, 0, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/haloing.py b/release/scripts/freestyle/style_modules/haloing.py new file mode 100644 index 00000000000..ee907ec967f --- /dev/null +++ b/release/scripts/freestyle/style_modules/haloing.py @@ -0,0 +1,46 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : haloing.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : This style module selects the lines that +# are connected (in the image) to a specific +# object and trims them in order to produce +# a haloing effect around the target shape + +from Freestyle import ChainSilhouetteIterator, Id, IncreasingColorShader, IncreasingThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TipRemoverShader, TrueUP1D +from PredicatesU1D import pyIsOccludedByUP1D +from logical_operators import AndUP1D, NotUP1D +from shaders import pyTVertexRemoverShader + +# id corresponds to the id of the target object +# (accessed by SHIFT+click) +id = Id(3,0) +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), pyIsOccludedByUP1D(id)) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +shaders_list = [ + IncreasingThicknessShader(3, 5), + IncreasingColorShader(1,0,0, 1,0,1,0,1), + SamplingShader(1.0), + pyTVertexRemoverShader(), + TipRemoverShader(3.0), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/ignore_small_occlusions.py b/release/scripts/freestyle/style_modules/ignore_small_occlusions.py new file mode 100644 index 00000000000..aeab1b2e731 --- /dev/null +++ b/release/scripts/freestyle/style_modules/ignore_small_occlusions.py @@ -0,0 +1,36 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : ignore_small_oclusions.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : The strokes are drawn through small occlusions + +from ChainingIterators import pyFillOcclusionsAbsoluteChainingIterator +from Freestyle import ConstantColorShader, ConstantThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +#Operators.bidirectional_chain(pyFillOcclusionsChainingIterator(0.1)) +Operators.bidirectional_chain(pyFillOcclusionsAbsoluteChainingIterator(12)) +shaders_list = [ + SamplingShader(5.0), + ConstantThicknessShader(3), + ConstantColorShader(0.0, 0.0, 0.0), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/invisible_lines.py b/release/scripts/freestyle/style_modules/invisible_lines.py new file mode 100644 index 00000000000..6a72534d0ac --- /dev/null +++ b/release/scripts/freestyle/style_modules/invisible_lines.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : invisible_lines.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws all lines whose Quantitative Invisibility +# is different from 0 + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D + +upred = NotUP1D(QuantitativeInvisibilityUP1D(0)) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +shaders_list = [ + SamplingShader(5.0), + ConstantThicknessShader(3.0), + ConstantColorShader(0.7, 0.7, 0.7), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/japanese_bigbrush.py b/release/scripts/freestyle/style_modules/japanese_bigbrush.py new file mode 100644 index 00000000000..ca421536c42 --- /dev/null +++ b/release/scripts/freestyle/style_modules/japanese_bigbrush.py @@ -0,0 +1,56 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : japanese_bigbrush.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Simulates a big brush fr oriental painting + +from Freestyle import BezierCurveShader, ChainSilhouetteIterator, ConstantColorShader, \ + ConstantThicknessShader, IntegrationType, Operators, QuantitativeInvisibilityUP1D, \ + SamplingShader, TextureAssignerShader, TipRemoverShader +from Functions0D import pyInverseCurvature2DAngleF0D +from PredicatesB1D import pyLengthBP1D +from PredicatesU0D import pyParameterUP0D +from PredicatesU1D import pyDensityUP1D, pyHigherLengthUP1D, pyHigherNumberOfTurnsUP1D +from logical_operators import NotUP1D +from shaders import pyNonLinearVaryingThicknessShader, pySamplingShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +## Splits strokes at points of highest 2D curavture +## when there are too many abrupt turns in it +func = pyInverseCurvature2DAngleF0D() +Operators.recursive_split(func, pyParameterUP0D(0.2, 0.8), NotUP1D(pyHigherNumberOfTurnsUP1D(3, 0.5)), 2) +## Keeps only long enough strokes +Operators.select(pyHigherLengthUP1D(100)) +## Sorts so as to draw the longest strokes first +## (this will be done using the causal density) +Operators.sort(pyLengthBP1D()) +shaders_list = [ + pySamplingShader(10), + BezierCurveShader(30), + SamplingShader(50), + ConstantThicknessShader(10), + pyNonLinearVaryingThicknessShader(4, 25, 0.6), + TextureAssignerShader(6), + ConstantColorShader(0.2, 0.2, 0.2,1.0), + TipRemoverShader(10), + ] +## Use the causal density to avoid cluttering +Operators.create(pyDensityUP1D(8, 0.4, IntegrationType.MEAN), shaders_list) diff --git a/release/scripts/freestyle/style_modules/logical_operators.py b/release/scripts/freestyle/style_modules/logical_operators.py new file mode 100644 index 00000000000..cffb9e1cc58 --- /dev/null +++ b/release/scripts/freestyle/style_modules/logical_operators.py @@ -0,0 +1,47 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : logical_operators.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 08/04/2005 +# Purpose : Logical unary predicates (functors) for 1D elements + +from Freestyle import UnaryPredicate1D + +class AndUP1D(UnaryPredicate1D): + def __init__(self, pred1, pred2): + UnaryPredicate1D.__init__(self) + self.__pred1 = pred1 + self.__pred2 = pred2 + def __call__(self, inter): + return self.__pred1(inter) and self.__pred2(inter) + +class OrUP1D(UnaryPredicate1D): + def __init__(self, pred1, pred2): + UnaryPredicate1D.__init__(self) + self.__pred1 = pred1 + self.__pred2 = pred2 + def __call__(self, inter): + return self.__pred1(inter) or self.__pred2(inter) + +class NotUP1D(UnaryPredicate1D): + def __init__(self, pred): + UnaryPredicate1D.__init__(self) + self.__pred = pred + def __call__(self, inter): + return not self.__pred(inter) diff --git a/release/scripts/freestyle/style_modules/long_anisotropically_dense.py b/release/scripts/freestyle/style_modules/long_anisotropically_dense.py new file mode 100644 index 00000000000..90fc84c5dae --- /dev/null +++ b/release/scripts/freestyle/style_modules/long_anisotropically_dense.py @@ -0,0 +1,68 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : long_anisotropically_dense.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Selects the lines that are long and have a high anisotropic +# a priori density and uses causal density +# to draw without cluttering. Ideally, half of the +# selected lines are culled using the causal density. +# +# ********************* WARNING ************************************* +# ******** The Directional a priori density maps must ****** +# ******** have been computed prior to using this style module ****** + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, DensityF1D, \ + IntegrationType, Operators, QuantitativeInvisibilityUP1D, SamplingShader, UnaryPredicate1D +from PredicatesB1D import pyLengthBP1D +from PredicatesU1D import pyHighDensityAnisotropyUP1D, pyHigherLengthUP1D +from logical_operators import NotUP1D + +## custom density predicate +class pyDensityUP1D(UnaryPredicate1D): + def __init__(self, wsize, threshold, integration=IntegrationType.MEAN, sampling=2.0): + UnaryPredicate1D.__init__(self) + self._wsize = wsize + self._threshold = threshold + self._integration = integration + self._func = DensityF1D(self._wsize, self._integration, sampling) + self._func2 = DensityF1D(self._wsize, IntegrationType.MAX, sampling) + def __call__(self, inter): + c = self._func(inter) + m = self._func2(inter) + if c < self._threshold: + return 1 + if m > 4*c: + if c < 1.5*self._threshold: + return 1 + return 0 + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(),NotUP1D(QuantitativeInvisibilityUP1D(0))) +Operators.select(pyHigherLengthUP1D(40)) +## selects lines having a high anisotropic a priori density +Operators.select(pyHighDensityAnisotropyUP1D(0.3,4)) +Operators.sort(pyLengthBP1D()) +shaders_list = [ + SamplingShader(2.0), + ConstantThicknessShader(2), + ConstantColorShader(0.2,0.2,0.25,1), + ] +## uniform culling +Operators.create(pyDensityUP1D(3.0,2.0e-2, IntegrationType.MEAN, 0.1), shaders_list) diff --git a/release/scripts/freestyle/style_modules/multiple_parameterization.py b/release/scripts/freestyle/style_modules/multiple_parameterization.py new file mode 100644 index 00000000000..2296a9735ce --- /dev/null +++ b/release/scripts/freestyle/style_modules/multiple_parameterization.py @@ -0,0 +1,47 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : multiple_parameterization.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : The thickness and the color of the strokes vary continuously +# independently from occlusions although only +# visible lines are actually drawn. This is equivalent +# to assigning the thickness using a parameterization covering +# the complete silhouette (visible+invisible) and drawing +# the strokes using a second parameterization that only +# covers the visible portions. + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, IncreasingColorShader, \ + IncreasingThicknessShader, Operators, QuantitativeInvisibilityUP1D, SamplingShader, \ + TextureAssignerShader, TrueUP1D +from shaders import pyHLRShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +## Chain following the same nature, but without the restriction +## of staying inside the selection (0). +Operators.bidirectional_chain(ChainSilhouetteIterator(0)) +shaders_list = [ + SamplingShader(20), + IncreasingThicknessShader(1.5, 30), + ConstantColorShader(0.0, 0.0, 0.0), + IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + TextureAssignerShader(-1), + pyHLRShader(), ## this shader draws only visible portions + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/nature.py b/release/scripts/freestyle/style_modules/nature.py new file mode 100644 index 00000000000..dd520c5c85f --- /dev/null +++ b/release/scripts/freestyle/style_modules/nature.py @@ -0,0 +1,39 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : nature.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Uses the NatureUP1D predicate to select the lines +# of a given type (among Nature.SILHOUETTE, Nature.CREASE, Nature.SUGGESTIVE_CONTOURS, +# Nature.BORDERS). +# The suggestive contours must have been enabled in the +# options dialog to appear in the View Map. + +from Freestyle import ChainSilhouetteIterator, IncreasingColorShader, \ + IncreasingThicknessShader, Nature, Operators, TrueUP1D +from PredicatesU1D import pyNatureUP1D +from logical_operators import NotUP1D + +Operators.select(pyNatureUP1D(Nature.SILHOUETTE)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(pyNatureUP1D(Nature.SILHOUETTE))) +shaders_list = [ + IncreasingThicknessShader(3, 10), + IncreasingColorShader(0.0, 0.0, 0.0, 1, 0.8, 0, 0, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/near_lines.py b/release/scripts/freestyle/style_modules/near_lines.py new file mode 100644 index 00000000000..d2dab89d478 --- /dev/null +++ b/release/scripts/freestyle/style_modules/near_lines.py @@ -0,0 +1,38 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : near_lines.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the lines that are "closer" than a threshold +# (between 0 and 1) + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + IntegrationType, Operators, QuantitativeInvisibilityUP1D, TextureAssignerShader, TrueUP1D +from PredicatesU1D import pyZSmallerUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), pyZSmallerUP1D(0.5, IntegrationType.MEAN)) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +shaders_list = [ + TextureAssignerShader(-1), + ConstantThicknessShader(5), + ConstantColorShader(0.0, 0.0, 0.0), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/occluded_by_specific_object.py b/release/scripts/freestyle/style_modules/occluded_by_specific_object.py new file mode 100644 index 00000000000..4f8c0e859ed --- /dev/null +++ b/release/scripts/freestyle/style_modules/occluded_by_specific_object.py @@ -0,0 +1,40 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : occluded_by_specific_object.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws only the lines that are occluded by a given object + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Id, Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from PredicatesU1D import pyIsInOccludersListUP1D +from logical_operators import AndUP1D, NotUP1D + +## the id of the occluder (use SHIFT+click on the ViewMap to +## retrieve ids) +id = Id(3,0) +upred = AndUP1D(NotUP1D(QuantitativeInvisibilityUP1D(0)), pyIsInOccludersListUP1D(id)) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +shaders_list = [ + SamplingShader(5), + ConstantThicknessShader(3), + ConstantColorShader(0.3, 0.3, 0.3, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/parameter_editor.py b/release/scripts/freestyle/style_modules/parameter_editor.py new file mode 100644 index 00000000000..ffb01f06c7b --- /dev/null +++ b/release/scripts/freestyle/style_modules/parameter_editor.py @@ -0,0 +1,1259 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : parameter_editor.py +# Authors : Tamito Kajiyama +# Date : 26/07/2010 +# Purpose : Interactive manipulation of stylization parameters + +import Freestyle +import math +import mathutils +import time + +from ChainingIterators import pySketchyChainSilhouetteIterator, pySketchyChainingIterator +from Freestyle import BackboneStretcherShader, BezierCurveShader, BinaryPredicate1D, ChainPredicateIterator, \ + ChainSilhouetteIterator, ConstantColorShader, ContourUP1D, Curvature2DAngleF0D, ExternalContourUP1D, \ + FalseBP1D, FalseUP1D, GuidingLinesShader, Interface0DIterator, Nature, Noise, Normal2DF0D, Operators, \ + PolygonalizationShader, QuantitativeInvisibilityF1D, QuantitativeInvisibilityUP1D, SamplingShader, \ + SpatialNoiseShader, StrokeAttribute, StrokeShader, TipRemoverShader, TrueBP1D, TrueUP1D, UnaryPredicate0D, \ + UnaryPredicate1D, VertexOrientation2DF0D, WithinImageBoundaryUP1D +from Functions0D import CurveMaterialF0D +from PredicatesU1D import pyNatureUP1D +from logical_operators import AndUP1D, NotUP1D, OrUP1D +from shaders import pyBluePrintCirclesShader, pyBluePrintEllipsesShader, pyBluePrintSquaresShader + +class ColorRampModifier(StrokeShader): + def __init__(self, blend, influence, ramp): + StrokeShader.__init__(self) + self.__blend = blend + self.__influence = influence + self.__ramp = ramp + def evaluate(self, t): + col = Freestyle.evaluateColorRamp(self.__ramp, t) + col = col.xyz # omit alpha + return col + def blend_ramp(self, a, b): + return Freestyle.blendRamp(self.__blend, a, self.__influence, b) + +class ScalarBlendModifier(StrokeShader): + def __init__(self, blend, influence): + StrokeShader.__init__(self) + self.__blend = blend + self.__influence = influence + def blend(self, v1, v2): + fac = self.__influence + facm = 1.0 - fac + if self.__blend == "MIX": + v1 = facm * v1 + fac * v2 + elif self.__blend == "ADD": + v1 += fac * v2 + elif self.__blend == "MULTIPLY": + v1 *= facm + fac * v2; + elif self.__blend == "SUBTRACT": + v1 -= fac * v2 + elif self.__blend == "DIVIDE": + if v2 != 0.0: + v1 = facm * v1 + fac * v1 / v2 + elif self.__blend == "DIFFERENCE": + v1 = facm * v1 + fac * abs(v1 - v2) + elif self.__blend == "MININUM": + tmp = fac * v1 + if v1 > tmp: + v1 = tmp + elif self.__blend == "MAXIMUM": + tmp = fac * v1 + if v1 < tmp: + v1 = tmp + else: + raise ValueError("unknown curve blend type: " + self.__blend) + return v1 + +class CurveMappingModifier(ScalarBlendModifier): + def __init__(self, blend, influence, mapping, invert, curve): + ScalarBlendModifier.__init__(self, blend, influence) + assert mapping in ("LINEAR", "CURVE") + self.__mapping = getattr(self, mapping) + self.__invert = invert + self.__curve = curve + def LINEAR(self, t): + if self.__invert: + return 1.0 - t + return t + def CURVE(self, t): + return Freestyle.evaluateCurveMappingF(self.__curve, 0, t) + def evaluate(self, t): + return self.__mapping(t) + +class ThicknessModifierMixIn: + def __init__(self): + scene = Freestyle.getCurrentScene() + self.__persp_camera = (scene.camera.data.type == "PERSP") + def set_thickness(self, sv, outer, inner): + fe = sv.first_svertex.get_fedge(sv.second_svertex) + nature = fe.nature + if (nature & Nature.BORDER): + if self.__persp_camera: + point = -sv.point_3d.copy() + point.normalize() + dir = point.dot(fe.normal_left) + else: + dir = fe.normal_left.z + if dir < 0.0: # the back side is visible + outer, inner = inner, outer + elif (nature & Nature.SILHOUETTE): + if fe.is_smooth: # TODO more tests needed + outer, inner = inner, outer + else: + outer = inner = (outer + inner) / 2 + sv.attribute.thickness = (outer, inner) + +class ThicknessBlenderMixIn(ThicknessModifierMixIn): + def __init__(self, position, ratio): + ThicknessModifierMixIn.__init__(self) + self.__position = position + self.__ratio = ratio + def blend_thickness(self, outer, inner, v): + if self.__position == "CENTER": + outer = self.blend(outer, v / 2) + inner = self.blend(inner, v / 2) + elif self.__position == "INSIDE": + outer = self.blend(outer, 0) + inner = self.blend(inner, v) + elif self.__position == "OUTSIDE": + outer = self.blend(outer, v) + inner = self.blend(inner, 0) + elif self.__position == "RELATIVE": + outer = self.blend(outer, v * self.__ratio) + inner = self.blend(inner, v * (1 - self.__ratio)) + else: + raise ValueError("unknown thickness position: " + self.__position) + return outer, inner + +class BaseColorShader(ConstantColorShader): + pass + +class BaseThicknessShader(StrokeShader, ThicknessModifierMixIn): + def __init__(self, thickness, position, ratio): + StrokeShader.__init__(self) + ThicknessModifierMixIn.__init__(self) + if position == "CENTER": + self.__outer = thickness / 2 + self.__inner = thickness / 2 + elif position == "INSIDE": + self.__outer = 0 + self.__inner = thickness + elif position == "OUTSIDE": + self.__outer = thickness + self.__inner = 0 + elif position == "RELATIVE": + self.__outer = thickness * ratio + self.__inner = thickness * (1 - ratio) + else: + raise ValueError("unknown thickness position: " + self.position) + def shade(self, stroke): + it = stroke.stroke_vertices_begin() + while not it.is_end: + sv = it.object + self.set_thickness(sv, self.__outer, self.__inner) + it.increment() + +# Along Stroke modifiers + +def iter_t2d_along_stroke(stroke): + total = stroke.length_2d + distance = 0.0 + it = stroke.stroke_vertices_begin() + prev = it.object.point + while not it.is_end: + p = it.object.point + distance += (prev - p).length + prev = p.copy() # need a copy because the point can be altered + t = min(distance / total, 1.0) + yield it, t + it.increment() + +class ColorAlongStrokeShader(ColorRampModifier): + def shade(self, stroke): + for it, t in iter_t2d_along_stroke(stroke): + sv = it.object + a = sv.attribute.color + b = self.evaluate(t) + sv.attribute.color = self.blend_ramp(a, b) + +class AlphaAlongStrokeShader(CurveMappingModifier): + def shade(self, stroke): + for it, t in iter_t2d_along_stroke(stroke): + sv = it.object + a = sv.attribute.alpha + b = self.evaluate(t) + sv.attribute.alpha = self.blend(a, b) + +class ThicknessAlongStrokeShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__value_min = value_min + self.__value_max = value_max + def shade(self, stroke): + for it, t in iter_t2d_along_stroke(stroke): + sv = it.object + a = sv.attribute.thickness + b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) + +# Distance from Camera modifiers + +def iter_distance_from_camera(stroke, range_min, range_max): + normfac = range_max - range_min # normalization factor + it = stroke.stroke_vertices_begin() + while not it.is_end: + p = it.object.point_3d # in the camera coordinate + distance = p.length + if distance < range_min: + t = 0.0 + elif distance > range_max: + t = 1.0 + else: + t = (distance - range_min) / normfac + yield it, t + it.increment() + +class ColorDistanceFromCameraShader(ColorRampModifier): + def __init__(self, blend, influence, ramp, range_min, range_max): + ColorRampModifier.__init__(self, blend, influence, ramp) + self.__range_min = range_min + self.__range_max = range_max + def shade(self, stroke): + for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max): + sv = it.object + a = sv.attribute.color + b = self.evaluate(t) + sv.attribute.color = self.blend_ramp(a, b) + +class AlphaDistanceFromCameraShader(CurveMappingModifier): + def __init__(self, blend, influence, mapping, invert, curve, range_min, range_max): + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__range_min = range_min + self.__range_max = range_max + def shade(self, stroke): + for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max): + sv = it.object + a = sv.attribute.alpha + b = self.evaluate(t) + sv.attribute.alpha = self.blend(a, b) + +class ThicknessDistanceFromCameraShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, range_min, range_max, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__range_min = range_min + self.__range_max = range_max + self.__value_min = value_min + self.__value_max = value_max + def shade(self, stroke): + for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max): + sv = it.object + a = sv.attribute.thickness + b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) + +# Distance from Object modifiers + +def iter_distance_from_object(stroke, object, range_min, range_max): + scene = Freestyle.getCurrentScene() + mv = scene.camera.matrix_world.copy() # model-view matrix + mv.invert() + loc = mv * object.location # loc in the camera coordinate + normfac = range_max - range_min # normalization factor + it = stroke.stroke_vertices_begin() + while not it.is_end: + p = it.object.point_3d # in the camera coordinate + distance = (p - loc).length + if distance < range_min: + t = 0.0 + elif distance > range_max: + t = 1.0 + else: + t = (distance - range_min) / normfac + yield it, t + it.increment() + +class ColorDistanceFromObjectShader(ColorRampModifier): + def __init__(self, blend, influence, ramp, target, range_min, range_max): + ColorRampModifier.__init__(self, blend, influence, ramp) + self.__target = target + self.__range_min = range_min + self.__range_max = range_max + def shade(self, stroke): + if self.__target is None: + return + for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max): + sv = it.object + a = sv.attribute.color + b = self.evaluate(t) + sv.attribute.color = self.blend_ramp(a, b) + +class AlphaDistanceFromObjectShader(CurveMappingModifier): + def __init__(self, blend, influence, mapping, invert, curve, target, range_min, range_max): + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__target = target + self.__range_min = range_min + self.__range_max = range_max + def shade(self, stroke): + if self.__target is None: + return + for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max): + sv = it.object + a = sv.attribute.alpha + b = self.evaluate(t) + sv.attribute.alpha = self.blend(a, b) + +class ThicknessDistanceFromObjectShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, target, range_min, range_max, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__target = target + self.__range_min = range_min + self.__range_max = range_max + self.__value_min = value_min + self.__value_max = value_max + def shade(self, stroke): + if self.__target is None: + return + for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max): + sv = it.object + a = sv.attribute.thickness + b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) + +# Material modifiers + +def iter_material_color(stroke, material_attr): + func = CurveMaterialF0D() + it = stroke.stroke_vertices_begin() + while not it.is_end: + material = func(Interface0DIterator(it)) + if material_attr == "DIFF": + color = (material.diffuse[0], + material.diffuse[1], + material.diffuse[2]) + elif material_attr == "SPEC": + color = (material.specular[0], + material.specular[1], + material.specular[2]) + else: + raise ValueError("unexpected material attribute: " + material_attr) + yield it, color + it.increment() + +def iter_material_value(stroke, material_attr): + func = CurveMaterialF0D() + it = stroke.stroke_vertices_begin() + while not it.is_end: + material = func(Interface0DIterator(it)) + if material_attr == "DIFF": + r = material.diffuse[0] + g = material.diffuse[1] + b = material.diffuse[2] + t = 0.35 * r + 0.45 * r + 0.2 * b + elif material_attr == "DIFF_R": + t = material.diffuse[0] + elif material_attr == "DIFF_G": + t = material.diffuse[1] + elif material_attr == "DIFF_B": + t = material.diffuse[2] + elif material_attr == "SPEC": + r = material.specular[0] + g = material.specular[1] + b = material.specular[2] + t = 0.35 * r + 0.45 * r + 0.2 * b + elif material_attr == "SPEC_R": + t = material.specular[0] + elif material_attr == "SPEC_G": + t = material.specular[1] + elif material_attr == "SPEC_B": + t = material.specular[2] + elif material_attr == "SPEC_HARDNESS": + t = material.shininess + elif material_attr == "ALPHA": + t = material.diffuse[3] + else: + raise ValueError("unexpected material attribute: " + material_attr) + yield it, t + it.increment() + +class ColorMaterialShader(ColorRampModifier): + def __init__(self, blend, influence, ramp, material_attr, use_ramp): + ColorRampModifier.__init__(self, blend, influence, ramp) + self.__material_attr = material_attr + self.__use_ramp = use_ramp + def shade(self, stroke): + if self.__material_attr in ["DIFF", "SPEC"] and not self.__use_ramp: + for it, b in iter_material_color(stroke, self.__material_attr): + sv = it.object + a = sv.attribute.color + sv.attribute.color = self.blend_ramp(a, b) + else: + for it, t in iter_material_value(stroke, self.__material_attr): + sv = it.object + a = sv.attribute.color + b = self.evaluate(t) + sv.attribute.color = self.blend_ramp(a, b) + +class AlphaMaterialShader(CurveMappingModifier): + def __init__(self, blend, influence, mapping, invert, curve, material_attr): + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__material_attr = material_attr + def shade(self, stroke): + for it, t in iter_material_value(stroke, self.__material_attr): + sv = it.object + a = sv.attribute.alpha + b = self.evaluate(t) + sv.attribute.alpha = self.blend(a, b) + +class ThicknessMaterialShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, material_attr, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) + CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) + self.__material_attr = material_attr + self.__value_min = value_min + self.__value_max = value_max + def shade(self, stroke): + for it, t in iter_material_value(stroke, self.__material_attr): + sv = it.object + a = sv.attribute.thickness + b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) + +# Calligraphic thickness modifier + +class CalligraphicThicknessShader(ThicknessBlenderMixIn, ScalarBlendModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, orientation, min_thickness, max_thickness): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) + ScalarBlendModifier.__init__(self, blend, influence) + self.__orientation = mathutils.Vector((math.cos(orientation), math.sin(orientation))) + self.__min_thickness = min_thickness + self.__max_thickness = max_thickness + def shade(self, stroke): + func = VertexOrientation2DF0D() + it = stroke.stroke_vertices_begin() + while not it.is_end: + dir = func(Interface0DIterator(it)) + orthDir = mathutils.Vector((-dir.y, dir.x)) + orthDir.normalize() + fac = abs(orthDir * self.__orientation) + sv = it.object + a = sv.attribute.thickness + b = self.__min_thickness + fac * (self.__max_thickness - self.__min_thickness) + b = max(b, 0.0) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) + it.increment() + +# Geometry modifiers + +def iter_distance_along_stroke(stroke): + distance = 0.0 + it = stroke.stroke_vertices_begin() + prev = it.object.point + while not it.is_end: + p = it.object.point + distance += (prev - p).length + prev = p.copy() # need a copy because the point can be altered + yield it, distance + it.increment() + +class SinusDisplacementShader(StrokeShader): + def __init__(self, wavelength, amplitude, phase): + StrokeShader.__init__(self) + self._wavelength = wavelength + self._amplitude = amplitude + self._phase = phase / wavelength * 2 * math.pi + self._getNormal = Normal2DF0D() + def shade(self, stroke): + for it, distance in iter_distance_along_stroke(stroke): + v = it.object + n = self._getNormal(Interface0DIterator(it)) + n = n * self._amplitude * math.cos(distance / self._wavelength * 2 * math.pi + self._phase) + v.point = v.point + n + stroke.update_length() + +class PerlinNoise1DShader(StrokeShader): + def __init__(self, freq = 10, amp = 10, oct = 4, angle = math.radians(45), seed = -1): + StrokeShader.__init__(self) + self.__noise = Noise(seed) + self.__freq = freq + self.__amp = amp + self.__oct = oct + self.__dir = mathutils.Vector([math.cos(angle), math.sin(angle)]) + def shade(self, stroke): + length = stroke.length_2d + it = stroke.stroke_vertices_begin() + while not it.is_end: + v = it.object + nres = self.__noise.turbulence1(length * v.u, self.__freq, self.__amp, self.__oct) + v.point = v.point + nres * self.__dir + it.increment() + stroke.update_length() + +class PerlinNoise2DShader(StrokeShader): + def __init__(self, freq = 10, amp = 10, oct = 4, angle = math.radians(45), seed = -1): + StrokeShader.__init__(self) + self.__noise = Noise(seed) + self.__freq = freq + self.__amp = amp + self.__oct = oct + self.__dir = mathutils.Vector([math.cos(angle), math.sin(angle)]) + def shade(self, stroke): + it = stroke.stroke_vertices_begin() + while not it.is_end: + v = it.object + vec = mathutils.Vector([v.projected_x, v.projected_y]) + nres = self.__noise.turbulence2(vec, self.__freq, self.__amp, self.__oct) + v.point = v.point + nres * self.__dir + it.increment() + stroke.update_length() + +class Offset2DShader(StrokeShader): + def __init__(self, start, end, x, y): + StrokeShader.__init__(self) + self.__start = start + self.__end = end + self.__xy = mathutils.Vector([x, y]) + self.__getNormal = Normal2DF0D() + def shade(self, stroke): + it = stroke.stroke_vertices_begin() + while not it.is_end: + v = it.object + u = v.u + a = self.__start + u * (self.__end - self.__start) + n = self.__getNormal(Interface0DIterator(it)) + n = n * a + v.point = v.point + n + self.__xy + it.increment() + stroke.update_length() + +class Transform2DShader(StrokeShader): + def __init__(self, pivot, scale_x, scale_y, angle, pivot_u, pivot_x, pivot_y): + StrokeShader.__init__(self) + self.__pivot = pivot + self.__scale_x = scale_x + self.__scale_y = scale_y + self.__angle = angle + self.__pivot_u = pivot_u + self.__pivot_x = pivot_x + self.__pivot_y = pivot_y + def shade(self, stroke): + # determine the pivot of scaling and rotation operations + if self.__pivot == "START": + it = stroke.stroke_vertices_begin() + pivot = it.object.point + elif self.__pivot == "END": + it = stroke.stroke_vertices_end() + it.decrement() + pivot = it.object.point + elif self.__pivot == "PARAM": + p = None + it = stroke.stroke_vertices_begin() + while not it.is_end: + prev = p + v = it.object + p = v.point + u = v.u + if self.__pivot_u < u: + break + it.increment() + if prev is None: + pivot = p + else: + delta = u - self.__pivot_u + pivot = p + delta * (prev - p) + elif self.__pivot == "CENTER": + pivot = mathutils.Vector([0.0, 0.0]) + n = 0 + it = stroke.stroke_vertices_begin() + while not it.is_end: + p = it.object.point + pivot = pivot + p + n = n + 1 + it.increment() + pivot.x = pivot.x / n + pivot.y = pivot.y / n + elif self.__pivot == "ABSOLUTE": + pivot = mathutils.Vector([self.__pivot_x, self.__pivot_y]) + # apply scaling and rotation operations + cos_theta = math.cos(self.__angle) + sin_theta = math.sin(self.__angle) + it = stroke.stroke_vertices_begin() + while not it.is_end: + v = it.object + p = v.point + p = p - pivot + x = p.x * self.__scale_x + y = p.y * self.__scale_y + p.x = x * cos_theta - y * sin_theta + p.y = x * sin_theta + y * cos_theta + v.point = p + pivot + it.increment() + stroke.update_length() + +# Predicates and helper functions + +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 + +def join_unary_predicates(upred_list, bpred): + if not upred_list: + return None + upred = upred_list[0] + for p in upred_list[1:]: + upred = bpred(upred, p) + return upred + +class ObjectNamesUP1D(UnaryPredicate1D): + def __init__(self, names, negative): + UnaryPredicate1D.__init__(self) + self._names = names + self._negative = negative + def __call__(self, viewEdge): + found = viewEdge.viewshape.name in self._names + if self._negative: + return not found + return found + +# Stroke caps + +def iter_stroke_vertices(stroke): + it = stroke.stroke_vertices_begin() + prev_p = None + while not it.is_end: + sv = it.object + p = sv.point + if prev_p is None or (prev_p - p).length > 1e-6: + yield sv + prev_p = p.copy() + it.increment() + +class RoundCapShader(StrokeShader): + def round_cap_thickness(self, x): + x = max(0.0, min(x, 1.0)) + return math.sqrt(1.0 - (x ** 2)) + def shade(self, stroke): + # save the location and attribute of stroke vertices + buffer = [] + for sv in iter_stroke_vertices(stroke): + buffer.append((mathutils.Vector(sv.point), StrokeAttribute(sv.attribute))) + nverts = len(buffer) + if nverts < 2: + return + # calculate the number of additional vertices to form caps + R, L = stroke[0].attribute.thickness + caplen_beg = (R + L) / 2.0 + nverts_beg = max(5, int(R + L)) + R, L = stroke[-1].attribute.thickness + caplen_end = (R + L) / 2.0 + nverts_end = max(5, int(R + L)) + # 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 in range(nverts): + p, attr = buffer[i] + 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] + d = p - q + d = d / d.length * caplen_beg + n = 1.0 / nverts_beg + R, L = attr.thickness + for i in range(nverts_beg): + t = (nverts_beg - i) * n + stroke[i].point = p + d * t + r = self.round_cap_thickness((nverts_beg - i + 1) * n) + stroke[i].attribute = attr + stroke[i].attribute.thickness = (R * r, L * r) + # reshape the cap at the end of the stroke + q, attr = buffer[-2] + p, attr = buffer[-1] + d = p - q + d = d / d.length * caplen_end + n = 1.0 / nverts_end + R, L = attr.thickness + for i in range(nverts_end): + t = (nverts_end - i) * n + stroke[-i-1].point = p + d * t + r = self.round_cap_thickness((nverts_end - i + 1) * n) + stroke[-i-1].attribute = attr + stroke[-i-1].attribute.thickness = (R * r, L * r) + # update the curvilinear 2D length of each vertex + stroke.update_length() + +class SquareCapShader(StrokeShader): + def shade(self, stroke): + # save the location and attribute of stroke vertices + buffer = [] + for sv in iter_stroke_vertices(stroke): + buffer.append((mathutils.Vector(sv.point), StrokeAttribute(sv.attribute))) + nverts = len(buffer) + if nverts < 2: + return + # calculate the number of additional vertices to form caps + R, L = stroke[0].attribute.thickness + caplen_beg = (R + L) / 2.0 + nverts_beg = 1 + R, L = stroke[-1].attribute.thickness + caplen_end = (R + L) / 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 in range(nverts): + p, attr = buffer[i] + 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] + d = p - q + stroke[0].point = p + d / d.length * caplen_beg + stroke[0].attribute = attr + # reshape the cap at the end of the stroke + q, attr = buffer[-2] + p, attr = buffer[-1] + d = p - q + stroke[-1].point = p + d / d.length * caplen_beg + stroke[-1].attribute = attr + # update the curvilinear 2D length of each vertex + stroke.update_length() + +# Split by dashed line pattern + +class SplitPatternStartingUP0D(UnaryPredicate0D): + def __init__(self, controller): + UnaryPredicate0D.__init__(self) + self._controller = controller + def __call__(self, inter): + return self._controller.start() + +class SplitPatternStoppingUP0D(UnaryPredicate0D): + def __init__(self, controller): + UnaryPredicate0D.__init__(self) + self._controller = controller + def __call__(self, inter): + return self._controller.stop() + +class SplitPatternController: + def __init__(self, pattern, sampling): + self.sampling = float(sampling) + k = len(pattern) // 2 + n = k * 2 + self.start_pos = [pattern[i] + pattern[i+1] for i in range(0, n, 2)] + self.stop_pos = [pattern[i] for i in range(0, n, 2)] + self.init() + def init(self): + self.start_len = 0.0 + self.start_idx = 0 + self.stop_len = self.sampling + self.stop_idx = 0 + def start(self): + self.start_len += self.sampling + if abs(self.start_len - self.start_pos[self.start_idx]) < self.sampling / 2.0: + self.start_len = 0.0 + self.start_idx = (self.start_idx + 1) % len(self.start_pos) + return True + return False + def stop(self): + if self.start_len > 0.0: + self.init() + self.stop_len += self.sampling + if abs(self.stop_len - self.stop_pos[self.stop_idx]) < self.sampling / 2.0: + self.stop_len = self.sampling + self.stop_idx = (self.stop_idx + 1) % len(self.stop_pos) + return True + return False + +# Dashed line + +class DashedLineShader(StrokeShader): + def __init__(self, pattern): + StrokeShader.__init__(self) + self._pattern = pattern + def shade(self, stroke): + index = 0 # pattern index + start = 0.0 # 2D curvilinear length + visible = True + sampling = 1.0 + it = stroke.stroke_vertices_begin(sampling) + while not it.is_end: + pos = it.t # curvilinear abscissa + # The extra 'sampling' term is added below, because the + # visibility attribute of the i-th vertex refers to the + # visibility of the stroke segment between the i-th and + # (i+1)-th vertices. + if pos - start + sampling > self._pattern[index]: + start = pos + index += 1 + if index == len(self._pattern): + index = 0 + visible = not visible + it.object.attribute.visible = visible + it.increment() + +# predicates for chaining + +class AngleLargerThanBP1D(BinaryPredicate1D): + def __init__(self, angle): + BinaryPredicate1D.__init__(self) + self._angle = angle + def __call__(self, i1, i2): + sv1a = i1.first_fedge.first_svertex.point_2d + sv1b = i1.last_fedge.second_svertex.point_2d + sv2a = i2.first_fedge.first_svertex.point_2d + sv2b = i2.last_fedge.second_svertex.point_2d + if (sv1a - sv2a).length < 1e-6: + dir1 = sv1a - sv1b + dir2 = sv2b - sv2a + elif (sv1b - sv2b).length < 1e-6: + dir1 = sv1b - sv1a + dir2 = sv2a - sv2b + elif (sv1a - sv2b).length < 1e-6: + dir1 = sv1a - sv1b + dir2 = sv2a - sv2b + elif (sv1b - sv2a).length < 1e-6: + dir1 = sv1b - sv1a + dir2 = sv2b - sv2a + else: + return False + denom = dir1.length * dir2.length + if denom < 1e-6: + return False + x = (dir1 * dir2) / denom + return math.acos(min(max(x, -1.0), 1.0)) > self._angle + +class AndBP1D(BinaryPredicate1D): + def __init__(self, pred1, pred2): + BinaryPredicate1D.__init__(self) + self.__pred1 = pred1 + self.__pred2 = pred2 + def __call__(self, i1, i2): + return self.__pred1(i1, i2) and self.__pred2(i1, i2) + +# predicates for selection + +class LengthThresholdUP1D(UnaryPredicate1D): + def __init__(self, min_length=None, max_length=None): + UnaryPredicate1D.__init__(self) + self._min_length = min_length + self._max_length = max_length + def __call__(self, inter): + length = inter.length_2d + if self._min_length is not None and length < self._min_length: + return False + if self._max_length is not None and length > self._max_length: + return False + return True + +class FaceMarkBothUP1D(UnaryPredicate1D): + def __call__(self, inter): # ViewEdge + fe = inter.first_fedge + while fe is not None: + if fe.is_smooth: + if fe.face_mark: + return True + else: + if fe.face_mark_right and fe.face_mark_left: + return True + fe = fe.next_fedge + return False + +class FaceMarkOneUP1D(UnaryPredicate1D): + def __call__(self, inter): # ViewEdge + fe = inter.first_fedge + while fe is not None: + if fe.is_smooth: + if fe.face_mark: + return True + else: + if fe.face_mark_right or fe.face_mark_left: + return True + fe = fe.next_fedge + return False + +# predicates for splitting + +class MaterialBoundaryUP0D(UnaryPredicate0D): + def __call__(self, it): + if it.is_begin: + return False + it_prev = Interface0DIterator(it) + it_prev.decrement() + v = it.object + it.increment() + if it.is_end: + return False + fe = v.get_fedge(it_prev.object) + idx1 = fe.material_index if fe.is_smooth else fe.material_index_left + fe = v.get_fedge(it.object) + idx2 = fe.material_index if fe.is_smooth else fe.material_index_left + return idx1 != idx2 + +class Curvature2DAngleThresholdUP0D(UnaryPredicate0D): + def __init__(self, min_angle=None, max_angle=None): + UnaryPredicate0D.__init__(self) + self._min_angle = min_angle + self._max_angle = max_angle + self._func = Curvature2DAngleF0D() + def __call__(self, inter): + angle = math.pi - self._func(inter) + if self._min_angle is not None and angle < self._min_angle: + return True + if self._max_angle is not None and angle > self._max_angle: + return True + return False + +class Length2DThresholdUP0D(UnaryPredicate0D): + def __init__(self, length_limit): + UnaryPredicate0D.__init__(self) + self._length_limit = length_limit + self._t = 0.0 + def __call__(self, inter): + t = inter.t # curvilinear abscissa + if t < self._t: + self._t = 0.0 + return False + if t - self._t < self._length_limit: + return False + self._t = t + return True + +# Seed for random number generation + +class Seed: + def __init__(self): + self.t_max = 2 ** 15 + self.t = int(time.time()) % self.t_max + def get(self, seed): + if seed < 0: + self.t = (self.t + 1) % self.t_max + return self.t + return seed + +_seed = Seed() + +# main function for parameter processing + +def process(layer_name, lineset_name): + scene = Freestyle.getCurrentScene() + layer = scene.render.layers[layer_name] + lineset = layer.freestyle_settings.linesets[lineset_name] + linestyle = lineset.linestyle + + selection_criteria = [] + # prepare selection criteria by visibility + if lineset.select_by_visibility: + if lineset.visibility == "VISIBLE": + selection_criteria.append( + QuantitativeInvisibilityUP1D(0)) + elif lineset.visibility == "HIDDEN": + selection_criteria.append( + NotUP1D(QuantitativeInvisibilityUP1D(0))) + elif lineset.visibility == "RANGE": + selection_criteria.append( + QuantitativeInvisibilityRangeUP1D(lineset.qi_start, lineset.qi_end)) + # prepare selection criteria by edge types + if lineset.select_by_edge_types: + edge_type_criteria = [] + if lineset.select_silhouette: + upred = pyNatureUP1D(Nature.SILHOUETTE) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_silhouette else upred) + if lineset.select_border: + upred = pyNatureUP1D(Nature.BORDER) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_border else upred) + if lineset.select_crease: + upred = pyNatureUP1D(Nature.CREASE) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_crease else upred) + if lineset.select_ridge_valley: + upred = pyNatureUP1D(Nature.RIDGE) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_ridge_valley else upred) + if lineset.select_suggestive_contour: + upred = pyNatureUP1D(Nature.SUGGESTIVE_CONTOUR) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_suggestive_contour else upred) + if lineset.select_material_boundary: + upred = pyNatureUP1D(Nature.MATERIAL_BOUNDARY) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_material_boundary else upred) + if lineset.select_edge_mark: + upred = pyNatureUP1D(Nature.EDGE_MARK) + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_edge_mark else upred) + if lineset.select_contour: + upred = ContourUP1D() + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_contour else upred) + if lineset.select_external_contour: + upred = ExternalContourUP1D() + edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_external_contour else upred) + if lineset.edge_type_combination == "OR": + upred = join_unary_predicates(edge_type_criteria, OrUP1D) + else: + upred = join_unary_predicates(edge_type_criteria, AndUP1D) + if upred is not None: + if lineset.edge_type_negation == "EXCLUSIVE": + upred = NotUP1D(upred) + selection_criteria.append(upred) + # prepare selection criteria by face marks + if lineset.select_by_face_marks: + if lineset.face_mark_condition == "BOTH": + upred = FaceMarkBothUP1D() + else: + upred = FaceMarkOneUP1D() + if lineset.face_mark_negation == "EXCLUSIVE": + upred = NotUP1D(upred) + selection_criteria.append(upred) + # prepare selection criteria by group of objects + if lineset.select_by_group: + if lineset.group is not None: + names = dict((ob.name, True) for ob in lineset.group.objects) + upred = ObjectNamesUP1D(names, lineset.group_negation == 'EXCLUSIVE') + selection_criteria.append(upred) + # prepare selection criteria by image border + if lineset.select_by_image_border: + fac = scene.render.resolution_percentage / 100.0 + w = scene.render.resolution_x * fac + h = scene.render.resolution_y * fac + if scene.render.use_border: + xmin = scene.render.border_min_x * w + xmax = scene.render.border_max_x * w + ymin = scene.render.border_min_y * h + ymax = scene.render.border_max_y * h + else: + xmin, xmax = 0.0, float(w) + ymin, ymax = 0.0, float(h) + upred = WithinImageBoundaryUP1D(xmin, ymin, xmax, ymax) + selection_criteria.append(upred) + # select feature edges + upred = join_unary_predicates(selection_criteria, AndUP1D) + if upred is None: + upred = TrueUP1D() + Operators.select(upred) + # join feature edges to form chains + if linestyle.use_chaining: + if linestyle.chaining == "PLAIN": + if linestyle.same_object: + Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) + else: + Operators.bidirectional_chain(ChainPredicateIterator(upred, TrueBP1D()), NotUP1D(upred)) + elif linestyle.chaining == "SKETCHY": + if linestyle.same_object: + Operators.bidirectional_chain(pySketchyChainSilhouetteIterator(linestyle.rounds)) + else: + Operators.bidirectional_chain(pySketchyChainingIterator(linestyle.rounds)) + else: + Operators.chain(ChainPredicateIterator(FalseUP1D(), FalseBP1D()), NotUP1D(upred)) + # split chains + if linestyle.material_boundary: + Operators.sequential_split(MaterialBoundaryUP0D()) + if linestyle.use_min_angle or linestyle.use_max_angle: + min_angle = linestyle.min_angle if linestyle.use_min_angle else None + max_angle = linestyle.max_angle if linestyle.use_max_angle else None + Operators.sequential_split(Curvature2DAngleThresholdUP0D(min_angle, max_angle)) + if linestyle.use_split_length: + Operators.sequential_split(Length2DThresholdUP0D(linestyle.split_length), 1.0) + if linestyle.use_split_pattern: + pattern = [] + if linestyle.split_dash1 > 0 and linestyle.split_gap1 > 0: + pattern.append(linestyle.split_dash1) + pattern.append(linestyle.split_gap1) + if linestyle.split_dash2 > 0 and linestyle.split_gap2 > 0: + pattern.append(linestyle.split_dash2) + pattern.append(linestyle.split_gap2) + if linestyle.split_dash3 > 0 and linestyle.split_gap3 > 0: + pattern.append(linestyle.split_dash3) + pattern.append(linestyle.split_gap3) + if len(pattern) > 0: + sampling = 1.0 + controller = SplitPatternController(pattern, sampling) + Operators.sequential_split(SplitPatternStartingUP0D(controller), + SplitPatternStoppingUP0D(controller), + sampling) + # select chains + if linestyle.use_min_length or linestyle.use_max_length: + min_length = linestyle.min_length if linestyle.use_min_length else None + max_length = linestyle.max_length if linestyle.use_max_length else None + Operators.select(LengthThresholdUP1D(min_length, max_length)) + # prepare a list of stroke shaders + shaders_list = [] + for m in linestyle.geometry_modifiers: + if not m.use: + continue + if m.type == "SAMPLING": + shaders_list.append(SamplingShader( + m.sampling)) + elif m.type == "BEZIER_CURVE": + shaders_list.append(BezierCurveShader( + m.error)) + elif m.type == "SINUS_DISPLACEMENT": + shaders_list.append(SinusDisplacementShader( + m.wavelength, m.amplitude, m.phase)) + elif m.type == "SPATIAL_NOISE": + shaders_list.append(SpatialNoiseShader( + m.amplitude, m.scale, m.octaves, m.smooth, m.pure_random)) + elif m.type == "PERLIN_NOISE_1D": + shaders_list.append(PerlinNoise1DShader( + m.frequency, m.amplitude, m.octaves, m.angle, _seed.get(m.seed))) + elif m.type == "PERLIN_NOISE_2D": + shaders_list.append(PerlinNoise2DShader( + m.frequency, m.amplitude, m.octaves, m.angle, _seed.get(m.seed))) + elif m.type == "BACKBONE_STRETCHER": + shaders_list.append(BackboneStretcherShader( + m.backbone_length)) + elif m.type == "TIP_REMOVER": + shaders_list.append(TipRemoverShader( + m.tip_length)) + elif m.type == "POLYGONIZATION": + shaders_list.append(PolygonalizationShader( + m.error)) + elif m.type == "GUIDING_LINES": + shaders_list.append(GuidingLinesShader( + m.offset)) + elif m.type == "BLUEPRINT": + if m.shape == "CIRCLES": + shaders_list.append(pyBluePrintCirclesShader( + m.rounds, m.random_radius, m.random_center)) + elif m.shape == "ELLIPSES": + shaders_list.append(pyBluePrintEllipsesShader( + m.rounds, m.random_radius, m.random_center)) + elif m.shape == "SQUARES": + shaders_list.append(pyBluePrintSquaresShader( + m.rounds, m.backbone_length, m.random_backbone)) + elif m.type == "2D_OFFSET": + shaders_list.append(Offset2DShader( + m.start, m.end, m.x, m.y)) + elif m.type == "2D_TRANSFORM": + shaders_list.append(Transform2DShader( + m.pivot, m.scale_x, m.scale_y, m.angle, m.pivot_u, m.pivot_x, m.pivot_y)) + color = linestyle.color + if (not linestyle.use_chaining) or (linestyle.chaining == "PLAIN" and linestyle.same_object): + thickness_position = linestyle.thickness_position + else: + thickness_position = "CENTER" + import bpy + if bpy.app.debug_freestyle: + print("Warning: Thickness position options are applied when chaining is disabled") + print(" or the Plain chaining is used with the Same Object option enabled.") + shaders_list.append(BaseColorShader(color.r, color.g, color.b, linestyle.alpha)) + shaders_list.append(BaseThicknessShader(linestyle.thickness, thickness_position, + linestyle.thickness_ratio)) + for m in linestyle.color_modifiers: + if not m.use: + continue + if m.type == "ALONG_STROKE": + shaders_list.append(ColorAlongStrokeShader( + m.blend, m.influence, m.color_ramp)) + elif m.type == "DISTANCE_FROM_CAMERA": + shaders_list.append(ColorDistanceFromCameraShader( + m.blend, m.influence, m.color_ramp, + m.range_min, m.range_max)) + elif m.type == "DISTANCE_FROM_OBJECT": + shaders_list.append(ColorDistanceFromObjectShader( + m.blend, m.influence, m.color_ramp, m.target, + m.range_min, m.range_max)) + elif m.type == "MATERIAL": + shaders_list.append(ColorMaterialShader( + m.blend, m.influence, m.color_ramp, m.material_attr, + m.use_ramp)) + for m in linestyle.alpha_modifiers: + if not m.use: + continue + if m.type == "ALONG_STROKE": + shaders_list.append(AlphaAlongStrokeShader( + m.blend, m.influence, m.mapping, m.invert, m.curve)) + elif m.type == "DISTANCE_FROM_CAMERA": + shaders_list.append(AlphaDistanceFromCameraShader( + m.blend, m.influence, m.mapping, m.invert, m.curve, + m.range_min, m.range_max)) + elif m.type == "DISTANCE_FROM_OBJECT": + shaders_list.append(AlphaDistanceFromObjectShader( + m.blend, m.influence, m.mapping, m.invert, m.curve, m.target, + m.range_min, m.range_max)) + elif m.type == "MATERIAL": + shaders_list.append(AlphaMaterialShader( + m.blend, m.influence, m.mapping, m.invert, m.curve, + m.material_attr)) + for m in linestyle.thickness_modifiers: + if not m.use: + continue + if m.type == "ALONG_STROKE": + shaders_list.append(ThicknessAlongStrokeShader( + thickness_position, linestyle.thickness_ratio, + m.blend, m.influence, m.mapping, m.invert, m.curve, + m.value_min, m.value_max)) + elif m.type == "DISTANCE_FROM_CAMERA": + shaders_list.append(ThicknessDistanceFromCameraShader( + thickness_position, linestyle.thickness_ratio, + m.blend, m.influence, m.mapping, m.invert, m.curve, + m.range_min, m.range_max, m.value_min, m.value_max)) + elif m.type == "DISTANCE_FROM_OBJECT": + shaders_list.append(ThicknessDistanceFromObjectShader( + thickness_position, linestyle.thickness_ratio, + m.blend, m.influence, m.mapping, m.invert, m.curve, m.target, + m.range_min, m.range_max, m.value_min, m.value_max)) + elif m.type == "MATERIAL": + shaders_list.append(ThicknessMaterialShader( + thickness_position, linestyle.thickness_ratio, + m.blend, m.influence, m.mapping, m.invert, m.curve, + m.material_attr, m.value_min, m.value_max)) + elif m.type == "CALLIGRAPHY": + shaders_list.append(CalligraphicThicknessShader( + thickness_position, linestyle.thickness_ratio, + m.blend, m.influence, + m.orientation, m.min_thickness, m.max_thickness)) + if linestyle.caps == "ROUND": + shaders_list.append(RoundCapShader()) + elif linestyle.caps == "SQUARE": + shaders_list.append(SquareCapShader()) + if linestyle.use_dashed_line: + pattern = [] + if linestyle.dash1 > 0 and linestyle.gap1 > 0: + pattern.append(linestyle.dash1) + pattern.append(linestyle.gap1) + if linestyle.dash2 > 0 and linestyle.gap2 > 0: + pattern.append(linestyle.dash2) + pattern.append(linestyle.gap2) + if linestyle.dash3 > 0 and linestyle.gap3 > 0: + pattern.append(linestyle.dash3) + pattern.append(linestyle.gap3) + if len(pattern) > 0: + shaders_list.append(DashedLineShader(pattern)) + # create strokes using the shaders list + Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/polygonalize.py b/release/scripts/freestyle/style_modules/polygonalize.py new file mode 100644 index 00000000000..092efbb2df1 --- /dev/null +++ b/release/scripts/freestyle/style_modules/polygonalize.py @@ -0,0 +1,36 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : polygonalize.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Make the strokes more "polygonal" + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, PolygonalizationShader, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + SamplingShader(2.0), + ConstantThicknessShader(3), + ConstantColorShader(0.0, 0.0, 0.0), + PolygonalizationShader(8), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/qi0.py b/release/scripts/freestyle/style_modules/qi0.py new file mode 100644 index 00000000000..3badf818566 --- /dev/null +++ b/release/scripts/freestyle/style_modules/qi0.py @@ -0,0 +1,36 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : qi0.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the visible lines (chaining follows same nature lines) +# (most basic style module) + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + SamplingShader(5.0), + ConstantThicknessShader(4.0), + ConstantColorShader(0.0, 0.0, 0.0) + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/qi0_not_external_contour.py b/release/scripts/freestyle/style_modules/qi0_not_external_contour.py new file mode 100644 index 00000000000..20860b36aa3 --- /dev/null +++ b/release/scripts/freestyle/style_modules/qi0_not_external_contour.py @@ -0,0 +1,41 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : qi0_not_external_contour.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the visible lines (chaining follows same nature lines) +# that do not belong to the external contour of the scene + +from Freestyle import BackboneStretcherShader, ChainSilhouetteIterator, ExternalContourUP1D, \ + IncreasingColorShader, IncreasingThicknessShader, Operators, QuantitativeInvisibilityUP1D, \ + SamplingShader, SpatialNoiseShader, TextureAssignerShader, TrueUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ExternalContourUP1D()) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +shaders_list = [ + SamplingShader(4), + SpatialNoiseShader(4, 150, 2, True, True), + IncreasingThicknessShader(2, 5), + BackboneStretcherShader(20), + IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + TextureAssignerShader(4), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/qi1.py b/release/scripts/freestyle/style_modules/qi1.py new file mode 100644 index 00000000000..7ab86b67c86 --- /dev/null +++ b/release/scripts/freestyle/style_modules/qi1.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : qi1.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws lines hidden by one surface. +# *** Quantitative Invisibility must have been +# enabled in the options dialog to use this style module **** + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(1)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(1))) +shaders_list = [ + SamplingShader(5.0), + ConstantThicknessShader(3), + ConstantColorShader(0.5, 0.5, 0.5, 1) + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/qi2.py b/release/scripts/freestyle/style_modules/qi2.py new file mode 100644 index 00000000000..510a22e3e07 --- /dev/null +++ b/release/scripts/freestyle/style_modules/qi2.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : qi2.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws lines hidden by two surfaces. +# *** Quantitative Invisibility must have been +# enabled in the options dialog to use this style module **** + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(2)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(2))) +shaders_list = [ + SamplingShader(10), + ConstantThicknessShader(1.5), + ConstantColorShader(0.7, 0.7, 0.7, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/sequentialsplit_sketchy.py b/release/scripts/freestyle/style_modules/sequentialsplit_sketchy.py new file mode 100644 index 00000000000..966e5ddf129 --- /dev/null +++ b/release/scripts/freestyle/style_modules/sequentialsplit_sketchy.py @@ -0,0 +1,44 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : sequentialsplit_sketchy.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Use the sequential split with two different +# predicates to specify respectively the starting and +# the stopping extremities for strokes + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, IncreasingThicknessShader, Nature, \ + Operators, QuantitativeInvisibilityUP1D, SpatialNoiseShader, TextureAssignerShader, TrueUP1D +from PredicatesU0D import pyBackTVertexUP0D, pyVertexNatureUP0D +from logical_operators import NotUP1D + +upred = QuantitativeInvisibilityUP1D(0) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +## starting and stopping predicates: +start = pyVertexNatureUP0D(Nature.NON_T_VERTEX) +stop = pyBackTVertexUP0D() +Operators.sequential_split(start, stop, 10) +shaders_list = [ + SpatialNoiseShader(7, 120, 2, True, True), + IncreasingThicknessShader(5, 8), + ConstantColorShader(0.2, 0.2, 0.2, 1), + TextureAssignerShader(4), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/shaders.py b/release/scripts/freestyle/style_modules/shaders.py new file mode 100644 index 00000000000..b955b9d4d08 --- /dev/null +++ b/release/scripts/freestyle/style_modules/shaders.py @@ -0,0 +1,1238 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : shaders.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 11/08/2005 +# Purpose : Stroke shaders to be used for creation of stylized strokes + +from Freestyle import AdjacencyIterator, Curvature2DAngleF0D, DensityF0D, GetProjectedZF0D, \ + Interface0DIterator, MaterialF0D, Nature, Noise, Normal2DF0D, Orientation2DF1D, \ + StrokeAttribute, StrokeShader, StrokeVertexIterator, ZDiscontinuityF0D +from Freestyle import ContextFunctions as CF +from PredicatesU0D import pyVertexNatureUP0D + +import math +import mathutils +import random + +## thickness modifiers +###################### + +class pyDepthDiscontinuityThicknessShader(StrokeShader): + def __init__(self, min, max): + StrokeShader.__init__(self) + self.__min = float(min) + self.__max = float(max) + 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() + +class pyConstantThicknessShader(StrokeShader): + def __init__(self, thickness): + StrokeShader.__init__(self) + self._thickness = thickness + 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() + +class pyFXSThicknessShader(StrokeShader): + def __init__(self, thickness): + StrokeShader.__init__(self) + self._thickness = thickness + 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() + +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._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() + +class pyIncreasingThicknessShader(StrokeShader): + def __init__(self, thicknessMin, thicknessMax): + StrokeShader.__init__(self) + self._thicknessMin = thicknessMin + 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 + 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() + +class pyConstrainedIncreasingThicknessShader(StrokeShader): + def __init__(self, thicknessMin, thicknessMax, ratio): + StrokeShader.__init__(self) + self._thicknessMin = thicknessMin + self._thicknessMax = thicknessMax + 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 + 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() + +class pyDecreasingThicknessShader(StrokeShader): + def __init__(self, thicknessMax, thicknessMin): + StrokeShader.__init__(self) + self._thicknessMin = thicknessMin + self._thicknessMax = thicknessMax + 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() + +def smoothC(a, exp): + return math.pow(float(a), exp) * math.pow(2.0, exp) + +class pyNonLinearVaryingThicknessShader(StrokeShader): + def __init__(self, thicknessExtremity, thicknessMiddle, exponent): + StrokeShader.__init__(self) + self._thicknessMin = thicknessMiddle + self._thicknessMax = thicknessExtremity + self._exponent = exponent + 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 = 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() + +## Spherical linear interpolation (cos) +class pySLERPThicknessShader(StrokeShader): + def __init__(self, thicknessMin, thicknessMax, omega=1.2): + StrokeShader.__init__(self) + self._thicknessMin = thicknessMin + self._thicknessMax = thicknessMax + 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 = math.sin((1-c)*self._omega)/math.sinh(self._omega)*self._thicknessMin + math.sin(c*self._omega)/math.sinh(self._omega) * maxT + else: + t = math.sin((1-c)*self._omega)/math.sinh(self._omega)*maxT + math.sin(c*self._omega)/math.sinh(self._omega) * self._thicknessMin + it.object.attribute.thickness = (t/2.0, t/2.0) + i = i+1 + it.increment() + +class pyTVertexThickenerShader(StrokeShader): ## FIXME + def __init__(self, a=1.5, n=3): + StrokeShader.__init__(self) + self._a = a + 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() + +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) + def shade(self, stroke): + origin = mathutils.Vector([self._x, self._y]) + it = stroke.stroke_vertices_begin() + while not it.is_end: + v = it.object + p = mathutils.Vector([v.projected_x, v.projected_y]) + 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() + +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) + def shade(self, stroke): + origin = mathutils.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() + +class pyZDependingThicknessShader(StrokeShader): + def __init__(self, min, max): + StrokeShader.__init__(self) + self.__min = min + self.__max = max + 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() + 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 + thickness = (1 - z) * self.__max + z * self.__min + it.object.attribute.thickness = (thickness, thickness) + it.increment() + + +## color modifiers +################## + +class pyConstantColorShader(StrokeShader): + def __init__(self,r,g,b, a = 1): + StrokeShader.__init__(self) + self._r = r + self._g = g + self._b = 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() + +#c1->c2 +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] + 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-c)*self._c1[0] + c*self._c2[0], + (1-c)*self._c1[1] + c*self._c2[1], + (1-c)*self._c1[2] + c*self._c2[2]) + att.alpha = (1-c)*self._c1[3] + c*self._c2[3] + inc = inc+1 + it.increment() + +# c1->c2->c1 +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] + 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-2*(math.fabs(u-0.5)) + att.color = ((1-c)*self._c1[0] + c*self._c2[0], + (1-c)*self._c1[1] + c*self._c2[1], + (1-c)*self._c1[2] + c*self._c2[2]) + att.alpha = (1-c)*self._c1[3] + c*self._c2[3] + inc = inc+1 + it.increment() + +class pyMaterialColorShader(StrokeShader): + def __init__(self, threshold=50): + StrokeShader.__init__(self) + self._threshold = threshold + 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 == 0 and Y == 0 and Z == 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. * math.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 + 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 + + Y = Yn * math.pow( ((L+16.)/116.), 3.) + 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) + + it.object.attribute.color = (r, g, b) + it.increment() + +class pyRandomColorShader(StrokeShader): + def __init__(self, s=1): + StrokeShader.__init__(self) + 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() + +class py2DCurvatureColorShader(StrokeShader): + 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() + +class pyTimeColorShader(StrokeShader): + def __init__(self, step=0.01): + StrokeShader.__init__(self) + self._t = 0 + self._step = step + def shade(self, stroke): + c = self._t*1.0 + it = stroke.stroke_vertices_begin() + while not it.is_end: + it.object.attribute.color = (c,c,c) + it.increment() + self._t = self._t+self._step + +## geometry modifiers + +class pySamplingShader(StrokeShader): + def __init__(self, sampling): + StrokeShader.__init__(self) + self._sampling = sampling + def shade(self, stroke): + stroke.resample(float(self._sampling)) + stroke.update_length() + +class pyBackboneStretcherShader(StrokeShader): + def __init__(self, l): + StrokeShader.__init__(self) + 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 = mathutils.Vector([v0.projected_x, v0.projected_y]) + pn = mathutils.Vector([vn.projected_x, vn.projected_y]) + p1 = mathutils.Vector([v1.projected_x, v1.projected_y]) + pn_1 = mathutils.Vector([vn_1.projected_x, vn_1.projected_y]) + d1 = p0-p1 + d1.normalize() + dn = pn-pn_1 + dn.normalize() + newFirst = p0+d1*float(self._l) + newLast = pn+dn*float(self._l) + v0.point = newFirst + vn.point = newLast + stroke.update_length() + +class pyLengthDependingBackboneStretcherShader(StrokeShader): + 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 = mathutils.Vector([v0.projected_x, v0.projected_y]) + pn = mathutils.Vector([vn.projected_x, vn.projected_y]) + p1 = mathutils.Vector([v1.projected_x, v1.projected_y]) + pn_1 = mathutils.Vector([vn_1.projected_x, vn_1.projected_y]) + d1 = p0-p1 + d1.normalize() + dn = pn-pn_1 + dn.normalize() + newFirst = p0+d1*float(stretch) + newLast = pn+dn*float(stretch) + v0.point = newFirst + vn.point = newLast + stroke.update_length() + + +## Shader to replace a stroke by its corresponding tangent +class pyGuidingLineShader(StrokeShader): + 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() + 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 + stroke.update_length() + + +class pyBackboneStretcherNoCuspShader(StrokeShader): + def __init__(self, l): + StrokeShader.__init__(self) + 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: + p0 = v0.point + p1 = v1.point + d1 = p0-p1 + d1.normalize() + newFirst = p0+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: + pn = vn.point + pn_1 = vn_1.point + dn = pn-pn_1 + dn.normalize() + newLast = pn+dn*float(self._l) + vn.point = newLast + stroke.update_length() + +class pyDiffusion2Shader(StrokeShader): + """This shader iteratively adds an offset to the position of each + stroke vertex in the direction perpendicular to the stroke direction + at the point. The offset is scaled by the 2D curvature (i.e., how + quickly the stroke curve is) at the point.""" + def __init__(self, lambda1, nbIter): + StrokeShader.__init__(self) + self._lambda = lambda1 + self._nbIter = nbIter + self._normalInfo = Normal2DF0D() + self._curvatureInfo = Curvature2DAngleF0D() + 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() + stroke.update_length() + +class pyTipRemoverShader(StrokeShader): + def __init__(self, l): + StrokeShader.__init__(self) + self._l = l + def shade(self, stroke): + originalSize = stroke.stroke_vertices_size() + if originalSize < 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: + return + for sv in verticesToRemove: + stroke.remove_vertex(sv) + stroke.update_length() + stroke.resample(originalSize) + if stroke.stroke_vertices_size() != originalSize: + 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() + stroke.update_length() + +class pyTVertexRemoverShader(StrokeShader): + def shade(self, stroke): + if stroke.stroke_vertices_size() <= 3: + 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 = mathutils.Vector([x1,y1]) + self._v2 = mathutils.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) + +def get_fedge(it1, it2): + return it1.get_fedge(it2) + +class pyHLRShader(StrokeShader): + def shade(self, stroke): + originalSize = stroke.stroke_vertices_size() + if originalSize < 4: + return + it = stroke.stroke_vertices_begin() + invisible = 0 + it2 = StrokeVertexIterator(it) + it2.increment() + fe = 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 = 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() + +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 = 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 = 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 = 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) + +class pySinusDisplacementShader(StrokeShader): + def __init__(self, f, a): + StrokeShader.__init__(self) + self._f = f + self._a = a + 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*(math.fabs(u-0.5))) + n = n*a*math.cos(self._f*u*6.28) + #print(n.x, n.y) + v.point = p+n + #v.point = v.point+n*a*math.cos(f*v.u) + it.increment() + stroke.update_length() + +class pyPerlinNoise1DShader(StrokeShader): + def __init__(self, freq = 10, amp = 10, oct = 4, seed = -1): + StrokeShader.__init__(self) + self.__noise = Noise(seed) + self.__freq = freq + self.__amp = amp + 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() + stroke.update_length() + +class pyPerlinNoise2DShader(StrokeShader): + def __init__(self, freq = 10, amp = 10, oct = 4, seed = -1): + StrokeShader.__init__(self) + self.__noise = Noise(seed) + self.__freq = freq + self.__amp = amp + self.__oct = oct + def shade(self, stroke): + it = stroke.stroke_vertices_begin() + while not it.is_end: + v = it.object + vec = mathutils.Vector([v.projected_x, v.projected_y]) + nres = self.__noise.turbulence2(vec, self.__freq, self.__amp, self.__oct) + v.point = (v.projected_x + nres, v.projected_y + nres) + it.increment() + stroke.update_length() + +class pyBluePrintCirclesShader(StrokeShader): + def __init__(self, turns = 1, random_radius = 3, random_center = 5): + StrokeShader.__init__(self) + self.__turns = turns + self.__random_center = random_center + 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() + 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 + center = (p_min + p_max) / 2 + radius = (center.x - p_min.x + center.y - p_min.y) / 2 + p_new = mathutils.Vector([0, 0]) +####################################################### + R = self.__random_radius + C = self.__random_center + i = 0 + it = stroke.stroke_vertices_begin() + for j in range(self.__turns): + prev_radius = radius + prev_center = center + radius = radius + random.randint(-R, R) + center = center + mathutils.Vector([random.randint(-C, C), random.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 * math.cos(2 * math.pi * t) + p_new.y = c.y + r * math.sin(2 * math.pi * t) + it.object.point = p_new + i = i + 1 + it.increment() + i = 1 + verticesToRemove = [] + while not it.is_end: + verticesToRemove.append(it.object) + it.increment() + for sv in verticesToRemove: + stroke.remove_vertex(sv) + stroke.update_length() + +class pyBluePrintEllipsesShader(StrokeShader): + def __init__(self, turns = 1, random_radius = 3, random_center = 5): + StrokeShader.__init__(self) + self.__turns = turns + self.__random_center = random_center + 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() + stroke.resample(32 * self.__turns) + sv_nb = stroke.stroke_vertices_size() + sv_nb = sv_nb // self.__turns + center = (p_min + p_max) / 2 + radius = center - p_min + p_new = mathutils.Vector([0, 0]) +####################################################### + R = self.__random_radius + C = self.__random_center + i = 0 + it = stroke.stroke_vertices_begin() + for j in range(self.__turns): + prev_radius = radius + prev_center = center + radius = radius + mathutils.Vector([random.randint(-R, R), random.randint(-R, R)]) + center = center + mathutils.Vector([random.randint(-C, C), random.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 * math.cos(2 * math.pi * t) + p_new.y = c.y + r.y * math.sin(2 * math.pi * t) + it.object.point = p_new + i = i + 1 + it.increment() + i = 1 + verticesToRemove = [] + while not it.is_end: + verticesToRemove.append(it.object) + it.increment() + for sv in verticesToRemove: + stroke.remove_vertex(sv) + stroke.update_length() + + +class pyBluePrintSquaresShader(StrokeShader): + def __init__(self, turns = 1, bb_len = 10, bb_rand = 0): + StrokeShader.__init__(self) + self.__turns = turns + self.__bb_len = bb_len + self.__bb_rand = bb_rand + 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() + 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 = mathutils.Vector([p_min.x - self.__bb_len, p_min.y]) + p_first_end = mathutils.Vector([p_max.x + self.__bb_len, p_min.y]) + p_second = mathutils.Vector([p_max.x, p_min.y - self.__bb_len]) + p_second_end = mathutils.Vector([p_max.x, p_max.y + self.__bb_len]) + p_third = mathutils.Vector([p_max.x + self.__bb_len, p_max.y]) + p_third_end = mathutils.Vector([p_min.x - self.__bb_len, p_max.y]) + p_fourth = mathutils.Vector([p_min.x, p_max.y + self.__bb_len]) + p_fourth_end = mathutils.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 + for j in range(self.__turns): + p_first = p_first + mathutils.Vector([random.randint(-R, R), random.randint(-r, r)]) + p_first_end = p_first_end + mathutils.Vector([random.randint(-R, R), random.randint(-r, r)]) + p_second = p_second + mathutils.Vector([random.randint(-r, r), random.randint(-R, R)]) + p_second_end = p_second_end + mathutils.Vector([random.randint(-r, r), random.randint(-R, R)]) + p_third = p_third + mathutils.Vector([random.randint(-R, R), random.randint(-r, r)]) + p_third_end = p_third_end + mathutils.Vector([random.randint(-R, R), random.randint(-r, r)]) + p_fourth = p_fourth + mathutils.Vector([random.randint(-r, r), random.randint(-R, R)]) + p_fourth_end = p_fourth_end + mathutils.Vector([random.randint(-r, r), random.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: + if i < first: + p_new = p_first + vec_first * float(i)/float(first - 1) + if i == first - 1: + visible = False + elif i < second: + p_new = p_second + vec_second * float(i - first)/float(second - first - 1) + if i == second - 1: + visible = False + elif i < third: + p_new = p_third + vec_third * float(i - second)/float(third - second - 1) + if i == third - 1: + visible = False + else: + p_new = p_fourth + vec_fourth * float(i - third)/float(fourth - third - 1) + if i == fourth - 1: + visible = False + if it.object == 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) + it.increment() + for sv in verticesToRemove: + stroke.remove_vertex(sv) + stroke.update_length() + + +class pyBluePrintDirectedSquaresShader(StrokeShader): + 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 + def shade(self, stroke): + stroke.resample(32 * self.__turns) + p_mean = mathutils.Vector([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 + math.pow(p.x - p_mean.x, 2) + p_var_yy = p_var_yy + math.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 + sqrt_coeff = math.sqrt(trace * trace - 4 * det) + lambda1 = (trace + sqrt_coeff) / 2 + lambda2 = (trace - sqrt_coeff) / 2 +## print(lambda1, lambda2) + theta = math.atan(2 * p_var_xy / (p_var_xx - p_var_yy)) / 2 +## print(theta) + if p_var_yy > p_var_xx: + e1 = mathutils.Vector([math.cos(theta + math.pi / 2), math.sin(theta + math.pi / 2)]) * math.sqrt(lambda1) * self.__mult + e2 = mathutils.Vector([math.cos(theta + math.pi), math.sin(theta + math.pi)]) * math.sqrt(lambda2) * self.__mult + else: + e1 = mathutils.Vector([math.cos(theta), math.sin(theta)]) * math.sqrt(lambda1) * self.__mult + e2 = mathutils.Vector([math.cos(theta + math.pi / 2), math.sin(theta + math.pi / 2)]) * math.sqrt(lambda2) * self.__mult +####################################################### + sv_nb = sv_nb // self.__turns + first = sv_nb // 4 + second = 2 * first + third = 3 * first + fourth = sv_nb + bb_len1 = self.__bb_len + bb_len2 = 1 + (bb_len1 - 1) * math.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 + for j in range(self.__turns): + i = 0 + while i < sv_nb: + if i < first: + p_new = p_first + vec_first * float(i)/float(first - 1) + if i == first - 1: + visible = False + elif i < second: + p_new = p_second + vec_second * float(i - first)/float(second - first - 1) + if i == second - 1: + visible = False + elif i < third: + p_new = p_third + vec_third * float(i - second)/float(third - second - 1) + if i == third - 1: + visible = False + 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) + it.increment() + for sv in verticesToRemove: + stroke.remove_vertex(sv) + stroke.update_length() + +class pyModulateAlphaShader(StrokeShader): + def __init__(self, min = 0, max = 1): + StrokeShader.__init__(self) + self.__min = min + self.__max = max + 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() + +## 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() + +class pyDebugShader(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) diff --git a/release/scripts/freestyle/style_modules/sketchy_multiple_parameterization.py b/release/scripts/freestyle/style_modules/sketchy_multiple_parameterization.py new file mode 100644 index 00000000000..e10b0c32b55 --- /dev/null +++ b/release/scripts/freestyle/style_modules/sketchy_multiple_parameterization.py @@ -0,0 +1,43 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : sketchy_multiple_parameterization.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Builds sketchy strokes whose topology relies on a +# parameterization that covers the complete lines (visible+invisible) +# whereas only the visible portions are actually drawn + +from ChainingIterators import pySketchyChainSilhouetteIterator +from Freestyle import IncreasingColorShader, IncreasingThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, SamplingShader, SmoothingShader, SpatialNoiseShader, \ + TextureAssignerShader, TrueUP1D +from shaders import pyHLRShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(pySketchyChainSilhouetteIterator(3, False)) +shaders_list = [ + SamplingShader(2), + SpatialNoiseShader(15, 120, 2, True, True), + IncreasingThicknessShader(5, 30), + SmoothingShader(100, 0.05, 0, 0.2, 0, 0, 0, 1), + IncreasingColorShader(0, 0.2, 0, 1, 0.2, 0.7, 0.2, 1), + TextureAssignerShader(6), + pyHLRShader(), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/sketchy_topology_broken.py b/release/scripts/freestyle/style_modules/sketchy_topology_broken.py new file mode 100644 index 00000000000..7c99946c813 --- /dev/null +++ b/release/scripts/freestyle/style_modules/sketchy_topology_broken.py @@ -0,0 +1,47 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : sketchy_topology_broken.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : The topology of the strokes is, first, built +# independantly from the 3D topology of objects, +# and, second, so as to chain several times the same ViewEdge. + +from ChainingIterators import pySketchyChainingIterator +from Freestyle import IncreasingColorShader, IncreasingThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, SamplingShader, SmoothingShader, SpatialNoiseShader, \ + TextureAssignerShader, TrueUP1D +from shaders import pyBackboneStretcherNoCuspShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +## Chain 3 times each ViewEdge indpendantly from the +## initial objects topology +Operators.bidirectional_chain(pySketchyChainingIterator(3)) +shaders_list = [ + SamplingShader(4), + SpatialNoiseShader(6, 120, 2, True, True), + IncreasingThicknessShader(4, 10), + SmoothingShader(100, 0.1, 0, 0.2, 0, 0, 0, 1), + pyBackboneStretcherNoCuspShader(20), + #ConstantColorShader(0.0, 0.0, 0.0) + IncreasingColorShader(0.2, 0.2, 0.2, 1, 0.5, 0.5, 0.5, 1), + #IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + TextureAssignerShader(4), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/sketchy_topology_preserved.py b/release/scripts/freestyle/style_modules/sketchy_topology_preserved.py new file mode 100644 index 00000000000..4b525b97f04 --- /dev/null +++ b/release/scripts/freestyle/style_modules/sketchy_topology_preserved.py @@ -0,0 +1,42 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : sketchy_topology_preserved.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : The topology of the strokes is built +# so as to chain several times the same ViewEdge. +# The topology of the objects is preserved + +from ChainingIterators import pySketchyChainSilhouetteIterator +from Freestyle import ConstantColorShader, IncreasingThicknessShader, Operators, \ + QuantitativeInvisibilityUP1D, SamplingShader, SmoothingShader, SpatialNoiseShader, \ + TextureAssignerShader, TrueUP1D + +upred = QuantitativeInvisibilityUP1D(0) +Operators.select(upred) +Operators.bidirectional_chain(pySketchyChainSilhouetteIterator(3, True)) +shaders_list = [ + SamplingShader(4), + SpatialNoiseShader(20, 220, 2, True, True), + IncreasingThicknessShader(4, 8), + SmoothingShader(300, 0.05, 0, 0.2, 0, 0, 0, 0.5), + ConstantColorShader(0.6, 0.2, 0.0), + TextureAssignerShader(4), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/split_at_highest_2d_curvatures.py b/release/scripts/freestyle/style_modules/split_at_highest_2d_curvatures.py new file mode 100644 index 00000000000..4e635bc4eee --- /dev/null +++ b/release/scripts/freestyle/style_modules/split_at_highest_2d_curvatures.py @@ -0,0 +1,41 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : split_at_highest_2d_curvature.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the visible lines (chaining follows same nature lines) +# (most basic style module) + +from Freestyle import ChainSilhouetteIterator, ConstantThicknessShader, IncreasingColorShader, \ + Operators, QuantitativeInvisibilityUP1D, TextureAssignerShader, TrueUP1D +from Functions0D import pyInverseCurvature2DAngleF0D +from PredicatesU0D import pyParameterUP0D +from PredicatesU1D import pyHigherLengthUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +func = pyInverseCurvature2DAngleF0D() +Operators.recursive_split(func, pyParameterUP0D(0.4, 0.6), NotUP1D(pyHigherLengthUP1D(100)), 2) +shaders_list = [ + ConstantThicknessShader(10), + IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + TextureAssignerShader(3), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/split_at_tvertices.py b/release/scripts/freestyle/style_modules/split_at_tvertices.py new file mode 100644 index 00000000000..122b79450c6 --- /dev/null +++ b/release/scripts/freestyle/style_modules/split_at_tvertices.py @@ -0,0 +1,40 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : split_at_tvertices.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws strokes that starts and stops at Tvertices (visible or not) + +from Freestyle import ChainSilhouetteIterator, ConstantThicknessShader, IncreasingColorShader, \ + Nature, Operators, QuantitativeInvisibilityUP1D, TextureAssignerShader, TrueUP1D +from PredicatesU0D import pyVertexNatureUP0D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +start = pyVertexNatureUP0D(Nature.T_VERTEX) +## use the same predicate to decide where to start and where to stop +## the strokes: +Operators.sequential_split(start, start, 10) +shaders_list = [ + ConstantThicknessShader(5), + IncreasingColorShader(1, 0, 0, 1, 0, 1, 0, 1), + TextureAssignerShader(3), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/stroke_texture.py b/release/scripts/freestyle/style_modules/stroke_texture.py new file mode 100644 index 00000000000..07fce377866 --- /dev/null +++ b/release/scripts/freestyle/style_modules/stroke_texture.py @@ -0,0 +1,38 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : stroke_texture.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws textured strokes (illustrate the StrokeTextureShader shader) + +from Freestyle import BezierCurveShader, ChainSilhouetteIterator, ConstantColorShader, \ + ConstantThicknessShader, Operators, QuantitativeInvisibilityUP1D, SamplingShader, \ + Stroke, StrokeTextureShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + SamplingShader(3), + BezierCurveShader(4), + StrokeTextureShader("washbrushAlpha.bmp", Stroke.DRY_MEDIUM, True), + ConstantThicknessShader(40), + ConstantColorShader(0, 0, 0, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/suggestive.py b/release/scripts/freestyle/style_modules/suggestive.py new file mode 100644 index 00000000000..3d0e031acc9 --- /dev/null +++ b/release/scripts/freestyle/style_modules/suggestive.py @@ -0,0 +1,38 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : suggestive.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Draws the suggestive contours. +# ***** The suggestive contours must be enabled +# in the options dialog ***** + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, IncreasingThicknessShader, \ + Nature, Operators, QuantitativeInvisibilityUP1D, TrueUP1D +from PredicatesU1D import pyNatureUP1D +from logical_operators import AndUP1D, NotUP1D + +upred = AndUP1D(pyNatureUP1D(Nature.SUGGESTIVE_CONTOUR), QuantitativeInvisibilityUP1D(0)) +Operators.select(upred) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred)) +shaders_list = [ + IncreasingThicknessShader(1, 3), + ConstantColorShader(0.2, 0.2, 0.2, 1), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/thickness_fof_depth_discontinuity.py b/release/scripts/freestyle/style_modules/thickness_fof_depth_discontinuity.py new file mode 100644 index 00000000000..9dff325bb00 --- /dev/null +++ b/release/scripts/freestyle/style_modules/thickness_fof_depth_discontinuity.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : thickness_fof_depth_discontinuity.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Assigns to strokes a thickness that depends on the depth discontinuity + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D +from shaders import pyDepthDiscontinuityThicknessShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + SamplingShader(1), + ConstantThicknessShader(3), + ConstantColorShader(0.0, 0.0, 0.0), + pyDepthDiscontinuityThicknessShader(0.8, 6), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/tipremover.py b/release/scripts/freestyle/style_modules/tipremover.py new file mode 100644 index 00000000000..92918840bec --- /dev/null +++ b/release/scripts/freestyle/style_modules/tipremover.py @@ -0,0 +1,36 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : tipremover.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Removes strokes extremities + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TipRemoverShader, TrueUP1D +from logical_operators import NotUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + SamplingShader(5), + ConstantThicknessShader(3), + ConstantColorShader(0, 0, 0), + TipRemoverShader(20), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/tvertex_remover.py b/release/scripts/freestyle/style_modules/tvertex_remover.py new file mode 100644 index 00000000000..962425fa009 --- /dev/null +++ b/release/scripts/freestyle/style_modules/tvertex_remover.py @@ -0,0 +1,37 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : tvertex_remover.py +# Author : Stephane Grabli +# Date : 04/08/2005 +# Purpose : Removes TVertices + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, IncreasingThicknessShader, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, TrueUP1D +from logical_operators import NotUP1D +from shaders import pyTVertexRemoverShader + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0))) +shaders_list = [ + IncreasingThicknessShader(3, 5), + ConstantColorShader(0.2, 0.2, 0.2, 1), + SamplingShader(10.0), + pyTVertexRemoverShader(), + ] +Operators.create(TrueUP1D(), shaders_list) diff --git a/release/scripts/freestyle/style_modules/uniformpruning_zsort.py b/release/scripts/freestyle/style_modules/uniformpruning_zsort.py new file mode 100644 index 00000000000..b9fcb33e895 --- /dev/null +++ b/release/scripts/freestyle/style_modules/uniformpruning_zsort.py @@ -0,0 +1,39 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Filename : uniformpruning_zsort.py +# Authors : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin +# Date : 08/04/2005 + +from Freestyle import ChainSilhouetteIterator, ConstantColorShader, ConstantThicknessShader, IntegrationType, \ + Operators, QuantitativeInvisibilityUP1D, SamplingShader, Stroke, StrokeTextureShader +from PredicatesB1D import pyZBP1D +from PredicatesU1D import pyDensityUP1D + +Operators.select(QuantitativeInvisibilityUP1D(0)) +Operators.bidirectional_chain(ChainSilhouetteIterator()) +#Operators.sequential_split(pyVertexNatureUP0D(Nature.VIEW_VERTEX), 2) +Operators.sort(pyZBP1D()) +shaders_list = [ + StrokeTextureShader("smoothAlpha.bmp", Stroke.OPAQUE_MEDIUM, False), + ConstantThicknessShader(3), + SamplingShader(5.0), + ConstantColorShader(0, 0, 0, 1), + ] +Operators.create(pyDensityUP1D(2, 0.05, IntegrationType.MEAN, 4), shaders_list) +#Operators.create(pyDensityFunctorUP1D(8, 0.03, pyGetInverseProjectedZF1D(), 0, 1, IntegrationType.MEAN), shaders_list) diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index 3ff02420bbd..64851a3a4c1 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -22,7 +22,7 @@ if "bpy" in locals(): from imp import reload as _reload for val in _modules_loaded.values(): _reload(val) -_modules = ( +_modules = [ "add_mesh_torus", "anim", "clip", @@ -44,16 +44,18 @@ _modules = ( "vertexpaint_dirt", "view3d", "wm", -) +] + +import bpy + +if bpy.app.build_options.freestyle: + _modules.append("freestyle") __import__(name=__name__, fromlist=_modules) _namespace = globals() -_modules_loaded = {name: _namespace[name] for name in _modules} +_modules_loaded = {name: _namespace[name] for name in _modules if name != 'bpy'} del _namespace -import bpy - - def register(): bpy.utils.register_module(__name__) diff --git a/release/scripts/startup/bl_operators/freestyle.py b/release/scripts/startup/bl_operators/freestyle.py new file mode 100644 index 00000000000..19e6f67ba77 --- /dev/null +++ b/release/scripts/startup/bl_operators/freestyle.py @@ -0,0 +1,135 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import sys +import bpy + +from bpy.props import (EnumProperty, StringProperty) + + +class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator): + '''Fill the Range Min/Max entries by the min/max distance between selected mesh objects and the source object + (either a user-specified object or the active camera)''' + bl_idname = "scene.freestyle_fill_range_by_selection" + bl_label = "Fill Range by Selection" + + type = EnumProperty(name="Type", description="Type of the modifier to work on", + items=(("COLOR", "Color", "Color modifier type"), + ("ALPHA", "Alpha", "Alpha modifier type"), + ("THICKNESS", "Thickness", "Thickness modifier type"))) + name = StringProperty(name="Name", description="Name of the modifier to work on") + + def execute(self, context): + rl = context.scene.render.layers.active + lineset = rl.freestyle_settings.linesets.active + linestyle = lineset.linestyle + # Find the modifier to work on + if self.type == 'COLOR': + m = linestyle.color_modifiers[self.name] + elif self.type == 'ALPHA': + m = linestyle.alpha_modifiers[self.name] + else: + m = linestyle.thickness_modifiers[self.name] + # Find the source object + if m.type == 'DISTANCE_FROM_CAMERA': + source = context.scene.camera + elif m.type == 'DISTANCE_FROM_OBJECT': + if m.target is None: + self.report({'ERROR'}, "Target object not specified") + return {'CANCELLED'} + source = m.target + else: + self.report({'ERROR'}, "Unexpected modifier type: " + m.type) + return {'CANCELLED'} + # Find selected mesh objects + selection = [ob for ob in context.scene.objects if ob.select and ob.type == 'MESH' and ob.name != source.name] + if len(selection) > 0: + # Compute the min/max distance between selected mesh objects and the source + min_dist = sys.float_info.max + max_dist = -min_dist + for ob in selection: + for vert in ob.data.vertices: + dist = (ob.matrix_world * vert.co - source.location).length + min_dist = min(dist, min_dist) + max_dist = max(dist, max_dist) + # Fill the Range Min/Max entries with the computed distances + m.range_min = min_dist + m.range_max = max_dist + return {'FINISHED'} + + +class SCENE_OT_freestyle_add_edge_marks_to_keying_set(bpy.types.Operator): + '''Add the data paths to the Freestyle Edge Mark property of selected edges to the active keying set''' + bl_idname = "scene.freestyle_add_edge_marks_to_keying_set" + bl_label = "Add Edge Marks to Keying Set" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + ob = context.active_object + return (ob and ob.type == 'MESH') + + def execute(self, context): + # active keying set + scene = context.scene + ks = scene.keying_sets.active + if ks is None: + ks = scene.keying_sets.new(idname="FreestyleEdgeMarkKeyingSet", name="Freestyle Edge Mark Keying Set") + ks.bl_description = "" + # add data paths to the keying set + ob = context.active_object + ob_mode = ob.mode + mesh = ob.data + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + for i, edge in enumerate(mesh.edges): + if not edge.hide and edge.select: + path = 'edges[%d].use_freestyle_edge_mark' % i + ks.paths.add(mesh, path, index=0) + bpy.ops.object.mode_set(mode=ob_mode, toggle=False) + return {'FINISHED'} + + +class SCENE_OT_freestyle_add_face_marks_to_keying_set(bpy.types.Operator): + '''Add the data paths to the Freestyle Face Mark property of selected polygons to the active keying set''' + bl_idname = "scene.freestyle_add_face_marks_to_keying_set" + bl_label = "Add Face Marks to Keying Set" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + ob = context.active_object + return (ob and ob.type == 'MESH') + + def execute(self, context): + # active keying set + scene = context.scene + ks = scene.keying_sets.active + if ks is None: + ks = scene.keying_sets.new(idname="FreestyleFaceMarkKeyingSet", name="Freestyle Face Mark Keying Set") + ks.bl_description = "" + # add data paths to the keying set + ob = context.active_object + ob_mode = ob.mode + mesh = ob.data + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + for i, polygon in enumerate(mesh.polygons): + if not polygon.hide and polygon.select: + path = 'polygons[%d].use_freestyle_face_mark' % i + ks.paths.add(mesh, path, index=0) + bpy.ops.object.mode_set(mode=ob_mode, toggle=False) + return {'FINISHED'} diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py index 18fa5ea50b6..868fa2a311c 100644 --- a/release/scripts/startup/bl_ui/__init__.py +++ b/release/scripts/startup/bl_ui/__init__.py @@ -24,7 +24,7 @@ if "bpy" in locals(): from imp import reload as _reload for val in _modules_loaded.values(): _reload(val) -_modules = ( +_modules = [ "properties_animviz", "properties_constraint", "properties_data_armature", @@ -53,6 +53,7 @@ _modules = ( "properties_physics_smoke", "properties_physics_softbody", "properties_render", + "properties_render_layer", "properties_scene", "properties_texture", "properties_world", @@ -74,16 +75,18 @@ _modules = ( "space_userpref", "space_view3d", "space_view3d_toolbar", -) +] + +import bpy + +if bpy.app.build_options.freestyle: + _modules.append("properties_freestyle") __import__(name=__name__, fromlist=_modules) _namespace = globals() -_modules_loaded = {name: _namespace[name] for name in _modules} +_modules_loaded = {name: _namespace[name] for name in _modules if name != 'bpy'} del _namespace -import bpy - - def register(): bpy.utils.register_module(__name__) diff --git a/release/scripts/startup/bl_ui/properties_freestyle.py b/release/scripts/startup/bl_ui/properties_freestyle.py new file mode 100644 index 00000000000..0665c0aba4a --- /dev/null +++ b/release/scripts/startup/bl_ui/properties_freestyle.py @@ -0,0 +1,668 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> +import bpy +from bpy.types import Menu, Panel, UIList +from bl_ui.properties_render import RenderButtonsPanel +from bl_ui.properties_render_layer import RenderLayerButtonsPanel + + +# Render properties + +class RenderFreestyleButtonsPanel(RenderButtonsPanel): + # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here + + @classmethod + def poll(cls, context): + if not super().poll(context): + return False + return bpy.app.build_options.freestyle + + +class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel): + bl_label = "Freestyle" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw_header(self, context): + rd = context.scene.render + self.layout.prop(rd, "use_freestyle", text="") + + def draw(self, context): + rd = context.scene.render + + layout = self.layout + layout.active = rd.use_freestyle + + row = layout.row() + row.label(text="Line Thickness:") + row.prop(rd, "line_thickness_mode", expand=True) + row = layout.row() + row.active = (rd.line_thickness_mode == 'ABSOLUTE') + row.prop(rd, "unit_line_thickness") + + +# Render layer properties + +class RenderLayerFreestyleButtonsPanel(RenderLayerButtonsPanel): + # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here + + @classmethod + def poll(cls, context): + if not super().poll(context): + return False + rd = context.scene.render + return bpy.app.build_options.freestyle and rd.use_freestyle and rd.layers.active + + +class RenderLayerFreestyleEditorButtonsPanel(RenderLayerFreestyleButtonsPanel): + # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here + + @classmethod + def poll(cls, context): + if not super().poll(context): + return False + rl = context.scene.render.layers.active + return rl and rl.freestyle_settings.mode == 'EDITOR' + + +class RENDERLAYER_UL_linesets(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + lineset = item + if self.layout_type in {'DEFAULT', 'COMPACT'}: + layout.label(lineset.name, icon_value=icon) + layout.prop(lineset, "show_render", text="", index=index) + elif self.layout_type in {'GRID'}: + layout.alignment = 'CENTER' + layout.label("", icon_value=icon) + +##ifdef WITH_FREESTYLE +# else if (RNA_struct_is_a(itemptr->type, &RNA_SceneRenderLayer) || +# RNA_struct_is_a(itemptr->type, &RNA_FreestyleLineSet)) { +##else +# else if (RNA_struct_is_a(itemptr->type, &RNA_SceneRenderLayer)) { +##endif +# uiItemL(sub, name, icon); +# uiBlockSetEmboss(block, UI_EMBOSS); +# uiDefButR(block, OPTION, 0, "", 0, 0, UI_UNIT_X, UI_UNIT_Y, itemptr, "use", 0, 0, 0, 0, 0, NULL); +# } + + +class RENDER_MT_lineset_specials(Menu): + bl_label = "Lineset Specials" + + def draw(self, context): + layout = self.layout + layout.operator("scene.freestyle_lineset_copy", icon='COPYDOWN') + layout.operator("scene.freestyle_lineset_paste", icon='PASTEDOWN') + + +class RENDERLAYER_PT_freestyle(RenderLayerFreestyleButtonsPanel, Panel): + bl_label = "Freestyle" + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw(self, context): + rd = context.scene.render + rl = rd.layers.active + freestyle = rl.freestyle_settings + + layout = self.layout + layout.active = rl.use_freestyle + layout.prop(freestyle, "mode", text="Control mode") + + col = layout.column() + col.label(text="Edge Detection Options:") + split = col.split() + sub = split.column() + sub.prop(freestyle, "crease_angle") + sub.prop(freestyle, "use_culling") + sub = split.column() + sub.prop(freestyle, "use_smoothness") + sub.prop(freestyle, "use_material_boundaries") + col.prop(freestyle, "use_advanced_options") + # Advanced options are hidden by default to warn new users + if freestyle.use_advanced_options: + split = col.split() + sub = split.column() + sub.active = freestyle.use_advanced_options + if freestyle.mode == 'SCRIPT': + sub.prop(freestyle, "use_ridges_and_valleys") + sub.prop(freestyle, "sphere_radius") + sub = split.column() + sub.active = freestyle.use_advanced_options + if freestyle.mode == 'SCRIPT': + sub.prop(freestyle, "use_suggestive_contours") + sub.prop(freestyle, "kr_derivative_epsilon") + + if freestyle.mode == 'SCRIPT': + split = layout.split() + split.label("Style modules:") + split.operator("scene.freestyle_module_add", text="Add") + for i, module in enumerate(freestyle.modules): + box = layout.box() + box.context_pointer_set("freestyle_module", module) + row = box.row(align=True) + row.prop(module, "use", text="") + row.prop(module, "module_path", text="") + row.operator("scene.freestyle_module_remove", icon='X', text="") + row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP' + row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + + +class RENDERLAYER_PT_freestyle_lineset(RenderLayerFreestyleEditorButtonsPanel, Panel): + bl_label = "Freestyle Line Set" + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw_edge_type_buttons(self, box, lineset, edge_type): + # property names + select_edge_type = "select_" + edge_type + exclude_edge_type = "exclude_" + edge_type + # draw edge type buttons + row = box.row(align=True) + row.prop(lineset, select_edge_type) + sub = row.column() + sub.prop(lineset, exclude_edge_type, text="") + sub.active = getattr(lineset, select_edge_type) + + def draw(self, context): + rd = context.scene.render + rl = rd.layers.active + freestyle = rl.freestyle_settings + lineset = freestyle.linesets.active + + layout = self.layout + layout.active = rl.use_freestyle + + col = layout.column() + row = col.row() + rows = 5 if lineset else 2 + row.template_list("RENDERLAYER_UL_linesets", "", freestyle, "linesets", freestyle.linesets, "active_index", rows=rows) + + sub = row.column() + subsub = sub.column(align=True) + subsub.operator("scene.freestyle_lineset_add", icon='ZOOMIN', text="") + subsub.operator("scene.freestyle_lineset_remove", icon='ZOOMOUT', text="") + subsub.menu("RENDER_MT_lineset_specials", icon='DOWNARROW_HLT', text="") + if lineset: + sub.separator() + subsub = sub.column(align=True) + subsub.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP' + subsub.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + + col.prop(lineset, "name") + + col = layout.column() + col.label(text="Selection By:") + row = col.row(align=True) + row.prop(lineset, "select_by_visibility", text="Visibility", toggle=True) + row.prop(lineset, "select_by_edge_types", text="Edge Types", toggle=True) + row.prop(lineset, "select_by_face_marks", text="Face Marks", toggle=True) + row.prop(lineset, "select_by_group", text="Group", toggle=True) + row.prop(lineset, "select_by_image_border", text="Image Border", toggle=True) + + if lineset.select_by_visibility: + col.label(text="Visibility:") + row = col.row(align=True) + row.prop(lineset, "visibility", expand=True) + if lineset.visibility == 'RANGE': + row = col.row(align=True) + row.prop(lineset, "qi_start") + row.prop(lineset, "qi_end") + + if lineset.select_by_edge_types: + col.label(text="Edge Types:") + row = col.row() + row.prop(lineset, "edge_type_negation", expand=True) + row.prop(lineset, "edge_type_combination", expand=True) + + split = col.split() + sub = split.column() + self.draw_edge_type_buttons(sub, lineset, "silhouette") + self.draw_edge_type_buttons(sub, lineset, "border") + self.draw_edge_type_buttons(sub, lineset, "contour") + self.draw_edge_type_buttons(sub, lineset, "suggestive_contour") + self.draw_edge_type_buttons(sub, lineset, "ridge_valley") + sub = split.column() + self.draw_edge_type_buttons(sub, lineset, "crease") + self.draw_edge_type_buttons(sub, lineset, "edge_mark") + self.draw_edge_type_buttons(sub, lineset, "external_contour") + self.draw_edge_type_buttons(sub, lineset, "material_boundary") + + if lineset.select_by_face_marks: + col.label(text="Face Marks:") + row = col.row() + row.prop(lineset, "face_mark_negation", expand=True) + row.prop(lineset, "face_mark_condition", expand=True) + + if lineset.select_by_group: + col.label(text="Group:") + row = col.row() + row.prop(lineset, "group", text="") + row.prop(lineset, "group_negation", expand=True) + + +class RENDERLAYER_PT_freestyle_linestyle(RenderLayerFreestyleEditorButtonsPanel, Panel): + bl_label = "Freestyle Line Style" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw_modifier_box_header(self, box, modifier): + row = box.row() + row.context_pointer_set("modifier", modifier) + if modifier.expanded: + icon = 'TRIA_DOWN' + else: + icon = 'TRIA_RIGHT' + row.prop(modifier, "expanded", text="", icon=icon, emboss=False) + # TODO: Use icons rather than text label, would save some room! + row.label(text=modifier.rna_type.name) + row.prop(modifier, "name", text="") + if modifier.use: + icon = 'RESTRICT_RENDER_OFF' + else: + icon = 'RESTRICT_RENDER_ON' + row.prop(modifier, "use", text="", icon=icon) + sub = row.row(align=True) + sub.operator("scene.freestyle_modifier_copy", icon='NONE', text="Copy") + sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP' + sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + sub.operator("scene.freestyle_modifier_remove", icon='X', text="") + + def draw_modifier_common(self, box, modifier): + row = box.row() + row.prop(modifier, "blend", text="") + row.prop(modifier, "influence") + + def draw_modifier_color_ramp_common(self, box, modifier, has_range): + box.template_color_ramp(modifier, "color_ramp", expand=True) + if has_range: + row = box.row(align=True) + row.prop(modifier, "range_min") + row.prop(modifier, "range_max") + + def draw_modifier_curve_common(self, box, modifier, has_range, has_value): + row = box.row() + row.prop(modifier, "mapping", text="") + sub = row.column() + sub.prop(modifier, "invert") + if modifier.mapping == 'CURVE': + sub.active = False + box.template_curve_mapping(modifier, "curve") + if has_range: + row = box.row(align=True) + row.prop(modifier, "range_min") + row.prop(modifier, "range_max") + if has_value: + row = box.row(align=True) + row.prop(modifier, "value_min") + row.prop(modifier, "value_max") + + def draw_color_modifier(self, context, modifier): + layout = self.layout + + col = layout.column(align=True) + self.draw_modifier_box_header(col.box(), modifier) + if modifier.expanded: + box = col.box() + self.draw_modifier_common(box, modifier) + + if modifier.type == 'ALONG_STROKE': + self.draw_modifier_color_ramp_common(box, modifier, False) + + elif modifier.type == 'DISTANCE_FROM_OBJECT': + box.prop(modifier, "target") + self.draw_modifier_color_ramp_common(box, modifier, True) + prop = box.operator("scene.freestyle_fill_range_by_selection") + prop.type = 'COLOR' + prop.name = modifier.name + + elif modifier.type == 'DISTANCE_FROM_CAMERA': + self.draw_modifier_color_ramp_common(box, modifier, True) + prop = box.operator("scene.freestyle_fill_range_by_selection") + prop.type = 'COLOR' + prop.name = modifier.name + + elif modifier.type == 'MATERIAL': + row = box.row() + row.prop(modifier, "material_attr", text="") + sub = row.column() + sub.prop(modifier, "use_ramp") + if modifier.material_attr in {'DIFF', 'SPEC'}: + sub.active = True + show_ramp = modifier.use_ramp + else: + sub.active = False + show_ramp = True + if show_ramp: + self.draw_modifier_color_ramp_common(box, modifier, False) + + def draw_alpha_modifier(self, context, modifier): + layout = self.layout + + col = layout.column(align=True) + self.draw_modifier_box_header(col.box(), modifier) + if modifier.expanded: + box = col.box() + self.draw_modifier_common(box, modifier) + + if modifier.type == 'ALONG_STROKE': + self.draw_modifier_curve_common(box, modifier, False, False) + + elif modifier.type == 'DISTANCE_FROM_OBJECT': + box.prop(modifier, "target") + self.draw_modifier_curve_common(box, modifier, True, False) + prop = box.operator("scene.freestyle_fill_range_by_selection") + prop.type = 'ALPHA' + prop.name = modifier.name + + elif modifier.type == 'DISTANCE_FROM_CAMERA': + self.draw_modifier_curve_common(box, modifier, True, False) + prop = box.operator("scene.freestyle_fill_range_by_selection") + prop.type = 'ALPHA' + prop.name = modifier.name + + elif modifier.type == 'MATERIAL': + box.prop(modifier, "material_attr", text="") + self.draw_modifier_curve_common(box, modifier, False, False) + + def draw_thickness_modifier(self, context, modifier): + layout = self.layout + + col = layout.column(align=True) + self.draw_modifier_box_header(col.box(), modifier) + if modifier.expanded: + box = col.box() + self.draw_modifier_common(box, modifier) + + if modifier.type == 'ALONG_STROKE': + self.draw_modifier_curve_common(box, modifier, False, True) + + elif modifier.type == 'DISTANCE_FROM_OBJECT': + box.prop(modifier, "target") + self.draw_modifier_curve_common(box, modifier, True, True) + prop = box.operator("scene.freestyle_fill_range_by_selection") + prop.type = 'THICKNESS' + prop.name = modifier.name + + elif modifier.type == 'DISTANCE_FROM_CAMERA': + self.draw_modifier_curve_common(box, modifier, True, True) + prop = box.operator("scene.freestyle_fill_range_by_selection") + prop.type = 'THICKNESS' + prop.name = modifier.name + + elif modifier.type == 'MATERIAL': + box.prop(modifier, "material_attr", text="") + self.draw_modifier_curve_common(box, modifier, False, True) + + elif modifier.type == 'CALLIGRAPHY': + box.prop(modifier, "orientation") + row = box.row(align=True) + row.prop(modifier, "min_thickness") + row.prop(modifier, "max_thickness") + + def draw_geometry_modifier(self, context, modifier): + layout = self.layout + + col = layout.column(align=True) + self.draw_modifier_box_header(col.box(), modifier) + if modifier.expanded: + box = col.box() + + if modifier.type == 'SAMPLING': + box.prop(modifier, "sampling") + + elif modifier.type == 'BEZIER_CURVE': + box.prop(modifier, "error") + + elif modifier.type == 'SINUS_DISPLACEMENT': + split = box.split() + col = split.column() + col.prop(modifier, "wavelength") + col.prop(modifier, "amplitude") + col = split.column() + col.prop(modifier, "phase") + + elif modifier.type == 'SPATIAL_NOISE': + split = box.split() + col = split.column() + col.prop(modifier, "amplitude") + col.prop(modifier, "scale") + col.prop(modifier, "octaves") + col = split.column() + col.prop(modifier, "smooth") + col.prop(modifier, "pure_random") + + elif modifier.type == 'PERLIN_NOISE_1D': + split = box.split() + col = split.column() + col.prop(modifier, "frequency") + col.prop(modifier, "amplitude") + col.prop(modifier, "seed") + col = split.column() + col.prop(modifier, "octaves") + col.prop(modifier, "angle") + + elif modifier.type == 'PERLIN_NOISE_2D': + split = box.split() + col = split.column() + col.prop(modifier, "frequency") + col.prop(modifier, "amplitude") + col.prop(modifier, "seed") + col = split.column() + col.prop(modifier, "octaves") + col.prop(modifier, "angle") + + elif modifier.type == 'BACKBONE_STRETCHER': + box.prop(modifier, "backbone_length") + + elif modifier.type == 'TIP_REMOVER': + box.prop(modifier, "tip_length") + + elif modifier.type == 'POLYGONIZATION': + box.prop(modifier, "error") + + elif modifier.type == 'GUIDING_LINES': + box.prop(modifier, "offset") + + elif modifier.type == 'BLUEPRINT': + row = box.row() + row.prop(modifier, "shape", expand=True) + box.prop(modifier, "rounds") + row = box.row() + if modifier.shape in {'CIRCLES', 'ELLIPSES'}: + row.prop(modifier, "random_radius") + row.prop(modifier, "random_center") + elif modifier.shape == 'SQUARES': + row.prop(modifier, "backbone_length") + row.prop(modifier, "random_backbone") + + elif modifier.type == '2D_OFFSET': + row = box.row(align=True) + row.prop(modifier, "start") + row.prop(modifier, "end") + row = box.row(align=True) + row.prop(modifier, "x") + row.prop(modifier, "y") + + elif modifier.type == '2D_TRANSFORM': + box.prop(modifier, "pivot") + if modifier.pivot == 'PARAM': + box.prop(modifier, "pivot_u") + elif modifier.pivot == 'ABSOLUTE': + row = box.row(align=True) + row.prop(modifier, "pivot_x") + row.prop(modifier, "pivot_y") + row = box.row(align=True) + row.prop(modifier, "scale_x") + row.prop(modifier, "scale_y") + box.prop(modifier, "angle") + + def draw(self, context): + rd = context.scene.render + rl = rd.layers.active + lineset = rl.freestyle_settings.linesets.active + + layout = self.layout + layout.active = rl.use_freestyle + + if lineset is None: + return + linestyle = lineset.linestyle + + layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new") + row = layout.row(align=True) + row.prop(linestyle, "panel", expand=True) + if linestyle.panel == 'STROKES': + ## Chaining + layout.label(text="Chaining:") + split = layout.split(align=True) + # First column + col = split.column() + col.prop(linestyle, "use_chaining", text="Enable Chaining") + sub = col.row() + sub.active = linestyle.use_chaining + sub.prop(linestyle, "same_object") + # Second column + col = split.column() + col.active = linestyle.use_chaining + col.prop(linestyle, "chaining", text="") + if linestyle.chaining == 'SKETCHY': + col.prop(linestyle, "rounds") + + ## Splitting + layout.label(text="Splitting:") + split = layout.split(align=True) + # First column + col = split.column() + row = col.row(align=True) + row.prop(linestyle, "use_min_angle", text="") + sub = row.row() + sub.active = linestyle.use_min_angle + sub.prop(linestyle, "min_angle") + row = col.row(align=True) + row.prop(linestyle, "use_max_angle", text="") + sub = row.row() + sub.active = linestyle.use_max_angle + sub.prop(linestyle, "max_angle") + # Second column + col = split.column() + row = col.row(align=True) + row.prop(linestyle, "use_split_length", text="") + sub = row.row() + sub.active = linestyle.use_split_length + sub.prop(linestyle, "split_length", text="2D Length") + row = col.row(align=True) + row.prop(linestyle, "material_boundary") + # End of columns + row = layout.row(align=True) + row.prop(linestyle, "use_split_pattern", text="") + sub = row.row() + sub.active = linestyle.use_split_pattern + sub.prop(linestyle, "split_dash1", text="D1") + sub.prop(linestyle, "split_gap1", text="G1") + sub.prop(linestyle, "split_dash2", text="D2") + sub.prop(linestyle, "split_gap2", text="G2") + sub.prop(linestyle, "split_dash3", text="D3") + sub.prop(linestyle, "split_gap3", text="G3") + + ## Selection + layout.label(text="Selection:") + split = layout.split(align=True) + # First column + col = split.column() + row = col.row(align=True) + row.prop(linestyle, "use_min_length", text="") + sub = row.row() + sub.active = linestyle.use_min_length + sub.prop(linestyle, "min_length") + # Second column + col = split.column() + row = col.row(align=True) + row.prop(linestyle, "use_max_length", text="") + sub = row.row() + sub.active = linestyle.use_max_length + sub.prop(linestyle, "max_length") + + ## Caps + layout.label(text="Caps:") + row = layout.row(align=True) + row.prop(linestyle, "caps", expand=True) + + ## Dashed lines + layout.label(text="Dashed Line:") + row = layout.row(align=True) + row.prop(linestyle, "use_dashed_line", text="") + sub = row.row() + sub.active = linestyle.use_dashed_line + sub.prop(linestyle, "dash1", text="D1") + sub.prop(linestyle, "gap1", text="G1") + sub.prop(linestyle, "dash2", text="D2") + sub.prop(linestyle, "gap2", text="G2") + sub.prop(linestyle, "dash3", text="D3") + sub.prop(linestyle, "gap3", text="G3") + + elif linestyle.panel == 'COLOR': + col = layout.column() + row = col.row() + row.label(text="Base Color:") + row.prop(linestyle, "color", text="") + col.label(text="Modifiers:") + col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.color_modifiers: + self.draw_color_modifier(context, modifier) + + elif linestyle.panel == 'ALPHA': + col = layout.column() + row = col.row() + row.label(text="Base Transparency:") + row.prop(linestyle, "alpha") + col.label(text="Modifiers:") + col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.alpha_modifiers: + self.draw_alpha_modifier(context, modifier) + + elif linestyle.panel == 'THICKNESS': + col = layout.column() + row = col.row() + row.label(text="Base Thickness:") + row.prop(linestyle, "thickness") + row = col.row() + row.prop(linestyle, "thickness_position", expand=True) + row = col.row() + row.prop(linestyle, "thickness_ratio") + row.active = (linestyle.thickness_position == 'RELATIVE') + col = layout.column() + col.label(text="Modifiers:") + col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.thickness_modifiers: + self.draw_thickness_modifier(context, modifier) + + elif linestyle.panel == 'GEOMETRY': + col = layout.column() + col.label(text="Modifiers:") + col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier") + for modifier in linestyle.geometry_modifiers: + self.draw_geometry_modifier(context, modifier) + + elif linestyle.panel == 'MISC': + pass + + +if __name__ == "__main__": # only for live edit. + bpy.utils.register_module(__name__) diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py index dc9b5e99788..c8cc433d3af 100644 --- a/release/scripts/startup/bl_ui/properties_render.py +++ b/release/scripts/startup/bl_ui/properties_render.py @@ -19,7 +19,7 @@ # <pep8 compliant> import bpy -from bpy.types import Menu, Panel, UIList +from bpy.types import Menu, Panel class RENDER_MT_presets(Menu): @@ -43,18 +43,6 @@ class RENDER_MT_framerate_presets(Menu): draw = Menu.draw_preset -class RENDER_UL_renderlayers(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - # assert(isinstance(item, bpy.types.SceneRenderLayer) - layer = item - if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.label(text=layer.name, translate=False, icon_value=icon) - layout.prop(layer, "use", text="", index=index) - elif self.layout_type in {'GRID'}: - layout.alignment = 'CENTER' - layout.label(text="", icon_value=icon) - - class RenderButtonsPanel(): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -63,8 +51,8 @@ class RenderButtonsPanel(): @classmethod def poll(cls, context): - rd = context.scene.render - return context.scene and (rd.engine in cls.COMPAT_ENGINES) + scene = context.scene + return scene and (scene.render.engine in cls.COMPAT_ENGINES) class RENDER_PT_render(RenderButtonsPanel, Panel): @@ -84,110 +72,6 @@ class RENDER_PT_render(RenderButtonsPanel, Panel): layout.prop(rd, "display_mode", text="Display") -class RENDER_PT_layers(RenderButtonsPanel, Panel): - bl_label = "Layers" - bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER'} - - def draw(self, context): - layout = self.layout - - scene = context.scene - rd = scene.render - - row = layout.row() - row.template_list("RENDER_UL_renderlayers", "", rd, "layers", rd.layers, "active_index", rows=2) - - col = row.column(align=True) - col.operator("scene.render_layer_add", icon='ZOOMIN', text="") - col.operator("scene.render_layer_remove", icon='ZOOMOUT', text="") - - row = layout.row() - rl = rd.layers.active - if rl: - row.prop(rl, "name") - row.prop(rd, "use_single_layer", text="", icon_only=True) - - split = layout.split() - - col = split.column() - col.prop(scene, "layers", text="Scene") - col.label(text="") - col.prop(rl, "light_override", text="Light") - col.prop(rl, "material_override", text="Material") - - col = split.column() - col.prop(rl, "layers", text="Layer") - col.label(text="Mask Layers:") - col.prop(rl, "layers_zmask", text="") - - layout.separator() - layout.label(text="Include:") - - split = layout.split() - - col = split.column() - col.prop(rl, "use_zmask") - row = col.row() - row.prop(rl, "invert_zmask", text="Negate") - row.active = rl.use_zmask - col.prop(rl, "use_all_z") - - col = split.column() - col.prop(rl, "use_solid") - col.prop(rl, "use_halo") - col.prop(rl, "use_ztransp") - - col = split.column() - col.prop(rl, "use_sky") - col.prop(rl, "use_edge_enhance") - col.prop(rl, "use_strand") - - layout.separator() - - split = layout.split() - - col = split.column() - col.label(text="Passes:") - col.prop(rl, "use_pass_combined") - col.prop(rl, "use_pass_z") - col.prop(rl, "use_pass_vector") - col.prop(rl, "use_pass_normal") - col.prop(rl, "use_pass_uv") - col.prop(rl, "use_pass_mist") - col.prop(rl, "use_pass_object_index") - col.prop(rl, "use_pass_material_index") - col.prop(rl, "use_pass_color") - - col = split.column() - col.label() - col.prop(rl, "use_pass_diffuse") - row = col.row() - row.prop(rl, "use_pass_specular") - row.prop(rl, "exclude_specular", text="") - row = col.row() - row.prop(rl, "use_pass_shadow") - row.prop(rl, "exclude_shadow", text="") - row = col.row() - row.prop(rl, "use_pass_emit") - row.prop(rl, "exclude_emit", text="") - row = col.row() - row.prop(rl, "use_pass_ambient_occlusion") - row.prop(rl, "exclude_ambient_occlusion", text="") - row = col.row() - row.prop(rl, "use_pass_environment") - row.prop(rl, "exclude_environment", text="") - row = col.row() - row.prop(rl, "use_pass_indirect") - row.prop(rl, "exclude_indirect", text="") - row = col.row() - row.prop(rl, "use_pass_reflection") - row.prop(rl, "exclude_reflection", text="") - row = col.row() - row.prop(rl, "use_pass_refraction") - row.prop(rl, "exclude_refraction", text="") - - class RENDER_PT_dimensions(RenderButtonsPanel, Panel): bl_label = "Dimensions" COMPAT_ENGINES = {'BLENDER_RENDER'} diff --git a/release/scripts/startup/bl_ui/properties_render_layer.py b/release/scripts/startup/bl_ui/properties_render_layer.py new file mode 100644 index 00000000000..af11fa505ff --- /dev/null +++ b/release/scripts/startup/bl_ui/properties_render_layer.py @@ -0,0 +1,178 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> +import bpy +from bpy.types import Menu, Panel, UIList + + +class RenderLayerButtonsPanel(): + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "render_layer" + # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here + + @classmethod + def poll(cls, context): + scene = context.scene + return scene and (scene.render.engine in cls.COMPAT_ENGINES) + + +class RENDERLAYER_UL_renderlayers(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + # assert(isinstance(item, bpy.types.SceneRenderLayer) + layer = item + if self.layout_type in {'DEFAULT', 'COMPACT'}: + layout.label(layer.name, icon_value=icon) + layout.prop(layer, "use", text="", index=index) + elif self.layout_type in {'GRID'}: + layout.alignment = 'CENTER' + layout.label("", icon_value=icon) + +# else if (RNA_struct_is_a(itemptr->type, &RNA_SceneRenderLayer)) { +# uiItemL(sub, name, icon); +# uiBlockSetEmboss(block, UI_EMBOSS); +# uiDefButR(block, OPTION, 0, "", 0, 0, UI_UNIT_X, UI_UNIT_Y, itemptr, "use", 0, 0, 0, 0, 0, NULL); +# } + + +class RENDERLAYER_PT_layers(RenderLayerButtonsPanel, Panel): + bl_label = "Layers" + bl_options = {'HIDE_HEADER'} + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw(self, context): + layout = self.layout + + scene = context.scene + rd = scene.render + + row = layout.row() + row.template_list("RENDERLAYER_UL_renderlayers", "", rd, "layers", rd.layers, "active_index", rows=2) + + col = row.column(align=True) + col.operator("scene.render_layer_add", icon='ZOOMIN', text="") + col.operator("scene.render_layer_remove", icon='ZOOMOUT', text="") + + row = layout.row() + rl = rd.layers.active + if rl: + row.prop(rl, "name") + row.prop(rd, "use_single_layer", text="", icon_only=True) + + +class RENDERLAYER_PT_layer_options(RenderLayerButtonsPanel, Panel): + bl_label = "Layer" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw(self, context): + layout = self.layout + + scene = context.scene + rd = scene.render + rl = rd.layers.active + + split = layout.split() + + col = split.column() + col.prop(scene, "layers", text="Scene") + col.label(text="") + col.prop(rl, "light_override", text="Light") + col.prop(rl, "material_override", text="Material") + + col = split.column() + col.prop(rl, "layers", text="Layer") + col.label(text="Mask Layers:") + col.prop(rl, "layers_zmask", text="") + + layout.separator() + layout.label(text="Include:") + + split = layout.split() + + col = split.column() + col.prop(rl, "use_zmask") + row = col.row() + row.prop(rl, "invert_zmask", text="Negate") + row.active = rl.use_zmask + col.prop(rl, "use_all_z") + + col = split.column() + col.prop(rl, "use_solid") + col.prop(rl, "use_halo") + col.prop(rl, "use_ztransp") + + col = split.column() + col.prop(rl, "use_sky") + col.prop(rl, "use_edge_enhance") + col.prop(rl, "use_strand") + if bpy.app.build_options.freestyle: + row = col.row() + row.prop(rl, "use_freestyle") + row.active = rd.use_freestyle + + +class RENDERLAYER_PT_layer_passes(RenderLayerButtonsPanel, Panel): + bl_label = "Render Passes" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_RENDER'} + + def draw_pass_type_buttons(self, box, rl, pass_type): + # property names + use_pass_type = "use_pass_" + pass_type + exclude_pass_type = "exclude_" + pass_type + # draw pass type buttons + row = box.row() + row.prop(rl, use_pass_type) + row.prop(rl, exclude_pass_type, text="") + + def draw(self, context): + layout = self.layout + + scene = context.scene + rd = scene.render + rl = rd.layers.active + + split = layout.split() + + col = split.column() + col.prop(rl, "use_pass_combined") + col.prop(rl, "use_pass_z") + col.prop(rl, "use_pass_vector") + col.prop(rl, "use_pass_normal") + col.prop(rl, "use_pass_uv") + col.prop(rl, "use_pass_mist") + col.prop(rl, "use_pass_object_index") + col.prop(rl, "use_pass_material_index") + col.prop(rl, "use_pass_color") + + col = split.column() + col.prop(rl, "use_pass_diffuse") + self.draw_pass_type_buttons(col, rl, "specular") + self.draw_pass_type_buttons(col, rl, "shadow") + self.draw_pass_type_buttons(col, rl, "emit") + self.draw_pass_type_buttons(col, rl, "ambient_occlusion") + self.draw_pass_type_buttons(col, rl, "environment") + self.draw_pass_type_buttons(col, rl, "indirect") + self.draw_pass_type_buttons(col, rl, "reflection") + self.draw_pass_type_buttons(col, rl, "refraction") + + +if __name__ == "__main__": # only for live edit. + bpy.utils.register_module(__name__) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index a82d9221166..8f65eb5b726 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -1906,6 +1906,12 @@ class VIEW3D_MT_edit_mesh_edges(Menu): layout.separator() + if context.scene and bpy.app.build_options.freestyle: + layout.operator("mesh.mark_freestyle_edge").clear = False + layout.operator("mesh.mark_freestyle_edge", text="Clear Freestyle Edge").clear = True + + layout.separator() + layout.operator("mesh.edge_rotate", text="Rotate Edge CW").use_ccw = False layout.operator("mesh.edge_rotate", text="Rotate Edge CCW").use_ccw = True @@ -1946,6 +1952,12 @@ class VIEW3D_MT_edit_mesh_faces(Menu): layout.separator() + if context.scene and bpy.app.build_options.freestyle: + layout.operator("mesh.mark_freestyle_face").clear = False + layout.operator("mesh.mark_freestyle_face", text="Clear Freestyle Face").clear = True + + layout.separator() + layout.operator("mesh.quads_convert_to_tris") layout.operator("mesh.tris_convert_to_quads") @@ -2534,18 +2546,26 @@ class VIEW3D_PT_view3d_meshdisplay(Panel): split = layout.split() + with_freestyle = context.scene and bpy.app.build_options.freestyle + col = split.column() col.label(text="Overlays:") col.prop(mesh, "show_faces", text="Faces") col.prop(mesh, "show_edges", text="Edges") col.prop(mesh, "show_edge_crease", text="Creases") + if with_freestyle: + col.prop(mesh, "show_edge_seams", text="Seams") col = split.column() col.label() - col.prop(mesh, "show_edge_seams", text="Seams") + if not with_freestyle: + col.prop(mesh, "show_edge_seams", text="Seams") col.prop(mesh, "show_edge_sharp", text="Sharp", text_ctxt=i18n_contexts.plural) col.prop(mesh, "show_edge_bevel_weight", text="Weights") - + if with_freestyle: + col.prop(mesh, "show_freestyle_edge_marks", text="Edge Marks") + col.prop(mesh, "show_freestyle_face_marks", text="Face Marks") + col = layout.column() col.separator() |