D319: Freestyle Python scripts update.

This revision is meant to update Freestyle's Python scripts to make full usage
of the new features of Python and Freestyle's Python API.

Freestyle's Python scripts are pretty old already, and were never given much
attention. With the 2.7x generation of Blender coming up, this is an excellent
time to update Freestyle's Python scripts, hopefully adding some new features
and achieving some speed improvements on the way.

Main goals:
* use for loops where possible
* general cleanup, making use of more recent python features (generators,
  ternary operator, ect.)
* update the documentation on the way (it's lacking atm)


Differential revision: https://developer.blender.org/D319

Author: flokkievids (Folkert de Vries)

Reviewed by: kjym3 (Tamito Kajiyama)
This commit is contained in:
Tamito Kajiyama
2014-06-24 13:52:12 +09:00
parent f37ec65f80
commit ce729677db
5 changed files with 1304 additions and 1540 deletions

View File

@@ -23,6 +23,20 @@ rules. Also intended to be a collection of examples for defining
chaining iterators in Python
"""
__all__ = (
"pyChainSilhouetteIterator",
"pyChainSilhouetteGenericIterator",
"pyExternalContourChainingIterator",
"pySketchyChainSilhouetteIterator",
"pySketchyChainingIterator",
"pyFillOcclusionsRelativeChainingIterator",
"pyFillOcclusionsAbsoluteChainingIterator",
"pyFillOcclusionsAbsoluteAndRelativeChainingIterator",
"pyFillQi0AbsoluteAndRelativeChainingIterator",
"pyNoIdChainSilhouetteIterator",
)
# module members
from _freestyle import (
ChainPredicateIterator,
@@ -41,11 +55,30 @@ from freestyle.predicates import (
)
from freestyle.utils import (
ContextFunctions as CF,
stroke_normal,
get_chain_length,
find_matching_vertex,
)
import bpy
NATURES = (
Nature.SILHOUETTE,
Nature.BORDER,
Nature.CREASE,
Nature.MATERIAL_BOUNDARY,
Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,
Nature.VALLEY,
Nature.RIDGE
)
def nature_in_preceding(nature, index):
""" Returns True if given nature appears before index, else False """
return any(nature & nat for nat in NATURES[:index])
class pyChainSilhouetteIterator(ChainingIterator):
"""Natural chaining iterator
@@ -61,43 +94,28 @@ class pyChainSilhouetteIterator(ChainingIterator):
pass
def traverse(self, iter):
winner = None
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
break
it.increment()
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for i in range(len(natures)):
currentNature = self.current_edge.nature
if (natures[i] & currentNature) != 0:
count=0
while not it.is_end:
visitNext = 0
oNature = it.object.nature
if (oNature & natures[i]) != 0:
if natures[i] != oNature:
for j in range(i):
if (natures[j] & oNature) != 0:
visitNext = 1
break
if visitNext != 0:
break
count = count+1
winner = it.object
it.increment()
if count != 1:
winner = None
break
return winner
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
return find_matching_vertex(mate.id, it)
## case of NonTVertex
winner = None
for i, nat in enumerate(NATURES):
if (nat & self.current_edge.nature):
for ve in it:
ve_nat = ve.nature
if (ve_nat & nat):
# search for matches in previous natures. if match -> break
if nat != ve_nat and nature_in_preceding(ve_nat, index=i):
break
# a second match must be an error
if winner is not None:
return None
# assign winner
winner = ve
return winner
class pyChainSilhouetteGenericIterator(ChainingIterator):
@@ -120,47 +138,30 @@ class pyChainSilhouetteGenericIterator(ChainingIterator):
pass
def traverse(self, iter):
winner = None
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
break
it.increment()
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for i in range(len(natures)):
currentNature = self.current_edge.nature
if (natures[i] & currentNature) != 0:
count=0
while not it.is_end:
visitNext = 0
oNature = it.object.nature
ve = it.object
if ve.id == self.current_edge.id:
it.increment()
continue
if (oNature & natures[i]) != 0:
if natures[i] != oNature:
for j in range(i):
if (natures[j] & oNature) != 0:
visitNext = 1
break
if visitNext != 0:
break
count = count+1
winner = ve
it.increment()
if count != 1:
winner = None
break
return winner
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
return find_matching_vertex(mate.id, it)
## case of NonTVertex
winner = None
for i, nat in enumerate(NATURES):
if (nat & self.current_edge.nature):
for ve in it:
ve_nat = ve.nature
if ve.id == self.current_edge.id:
continue
if (ve_nat & nat):
if nat != ve_nat and nature_in_preceding(ve_nat, index=i):
break
if winner is not None:
return None
winner = ve
return winner
return None
class pyExternalContourChainingIterator(ChainingIterator):
@@ -168,49 +169,40 @@ class pyExternalContourChainingIterator(ChainingIterator):
def __init__(self):
ChainingIterator.__init__(self, False, True, None, True)
self._isExternalContour = ExternalContourUP1D()
self.ExternalContour = ExternalContourUP1D()
def init(self):
self._nEdges = 0
self._isInSelection = 1
def checkViewEdge(self, ve, orientation):
if orientation != 0:
vertex = ve.second_svertex()
else:
vertex = ve.first_svertex()
it = AdjacencyIterator(vertex,1,1)
while not it.is_end:
ave = it.object
if self._isExternalContour(ave):
return True
it.increment()
if bpy.app.debug_freestyle:
vertex = (ve.first_viewvertex if orientation else
ve.last_viewvertex)
it = AdjacencyIterator(vertex, True, True)
result = any(self.ExternalContour(ave) for ave in it)
# report if there is no result (that's bad)
if not result and bpy.app.debug_freestyle:
print("pyExternalContourChainingIterator : didn't find next edge")
return False
return result
def traverse(self, iter):
winner = None
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 += 1
it = AdjacencyIterator(iter)
time_stamp = CF.get_time_stamp()
for ve in it:
if self.ExternalContour(ve) and ve.time_stamp == time_stamp:
winner = ve
self._nEdges = self._nEdges+1
if winner is None:
orient = 1
it = AdjacencyIterator(iter)
while not it.is_end:
ve = it.object
if it.is_incoming:
orient = 0
good = self.checkViewEdge(ve,orient)
if good != 0:
for ve in it:
if self.checkViewEdge(ve, not it.is_incoming):
winner = ve
it.increment()
return winner
@@ -227,58 +219,49 @@ class pySketchyChainSilhouetteIterator(ChainingIterator):
def __init__(self, nRounds=3,stayInSelection=True):
ChainingIterator.__init__(self, stayInSelection, False, None, True)
self._timeStamp = CF.get_time_stamp()+nRounds
self._timeStamp = CF.get_time_stamp() + nRounds
self._nRounds = nRounds
def init(self):
self._timeStamp = CF.get_time_stamp()+self._nRounds
self._timeStamp = CF.get_time_stamp() + self._nRounds
# keeping this local saves passing a reference to 'self' around
def make_sketchy(self, ve):
"""
Creates the skeychy effect by causing the chain to run from
the start again. (loop over itself again)
"""
if ve is None:
ve = self.current_edge
if ve.chaining_time_stamp == self._timeStamp:
return None
return ve
def traverse(self, iter):
winner = None
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
break
it.increment()
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for i in range(len(natures)):
currentNature = self.current_edge.nature
if (natures[i] & currentNature) != 0:
count=0
while not it.is_end:
visitNext = 0
oNature = it.object.nature
ve = it.object
if ve.id == self.current_edge.id:
it.increment()
continue
if (oNature & natures[i]) != 0:
if (natures[i] != oNature) != 0:
for j in range(i):
if (natures[j] & oNature) != 0:
visitNext = 1
break
if visitNext != 0:
break
count = count+1
winner = ve
it.increment()
if count != 1:
winner = None
break
if winner is None:
winner = self.current_edge
if winner.chaining_time_stamp == self._timeStamp:
winner = None
return winner
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
return self.make_sketchy(find_matching_vertex(mate.id, it))
## case of NonTVertex
winner = None
for i, nat in enumerate(NATURES):
if (nat & self.current_edge.nature):
for ve in it:
if ve.id == self.current_edge.id:
continue
ve_nat = ve.nature
if (ve_nat & nat):
if nat != ve_nat and nature_in_preceding(ve_nat, i):
break
if winner is not None:
return self.make_sketchy(None)
winner = ve
break
return self.make_sketchy(winner)
class pySketchyChainingIterator(ChainingIterator):
@@ -289,30 +272,30 @@ class pySketchyChainingIterator(ChainingIterator):
"""
def __init__(self, nRounds=3, stayInSelection=True):
ChainingIterator.__init__(self, stayInSelection, False, None, True)
self._timeStamp = CF.get_time_stamp()+nRounds
self._timeStamp = CF.get_time_stamp() + nRounds
self._nRounds = nRounds
self.t = False
def init(self):
self._timeStamp = CF.get_time_stamp()+self._nRounds
self._timeStamp = CF.get_time_stamp() + self._nRounds
def traverse(self, iter):
winner = None
found = False
it = AdjacencyIterator(iter)
while not it.is_end:
ve = it.object
if ve.id == self.current_edge.id:
for ve in AdjacencyIterator(iter):
if self.current_edge.id == ve.id:
found = True
it.increment()
continue
winner = ve
it.increment()
if not found:
# This is a fatal error condition: self.current_edge must be found
# among the edges seen by the AdjacencyIterator [bug #35695].
if bpy.app.debug_freestyle:
print('pySketchyChainingIterator: current edge not found')
return None
if winner is None:
winner = self.current_edge
if winner.chaining_time_stamp == self._timeStamp:
@@ -330,97 +313,61 @@ class pyFillOcclusionsRelativeChainingIterator(ChainingIterator):
def __init__(self, percent):
ChainingIterator.__init__(self, False, True, None, True)
self._length = 0
self._length = 0.0
self._percent = float(percent)
self.timestamp = CF.get_time_stamp()
def init(self):
# A chain's length should preferably be evaluated only once.
# Therefore, the chain length is reset here.
self._length = 0
self._length = 0.0
def traverse(self, iter):
winner = None
winnerOrientation = False
winnerOrientation = 0
#print(self.current_edge.id.first, self.current_edge.id.second)
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
winnerOrientation = not it.is_incoming
break
it.increment()
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
winner = find_matching_vertex(mate.id, it)
winnerOrientation = not it.is_incoming if not it.is_end else False
## case of NonTVertex
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for nat in natures:
if (self.current_edge.nature & nat) != 0:
count=0
while not it.is_end:
ve = it.object
if (ve.nature & nat) != 0:
count = count+1
for nat in NATURES:
if (self.current_edge.nature & nat):
for ve in it:
if (ve.nature & nat):
if winner is not None:
return None
winner = ve
winnerOrientation = not it.is_incoming
it.increment()
if count != 1:
winner = None
break
if winner is not None:
# check whether this edge was part of the selection
if winner.time_stamp != CF.get_time_stamp():
#print("---", winner.id.first, winner.id.second)
# if not, let's check whether it's short enough with
# respect to the chain made without staying in the selection
#------------------------------------------------------------
# Did we compute the prospective chain length already ?
if self._length == 0:
#if not, let's do it
_it = pyChainSilhouetteGenericIterator(False, False)
_it.begin = winner
_it.current_edge = winner
_it.orientation = winnerOrientation
_it.init()
while not _it.is_end:
ve = _it.object
#print("--------", ve.id.first, ve.id.second)
self._length = self._length + ve.length_2d
_it.increment()
if _it.is_begin:
break;
_it.begin = winner
_it.current_edge = winner
_it.orientation = winnerOrientation
if not _it.is_begin:
_it.decrement()
while (not _it.is_end) and (not _it.is_begin):
ve = _it.object
#print("--------", ve.id.first, ve.id.second)
self._length = self._length + ve.length_2d
_it.decrement()
# let's do the comparison:
# nw let's compute the length of this connex non selected part:
connexl = 0
# check timestamp to see if this edge was part of the selection
if winner is not None and winner.time_stamp != self.timestamp:
# if the edge wasn't part of the selection, let's see
# whether it's short enough (with respect to self.percent)
# to be included.
if self._length == 0.0:
self._length = get_chain_length(winner, winnerOrientation)
# check if the gap can be bridged
connexl = 0.0
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while (not _cit.is_end) and _cit.object.time_stamp != self.timestamp:
connexl += _cit.object.length_2d
_cit.increment()
if _cit.is_begin: break
if connexl > self._percent * self._length:
return None
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp():
ve = _cit.object
#print("-------- --------", ve.id.first, ve.id.second)
connexl = connexl + ve.length_2d
_cit.increment()
if connexl > self._percent * self._length:
winner = None
return winner
@@ -433,6 +380,7 @@ class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator):
def __init__(self, length):
ChainingIterator.__init__(self, False, True, None, True)
self._length = float(length)
self.timestamp = CF.get_time_stamp()
def init(self):
pass
@@ -440,53 +388,41 @@ class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator):
def traverse(self, iter):
winner = None
winnerOrientation = False
#print(self.current_edge.id.first, self.current_edge.id.second)
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
winnerOrientation = not it.is_incoming
break
it.increment()
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
winner = find_matching_vertex(mate.id, it)
winnerOrientation = not it.is_incoming if not it.is_end else False
## case of NonTVertex
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for nat in natures:
if (self.current_edge.nature & nat) != 0:
count=0
while not it.is_end:
ve = it.object
if (ve.nature & nat) != 0:
count = count+1
for nat in NATURES:
if (self.current_edge.nature & nat):
for ve in it:
if (ve.nature & nat):
if winner is not None:
return None
winner = ve
winnerOrientation = not it.is_incoming
it.increment()
if count != 1:
winner = None
break
if winner is not None:
# check whether this edge was part of the selection
if winner.time_stamp != CF.get_time_stamp():
#print("---", winner.id.first, winner.id.second)
# nw let's compute the length of this connex non selected part:
connexl = 0
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp():
ve = _cit.object
#print("-------- --------", ve.id.first, ve.id.second)
connexl = connexl + ve.length_2d
_cit.increment()
if connexl > self._length:
winner = None
if winner is not None and winner.time_stamp != self.timestamp:
connexl = 0.0
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while (not _cit.is_end) and _cit.object.time_stamp != self.timestamp:
connexl += _cit.object.length_2d
_cit.increment()
if _cit.is_begin: break
if connexl > self._length:
return None
return winner
@@ -500,7 +436,7 @@ class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator):
"""
def __init__(self, percent, l):
ChainingIterator.__init__(self, False, True, None, True)
self._length = 0
self._length = 0.0
self._absLength = l
self._percent = float(percent)
@@ -508,88 +444,48 @@ class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator):
# each time we're evaluating a chain length
# we try to do it once. Thus we reinit
# the chain length here:
self._length = 0
self._length = 0.0
def traverse(self, iter):
winner = None
winnerOrientation = False
#print(self.current_edge.id.first, self.current_edge.id.second)
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
winnerOrientation = not it.is_incoming
break
it.increment()
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
winner = find_matching_vertex(mate.id, it)
winnerOrientation = not it.is_incoming if not it.is_end else False
## case of NonTVertex
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for nat in natures:
if (self.current_edge.nature & nat) != 0:
count=0
while not it.is_end:
ve = it.object
if (ve.nature & nat) != 0:
count = count+1
for nat in NATURES:
if (self.current_edge.nature & nat):
for ve in it:
if (ve.nature & nat):
if winner is not None:
return None
winner = ve
winnerOrientation = not it.is_incoming
it.increment()
if count != 1:
winner = None
break
if winner is not None:
# check whether this edge was part of the selection
if winner.time_stamp != CF.get_time_stamp():
#print("---", winner.id.first, winner.id.second)
# if not, let's check whether it's short enough with
# respect to the chain made without staying in the selection
#------------------------------------------------------------
# Did we compute the prospective chain length already ?
if self._length == 0:
#if not, let's do it
_it = pyChainSilhouetteGenericIterator(False, False)
_it.begin = winner
_it.current_edge = winner
_it.orientation = winnerOrientation
_it.init()
while not _it.is_end:
ve = _it.object
#print("--------", ve.id.first, ve.id.second)
self._length = self._length + ve.length_2d
_it.increment()
if _it.is_begin:
break;
_it.begin = winner
_it.current_edge = winner
_it.orientation = winnerOrientation
if not _it.is_begin:
_it.decrement()
while (not _it.is_end) and (not _it.is_begin):
ve = _it.object
#print("--------", ve.id.first, ve.id.second)
self._length = self._length + ve.length_2d
_it.decrement()
# let's do the comparison:
# nw let's compute the length of this connex non selected part:
connexl = 0
if winner is not None and winner.time_stamp != CF.get_time_stamp():
if self._length == 0.0:
self._length = get_chain_length(winner, winnerOrientation)
connexl = 0.0
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while _cit.is_end == 0 and _cit.object.time_stamp != CF.get_time_stamp():
ve = _cit.object
#print("-------- --------", ve.id.first, ve.id.second)
connexl = connexl + ve.length_2d
while (not _cit.is_end) and _cit.object.time_stamp != CF.get_time_stamp():
connexl += _cit.object.length_2d
_cit.increment()
if _cit.is_begin: break
if (connexl > self._percent * self._length) or (connexl > self._absLength):
winner = None
return None
return winner
@@ -603,96 +499,55 @@ class pyFillQi0AbsoluteAndRelativeChainingIterator(ChainingIterator):
"""
def __init__(self, percent, l):
ChainingIterator.__init__(self, False, True, None, True)
self._length = 0
self._length = 0.0
self._absLength = l
self._percent = float(percent)
self._percent = percent
def init(self):
# A chain's length should preverably be evaluated only once.
# Therefore, the chain length is reset here.
self._length = 0
self._length = 0.0
def traverse(self, iter):
winner = None
winnerOrientation = False
#print(self.current_edge.id.first, self.current_edge.id.second)
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
if ve.id == mateVE.id:
winner = ve
winnerOrientation = not it.is_incoming
break
it.increment()
## case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
mate = vertex.get_mate(self.current_edge)
winner = find_matching_vertex(mate.id, it)
winnerOrientation = not it.is_incoming if not it.is_end else False
## case of NonTVertex
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for nat in natures:
if (self.current_edge.nature & nat) != 0:
count=0
while not it.is_end:
ve = it.object
if (ve.nature & nat) != 0:
count = count+1
for nat in NATURES:
if (self.current_edge.nature & nat):
for ve in it:
if (ve.nature & nat):
if winner is not None:
return None
winner = ve
winnerOrientation = not it.is_incoming
it.increment()
if count != 1:
winner = None
break
if winner is not None:
# check whether this edge was part of the selection
if winner.qi != 0:
#print("---", winner.id.first, winner.id.second)
# if not, let's check whether it's short enough with
# respect to the chain made without staying in the selection
#------------------------------------------------------------
# Did we compute the prospective chain length already ?
if self._length == 0:
#if not, let's do it
_it = pyChainSilhouetteGenericIterator(False, False)
_it.begin = winner
_it.current_edge = winner
_it.orientation = winnerOrientation
_it.init()
while not _it.is_end:
ve = _it.object
#print("--------", ve.id.first, ve.id.second)
self._length = self._length + ve.length_2d
_it.increment()
if _it.is_begin:
break;
_it.begin = winner
_it.current_edge = winner
_it.orientation = winnerOrientation
if not _it.is_begin:
_it.decrement()
while (not _it.is_end) and (not _it.is_begin):
ve = _it.object
#print("--------", ve.id.first, ve.id.second)
self._length = self._length + ve.length_2d
_it.decrement()
# let's do the comparison:
# nw let's compute the length of this connex non selected part:
if winner is not None and winner.qi:
if self._length == 0.0:
self._length = get_chain_length(winner, winnerOrientation)
connexl = 0
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while not _cit.is_end and _cit.object.qi != 0:
ve = _cit.object
#print("-------- --------", ve.id.first, ve.id.second)
connexl = connexl + ve.length_2d
while (not _cit.is_end) and _cit.object.qi != 0:
connexl += _cit.object.length_2d
_cit.increment()
if _cit.is_begin: break
if (connexl > self._percent * self._length) or (connexl > self._absLength):
winner = None
return None
return winner
@@ -717,63 +572,44 @@ class pyNoIdChainSilhouetteIterator(ChainingIterator):
def traverse(self, iter):
winner = None
it = AdjacencyIterator(iter)
tvertex = self.next_vertex
if type(tvertex) is TVertex:
mateVE = tvertex.get_mate(self.current_edge)
while not it.is_end:
ve = it.object
feB = self.current_edge.last_fedge
feA = ve.first_fedge
vB = feB.second_svertex
vA = feA.first_svertex
# case of TVertex
vertex = self.next_vertex
if type(vertex) is TVertex:
for ve in it:
# case one
vA = self.current_edge.last_fedge.second_svertex
vB = ve.first_fedge.first_svertex
if vA.id.first == vB.id.first:
winner = ve
break
feA = self.current_edge.first_fedge
feB = ve.last_fedge
vB = feB.second_svertex
vA = feA.first_svertex
return ve
# case two
vA = self.current_edge.first_fedge.first_svertex
vB = ve.last_fedge.second_svertex
if vA.id.first == vB.id.first:
winner = ve
break
feA = self.current_edge.last_fedge
feB = ve.last_fedge
vB = feB.second_svertex
vA = feA.second_svertex
return ve
# case three
vA = self.current_edge.last_fedge.second_svertex
vB = ve.last_fedge.second_svertex
if vA.id.first == vB.id.first:
winner = ve
break
feA = self.current_edge.first_fedge
feB = ve.first_fedge
vB = feB.first_svertex
vA = feA.first_svertex
return ve
# case four
vA = self.current_edge.first_fedge.first_svertex
vB = ve.first_fedge.first_svertex
if vA.id.first == vB.id.first:
winner = ve
break
it.increment()
return ve
return None
## case of NonTVertex
else:
## case of NonTVertex
natures = [Nature.SILHOUETTE,Nature.BORDER,Nature.CREASE,Nature.MATERIAL_BOUNDARY,Nature.EDGE_MARK,
Nature.SUGGESTIVE_CONTOUR,Nature.VALLEY,Nature.RIDGE]
for i in range(len(natures)):
currentNature = self.current_edge.nature
if (natures[i] & currentNature) != 0:
count=0
while not it.is_end:
visitNext = 0
oNature = it.object.nature
if (oNature & natures[i]) != 0:
if natures[i] != oNature:
for j in range(i):
if (natures[j] & oNature) != 0:
visitNext = 1
break
if visitNext != 0:
break
count = count+1
winner = it.object
it.increment()
if count != 1:
winner = None
break
return winner
for i, nat in enumerate(NATURES):
if (nat & self.current_edge.nature):
for ve in it:
ve_nat = ve.nature
if (ve_nat & nat):
if (nat != ve_nat) and any(n & ve_nat for n in NATURES[:i]):
break
if winner is not None:
return
winner = ve
return winner
return None

View File

@@ -91,8 +91,8 @@ from freestyle.utils import integrate
from mathutils import Vector
## Functions for 0D elements (vertices)
#######################################
# -- Functions for 0D elements (vertices) -- #
class CurveMaterialF0D(UnaryFunction0DMaterial):
@@ -104,7 +104,7 @@ class CurveMaterialF0D(UnaryFunction0DMaterial):
cp = inter.object
assert(isinstance(cp, CurvePoint))
fe = cp.first_svertex.get_fedge(cp.second_svertex)
assert(fe is not None)
assert(fe is not None), "CurveMaterialF0D: fe is None"
return fe.material if fe.is_smooth else fe.material_left
@@ -140,11 +140,7 @@ class pyDensityAnisotropyF0D(UnaryFunction0DDouble):
c_3 = self.d3Density(inter)
cMax = max(max(c_0,c_1), max(c_2,c_3))
cMin = min(min(c_0,c_1), min(c_2,c_3))
if c_iso == 0:
v = 0
else:
v = (cMax-cMin)/c_iso
return v
return 0 if (c_iso == 0) else (cMax-cMin) / c_iso
class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f):
@@ -161,9 +157,9 @@ class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f):
def __call__(self, iter):
p = iter.object.point_2d
gx = CF.read_complete_view_map_pixel(self._l, int(p.x+self._step), int(p.y)) - \
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y+self._step)) - \
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
return Vector((gx, gy))
@@ -171,19 +167,18 @@ class pyViewMapGradientNormF0D(UnaryFunction0DDouble):
def __init__(self, l):
UnaryFunction0DDouble.__init__(self)
self._l = l
self._step = pow(2,self._l)
self._step = pow(2, self._l)
def __call__(self, iter):
p = iter.object.point_2d
gx = CF.read_complete_view_map_pixel(self._l, int(p.x+self._step), int(p.y)) - \
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y+self._step)) - \
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
grad = Vector((gx, gy))
return grad.length
gx = CF.read_complete_view_map_pixel(self._l, int(p.x + self._step), int(p.y)) - \
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y + self._step)) - \
CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
return Vector((gx, gy)).length
## Functions for 1D elements (curves)
#####################################
# -- Functions for 1D elements (curves) -- #
class pyGetInverseProjectedZF1D(UnaryFunction1DDouble):

View File

@@ -51,6 +51,7 @@ from freestyle.types import (
TVertex,
UnaryPredicate0D,
UnaryPredicate1D,
Id,
)
from freestyle.functions import (
Curvature2DAngleF0D,
@@ -70,14 +71,15 @@ from freestyle.functions import (
pyDensityAnisotropyF1D,
pyViewMapGradientNormF1D,
)
import random
## Unary predicates for 0D elements (vertices)
##############################################
# -- Unary predicates for 0D elements (vertices) -- #
class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D):
def __init__(self,a):
def __init__(self, a):
UnaryPredicate0D.__init__(self)
self._a = a
@@ -88,15 +90,15 @@ class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D):
class pyUEqualsUP0D(UnaryPredicate0D):
def __init__(self,u, w):
def __init__(self, u, w):
UnaryPredicate0D.__init__(self)
self._u = u
self._w = w
self._func = pyCurvilinearLengthF0D()
def __call__(self, inter):
func = pyCurvilinearLengthF0D()
u = func(inter)
return (u > (self._u-self._w)) and (u < (self._u+self._w))
u = self._func(inter)
return (u > (self._u - self._w)) and (u < (self._u + self._w))
class pyVertexNatureUP0D(UnaryPredicate0D):
@@ -105,26 +107,23 @@ class pyVertexNatureUP0D(UnaryPredicate0D):
self._nature = nature
def __call__(self, inter):
v = inter.object
return (v.nature & self._nature) != 0
return bool(inter.object.nature & self._nature)
## check whether an Interface0DIterator
## is a TVertex and is the one that is
## hidden (inferred from the context)
class pyBackTVertexUP0D(UnaryPredicate0D):
"""
Check whether an Interface0DIterator
references a TVertex and is the one that is
hidden (inferred from the context)
"""
def __init__(self):
UnaryPredicate0D.__init__(self)
self._getQI = QuantitativeInvisibilityF0D()
def __call__(self, iter):
if (iter.object.nature & Nature.T_VERTEX) == 0:
if not (iter.object.nature & Nature.T_VERTEX) or iter.is_end:
return False
if iter.is_end:
return False
if self._getQI(iter) != 0:
return True
return False
return self._getQI(iter) != 0
class pyParameterUP0DGoodOne(UnaryPredicate0D):
@@ -135,7 +134,7 @@ class pyParameterUP0DGoodOne(UnaryPredicate0D):
def __call__(self, inter):
u = inter.u
return ((u>=self._m) and (u<=self._M))
return ((u >= self._m) and (u <= self._M))
class pyParameterUP0D(UnaryPredicate0D):
@@ -143,36 +142,39 @@ class pyParameterUP0D(UnaryPredicate0D):
UnaryPredicate0D.__init__(self)
self._m = pmin
self._M = pmax
self._func = Curvature2DAngleF0D()
def __call__(self, inter):
func = Curvature2DAngleF0D()
c = func(inter)
b1 = (c>0.1)
c = self._func(inter)
b1 = (c > 0.1)
u = inter.u
b = ((u>=self._m) and (u<=self._M))
return b and b1
b = ((u >= self._m) and (u <= self._M))
return (b and b1)
# -- Unary predicates for 1D elements (curves) -- #
## Unary predicates for 1D elements (curves)
############################################
class AndUP1D(UnaryPredicate1D):
def __init__(self, pred1, pred2):
def __init__(self, *predicates):
UnaryPredicate1D.__init__(self)
self.__pred1 = pred1
self.__pred2 = pred2
self.predicates = predicates
if len(self.predicates) < 2:
raise ValueError("Expected two or more UnaryPredicate1D")
def __call__(self, inter):
return self.__pred1(inter) and self.__pred2(inter)
return all(pred(inter) for pred in self.predicates)
class OrUP1D(UnaryPredicate1D):
def __init__(self, pred1, pred2):
def __init__(self, *predicates):
UnaryPredicate1D.__init__(self)
self.__pred1 = pred1
self.__pred2 = pred2
self.predicates = predicates
if len(self.predicates) < 2:
raise ValueError("Expected two or more UnaryPredicate1D")
def __call__(self, inter):
return self.__pred1(inter) or self.__pred2(inter)
return any(pred(inter) for pred in self.predicates)
class NotUP1D(UnaryPredicate1D):
@@ -184,6 +186,29 @@ class NotUP1D(UnaryPredicate1D):
return not self.__pred(inter)
class ObjectNamesUP1D(UnaryPredicate1D):
def __init__(self, names, negative=False):
UnaryPredicate1D.__init__(self)
self._names = names
self._negative = negative
def __call__(self, viewEdge):
found = viewEdge.viewshape.name in self._names
return found if not self._negative else not found
class QuantitativeInvisibilityRangeUP1D(UnaryPredicate1D):
def __init__(self, qi_start, qi_end):
UnaryPredicate1D.__init__(self)
self.__getQI = QuantitativeInvisibilityF1D()
self.__qi_start = qi_start
self.__qi_end = qi_end
def __call__(self, inter):
qi = self.__getQI(inter)
return (self.__qi_start <= qi <= self.__qi_end)
class pyNFirstUP1D(UnaryPredicate1D):
def __init__(self, n):
UnaryPredicate1D.__init__(self)
@@ -191,14 +216,12 @@ class pyNFirstUP1D(UnaryPredicate1D):
self.__count = 0
def __call__(self, inter):
self.__count = self.__count + 1
if self.__count <= self.__n:
return True
return False
self.__count += 1
return (self.__count <= self.__n)
class pyHigherLengthUP1D(UnaryPredicate1D):
def __init__(self,l):
def __init__(self, l):
UnaryPredicate1D.__init__(self)
self._l = l
@@ -213,28 +236,20 @@ class pyNatureUP1D(UnaryPredicate1D):
self._getNature = CurveNatureF1D()
def __call__(self, inter):
if(self._getNature(inter) & self._nature):
return True
return False
return bool(self._getNature(inter) & self._nature)
class pyHigherNumberOfTurnsUP1D(UnaryPredicate1D):
def __init__(self,n,a):
def __init__(self, n, a):
UnaryPredicate1D.__init__(self)
self._n = n
self._a = a
def __call__(self, inter):
count = 0
func = Curvature2DAngleF0D()
it = inter.vertices_begin()
while not it.is_end:
if func(it) > self._a:
count = count+1
if count > self._n:
return True
it.increment()
return False
# sum the turns, check against n
return sum(1 for ve in it if func(it) > self._a) > self._n
class pyDensityUP1D(UnaryPredicate1D):
@@ -278,9 +293,7 @@ class pyHighSteerableViewMapDensityUP1D(UnaryPredicate1D):
def __init__(self, threshold, level, integration=IntegrationType.MEAN):
UnaryPredicate1D.__init__(self)
self._threshold = threshold
self._level = level
self._integration = integration
self._func = GetSteerableViewMapDensityF1D(self._level, self._integration)
self._func = GetSteerableViewMapDensityF1D(level, integration)
def __call__(self, inter):
return (self._func(inter) > self._threshold)
@@ -290,24 +303,17 @@ class pyHighDirectionalViewMapDensityUP1D(UnaryPredicate1D):
def __init__(self, threshold, orientation, level, integration=IntegrationType.MEAN, sampling=2.0):
UnaryPredicate1D.__init__(self)
self._threshold = threshold
self._orientation = orientation
self._level = level
self._integration = integration
self._sampling = sampling
self._func = GetDirectionalViewMapDensityF1D(orientation, level, integration, sampling)
def __call__(self, inter):
func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration, self._sampling)
return (func(inter) > self._threshold)
return (self.func(inter) > self._threshold)
class pyHighViewMapDensityUP1D(UnaryPredicate1D):
def __init__(self, threshold, level, integration=IntegrationType.MEAN, sampling=2.0):
UnaryPredicate1D.__init__(self)
self._threshold = threshold
self._level = level
self._integration = integration
self._sampling = sampling
self._func = GetCompleteViewMapDensityF1D(self._level, self._integration, self._sampling) # 2.0 is the smpling
self._func = GetCompleteViewMapDensityF1D(level, integration, sampling)
def __call__(self, inter):
return (self._func(inter) > self._threshold)
@@ -316,67 +322,56 @@ class pyHighViewMapDensityUP1D(UnaryPredicate1D):
class pyDensityFunctorUP1D(UnaryPredicate1D):
def __init__(self, wsize, threshold, functor, funcmin=0.0, funcmax=1.0, integration=IntegrationType.MEAN):
UnaryPredicate1D.__init__(self)
self._wsize = wsize
self._threshold = float(threshold)
self._functor = functor
self._funcmin = float(funcmin)
self._funcmax = float(funcmax)
self._integration = integration
self._func = DensityF1D(wsize, integration)
def __call__(self, inter):
func = DensityF1D(self._wsize, self._integration)
res = self._functor(inter)
k = (res-self._funcmin)/(self._funcmax-self._funcmin)
k = (res - self._funcmin) / (self._funcmax - self._funcmin)
return (func(inter) < (self._threshold * k))
class pyZSmallerUP1D(UnaryPredicate1D):
def __init__(self,z, integration=IntegrationType.MEAN):
def __init__(self, z, integration=IntegrationType.MEAN):
UnaryPredicate1D.__init__(self)
self._z = z
self._integration = integration
self.func = GetProjectedZF1D(integration)
def __call__(self, inter):
func = GetProjectedZF1D(self._integration)
return (func(inter) < self._z)
return (self.func(inter) < self._z)
class pyIsOccludedByUP1D(UnaryPredicate1D):
def __init__(self,id):
UnaryPredicate1D.__init__(self)
if not isinstance(id, Id):
raise TypeError("pyIsOccludedByUP1D expected freestyle.types.Id, not " + type(id).__name__)
self._id = id
def __call__(self, inter):
func = GetShapeF1D()
shapes = func(inter)
for s in shapes:
if(s.id == self._id):
return False
shapes = GetShapeF1D()(inter)
if any(s.id == self._id for s in shapes):
return False
# construct iterators
it = inter.vertices_begin()
itlast = inter.vertices_end()
itlast.decrement()
v = it.object
vlast = itlast.object
tvertex = v.viewvertex
if type(tvertex) is TVertex:
#print("TVertex: [ ", tvertex.id.first, ",", tvertex.id.second," ]")
vertex = next(it)
if type(vertex) is TVertex:
eit = vertex.edges_begin()
if any(ve.id == self._id for (ve, incoming) in eit):
return True
vertex = next(itlast)
if type(vertex) is TVertex:
eit = tvertex.edges_begin()
while not eit.is_end:
ve, incoming = eit.object
if ve.id == self._id:
return True
#print("-------", ve.id.first, "-", ve.id.second)
eit.increment()
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 True
#print("-------", ve.id.first, "-", ve.id.second)
eit.increment()
if any(ve.id == self._id for (ve, incoming) in eit):
return True
return False
@@ -386,12 +381,8 @@ class pyIsInOccludersListUP1D(UnaryPredicate1D):
self._id = id
def __call__(self, inter):
func = GetOccludersF1D()
occluders = func(inter)
for a in occluders:
if a.id == self._id:
return True
return False
occluders = GetOccludersF1D()(inter)
return any(a.id == self._id for a in occluders)
class pyIsOccludedByItselfUP1D(UnaryPredicate1D):
@@ -403,11 +394,7 @@ class pyIsOccludedByItselfUP1D(UnaryPredicate1D):
def __call__(self, inter):
lst1 = self.__func1(inter)
lst2 = self.__func2(inter)
for vs1 in lst1:
for vs2 in lst2:
if vs1.id == vs2.id:
return True
return False
return any(vs1.id == vs2.id for vs1 in lst1 for vs2 in lst2)
class pyIsOccludedByIdListUP1D(UnaryPredicate1D):
@@ -417,27 +404,17 @@ class pyIsOccludedByIdListUP1D(UnaryPredicate1D):
self.__func1 = GetOccludersF1D()
def __call__(self, inter):
lst1 = self.__func1(inter)
for vs1 in lst1:
for _id in self._idlist:
if vs1.id == _id:
return True
return False
lst1 = self.__func1(inter.object)
return any(vs1.id == _id for vs1 in lst1 for _id in self._idlist)
class pyShapeIdListUP1D(UnaryPredicate1D):
def __init__(self,idlist):
UnaryPredicate1D.__init__(self)
self._idlist = idlist
self._funcs = []
for _id in idlist:
self._funcs.append(ShapeUP1D(_id.first, _id.second))
self._funcs = tuple(ShapeUP1D(_id, 0) for _id in idlist)
def __call__(self, inter):
for func in self._funcs:
if func(inter) == 1:
return True
return False
return any(func(inter) for func in self._funcs)
## deprecated
@@ -447,12 +424,8 @@ class pyShapeIdUP1D(UnaryPredicate1D):
self._id = _id
def __call__(self, inter):
func = GetShapeF1D()
shapes = func(inter)
for a in shapes:
if a.id == self._id:
return True
return False
shapes = GetShapeF1D()(inter)
return any(a.id == self._id for a in shapes)
class pyHighDensityAnisotropyUP1D(UnaryPredicate1D):
@@ -473,7 +446,6 @@ class pyHighViewMapGradientNormUP1D(UnaryPredicate1D):
def __call__(self, inter):
gn = self._GetGradient(inter)
#print(gn)
return (gn > self._threshold)
@@ -503,53 +475,50 @@ class pyClosedCurveUP1D(UnaryPredicate1D):
it = inter.vertices_begin()
itlast = inter.vertices_end()
itlast.decrement()
vlast = itlast.object
v = it.object
#print(v.id.first, v.id.second)
#print(vlast.id.first, vlast.id.second)
if v.id == vlast.id:
return True
return False
return (next(it).id == next(itlast).id)
# -- Binary predicates for 1D elements (curves) -- #
## Binary predicates for 1D elements (curves)
#############################################
class AndBP1D(BinaryPredicate1D):
def __init__(self, pred1, pred2):
def __init__(self, *predicates):
BinaryPredicate1D.__init__(self)
self.__pred1 = pred1
self.__pred2 = pred2
self._predicates = predicates
if len(self.predicates) < 2:
raise ValueError("Expected two or more BinaryPredicate1D")
def __call__(self, inter1, inter2):
return self.__pred1(inter1, inter2) and self.__pred2(inter1, inter2)
def __call__(self, i1, i2):
return all(pred(i1, i2) for pred in self._predicates)
class OrBP1D(BinaryPredicate1D):
def __init__(self, pred1, pred2):
def __init__(self, *predicates):
BinaryPredicate1D.__init__(self)
self.__pred1 = pred1
self.__pred2 = pred2
self._predicates = predicates
if len(self.predicates) < 2:
raise ValueError("Expected two or more BinaryPredicate1D")
def __call__(self, inter1, inter2):
return self.__pred1(inter1, inter2) or self.__pred2(inter1, inter2)
def __call__(self, i1, i2):
return any(pred(i1, i2) for pred in self._predicates)
class NotBP1D(BinaryPredicate1D):
def __init__(self, pred):
def __init__(self, predicate):
BinaryPredicate1D.__init__(self)
self.__pred = pred
self._predicate = predicate
def __call__(self, inter1, inter2):
return not self.__pred(inter1, inter2)
def __call__(self, i1, i2):
return (not self._precicate(i1, i2))
class pyZBP1D(BinaryPredicate1D):
def __init__(self, iType=IntegrationType.MEAN):
BinaryPredicate1D.__init__(self)
self._GetZ = GetZF1D(iType)
self.func = GetZF1D(iType)
def __call__(self, i1, i2):
return (self._GetZ(i1) > self._GetZ(i2))
return (self.func(i1) > self.func(i2))
class pyZDiscontinuityBP1D(BinaryPredicate1D):
@@ -569,10 +538,10 @@ class pyLengthBP1D(BinaryPredicate1D):
class pySilhouetteFirstBP1D(BinaryPredicate1D):
def __call__(self, inter1, inter2):
bpred = SameShapeIdBP1D()
if (bpred(inter1, inter2) != 1):
if (not bpred(inter1, inter2)):
return False
if (inter1.nature & Nature.SILHOUETTE):
return (inter2.nature & Nature.SILHOUETTE) != 0
return bool(inter2.nature & Nature.SILHOUETTE)
return (inter1.nature == inter2.nature)
@@ -587,16 +556,13 @@ class pyViewMapGradientNormBP1D(BinaryPredicate1D):
self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN)
def __call__(self, i1,i2):
#print("compare gradient")
return (self._GetGradient(i1) > self._GetGradient(i2))
class pyShuffleBP1D(BinaryPredicate1D):
def __init__(self):
BinaryPredicate1D.__init__(self)
random.seed(1)
random.seed = 1
def __call__(self, inter1, inter2):
r1 = random.uniform(0,1)
r2 = random.uniform(0,1)
return (r1<r2)
return (random.uniform(0,1) < random.uniform(0,1))

View File

@@ -62,6 +62,7 @@ from freestyle.types import (
StrokeAttribute,
StrokeShader,
StrokeVertexIterator,
StrokeVertex,
)
from freestyle.functions import (
Curvature2DAngleF0D,
@@ -74,16 +75,27 @@ from freestyle.functions import (
)
from freestyle.predicates import (
pyVertexNatureUP0D,
pyUEqualsUP0D,
)
from freestyle.utils import (
bound,
bounding_box,
phase_to_direction,
)
from freestyle.utils import ContextFunctions as CF
from math import atan, cos, pi, pow, sin, sinh, sqrt
import bpy
import random
from math import atan, cos, pi, sin, sinh, sqrt
from mathutils import Vector
from random import randint
## thickness modifiers
######################
# -- Thickness Stroke Shaders -- #
class pyDepthDiscontinuityThicknessShader(StrokeShader):
"""
@@ -92,21 +104,16 @@ class pyDepthDiscontinuityThicknessShader(StrokeShader):
"""
def __init__(self, min, max):
StrokeShader.__init__(self)
self.__min = float(min)
self.__max = float(max)
self.__func = ZDiscontinuityF0D()
self.a = max - min
self.b = min
self.func = ZDiscontinuityF0D()
def shade(self, stroke):
z_min=0.0
z_max=1.0
a = (self.__max - self.__min)/(z_max-z_min)
b = (self.__min*z_max-self.__max*z_min)/(z_max-z_min)
it = stroke.stroke_vertices_begin()
while not it.is_end:
z = self.__func(Interface0DIterator(it))
thickness = a*z+b
it.object.attribute.thickness = (thickness, thickness)
it.increment()
it = Interface0DIterator(stroke)
for svert in it:
z = self.func(it)
thickness = self.a * z + self.b
svert.attribute.thickness = (thickness, thickness)
class pyConstantThicknessShader(StrokeShader):
@@ -115,14 +122,11 @@ class pyConstantThicknessShader(StrokeShader):
"""
def __init__(self, thickness):
StrokeShader.__init__(self)
self._thickness = thickness
self._thickness = thickness / 2.0
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
while not it.is_end:
t = self._thickness/2.0
it.object.attribute.thickness = (t, t)
it.increment()
for svert in stroke:
svert.attribute.thickness = (self._thickness, self._thickness)
class pyFXSVaryingThicknessWithDensityShader(StrokeShader):
@@ -131,28 +135,22 @@ class pyFXSVaryingThicknessWithDensityShader(StrokeShader):
"""
def __init__(self, wsize, threshold_min, threshold_max, thicknessMin, thicknessMax):
StrokeShader.__init__(self)
self.wsize= wsize
self.threshold_min= threshold_min
self.threshold_max= threshold_max
self._func = DensityF0D(wsize)
self.threshold_min = threshold_min
self.threshold_max = threshold_max
self._thicknessMin = thicknessMin
self._thicknessMax = thicknessMax
def shade(self, stroke):
n = stroke.stroke_vertices_size()
i = 0
it = stroke.stroke_vertices_begin()
func = DensityF0D(self.wsize)
while not it.is_end:
c = func(Interface0DIterator(it))
if c < self.threshold_min:
c = self.threshold_min
if c > self.threshold_max:
c = self.threshold_max
## t = (c - self.threshold_min)/(self.threshold_max - self.threshold_min)*(self._thicknessMax-self._thicknessMin) + self._thicknessMin
t = (self.threshold_max - c )/(self.threshold_max - self.threshold_min)*(self._thicknessMax-self._thicknessMin) + self._thicknessMin
it.object.attribute.thickness = (t/2.0, t/2.0)
i = i+1
it.increment()
it = Interface0DIterator(stroke)
delta_threshold = self.threshold_max - self.threshold_min
delta_thickness = self._thicknessMax - self._thicknessMin
for svert in it:
c = self._func(it)
c = bound(self.threshold_min, c, self.threshold_max)
t = (self.threshold_max - c) / delta_threshold * delta_thickness + self._thicknessMin
svert.attribute.thickness = (t / 2.0, t / 2.0)
class pyIncreasingThicknessShader(StrokeShader):
@@ -165,18 +163,14 @@ class pyIncreasingThicknessShader(StrokeShader):
self._thicknessMax = thicknessMax
def shade(self, stroke):
n = stroke.stroke_vertices_size()
i = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
c = float(i)/float(n)
if i < float(n)/2.0:
t = (1.0 - c)*self._thicknessMin + c * self._thicknessMax
n = len(stroke)
for i, svert in enumerate(stroke):
c = i / n
if i < (n * 0.5):
t = (1.0 - c) * self._thicknessMin + c * self._thicknessMax
else:
t = (1.0 - c)*self._thicknessMax + c * self._thicknessMin
it.object.attribute.thickness = (t/2.0, t/2.0)
i = i+1
it.increment()
t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin
svert.attribute.thickness = (t / 2.0, t / 2.0)
class pyConstrainedIncreasingThicknessShader(StrokeShader):
@@ -191,28 +185,20 @@ class pyConstrainedIncreasingThicknessShader(StrokeShader):
self._ratio = ratio
def shade(self, stroke):
slength = stroke.length_2d
tmp = self._ratio*slength
maxT = 0.0
if tmp < self._thicknessMax:
maxT = tmp
else:
maxT = self._thicknessMax
n = stroke.stroke_vertices_size()
i = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
att = it.object.attribute
c = float(i)/float(n)
if i < float(n)/2.0:
t = (1.0 - c)*self._thicknessMin + c * maxT
n = len(stroke)
maxT = min(self._ratio * stroke.length_2d, self._thicknessMax)
for i, svert in enumerate(stroke):
c = i / n
if i < (n * 0.5):
t = (1.0 - c) * self._thicknessMin + c * maxT
else:
t = (1.0 - c)*maxT + c * self._thicknessMin
att.thickness = (t/2.0, t/2.0)
if i == n-1:
att.thickness = (self._thicknessMin/2.0, self._thicknessMin/2.0)
i = i+1
it.increment()
t = (1.0 - c) * maxT + c * self._thicknessMin
if i == (n - 1):
svert.attribute.thickness = (self._thicknessMin / 2.0, self._thicknessMin / 2.0)
else:
svert.attribute.thickness = (t / 2.0, t / 2.0)
class pyDecreasingThicknessShader(StrokeShader):
@@ -226,21 +212,14 @@ class pyDecreasingThicknessShader(StrokeShader):
def shade(self, stroke):
l = stroke.length_2d
tMax = self._thicknessMax
if self._thicknessMax > 0.33*l:
tMax = 0.33*l
tMin = self._thicknessMin
if self._thicknessMin > 0.1*l:
tMin = 0.1*l
n = stroke.stroke_vertices_size()
i = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
c = float(i)/float(n)
t = (1.0 - c)*tMax +c*tMin
it.object.attribute.thickness = (t/2.0, t/2.0)
i = i+1
it.increment()
n = len(stroke)
tMax = min(self._thicknessMax, 0.33 * l)
tMin = min(self._thicknessMin, 0.10 * l)
for i, svert in enumerate(stroke):
c = i / n
t = (1.0 - c) * tMax + c * tMin
svert.attribute.thickness = (t / 2.0, t / 2.0)
class pyNonLinearVaryingThicknessShader(StrokeShader):
@@ -248,28 +227,18 @@ class pyNonLinearVaryingThicknessShader(StrokeShader):
Assigns thickness to a stroke based on an exponential function
"""
def __init__(self, thicknessExtremity, thicknessMiddle, exponent):
StrokeShader.__init__(self)
self._thicknessMin = thicknessMiddle
self._thicknessMax = thicknessExtremity
self._exponent = exponent
self._exp = exponent
StrokeShader.__init__(self)
def shade(self, stroke):
n = stroke.stroke_vertices_size()
i = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
if i < float(n)/2.0:
c = float(i)/float(n)
else:
c = float(n-i)/float(n)
c = self.smoothC(c, self._exponent)
t = (1.0 - c)*self._thicknessMax + c * self._thicknessMin
it.object.attribute.thickness = (t/2.0, t/2.0)
i = i+1
it.increment()
def smoothC(self, a, exp):
return pow(float(a), exp) * pow(2.0, exp)
n = len(stroke)
for i, svert in enumerate(stroke):
c = (i / n) if (i < n / 2.0) else ((n - i) / n)
c = pow(c, self._exp) * pow(2.0, self._exp)
t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin
svert.attribute.thickness = (t / 2.0, t / 2.0)
class pySLERPThicknessShader(StrokeShader):
@@ -280,29 +249,23 @@ class pySLERPThicknessShader(StrokeShader):
StrokeShader.__init__(self)
self._thicknessMin = thicknessMin
self._thicknessMax = thicknessMax
self._omega = omega
self.omega = omega
def shade(self, stroke):
slength = stroke.length_2d
tmp = 0.33*slength
maxT = self._thicknessMax
if tmp < self._thicknessMax:
maxT = tmp
n = stroke.stroke_vertices_size()
i = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
c = float(i)/float(n)
if i < float(n)/2.0:
t = sin((1-c)*self._omega)/sinh(self._omega)*self._thicknessMin + sin(c*self._omega)/sinh(self._omega) * maxT
n = len(stroke)
maxT = min(self._thicknessMax, 0.33 * stroke.length_2d)
omega = self.omega
sinhyp = sinh(omega)
for i, svert in enumerate(stroke):
c = i / n
if i < (n * 0.5):
t = sin((1-c) * omega) / sinhyp * self._thicknessMin + sin(c * omega) / sinhyp * maxT
else:
t = sin((1-c)*self._omega)/sinh(self._omega)*maxT + sin(c*self._omega)/sinh(self._omega) * self._thicknessMin
it.object.attribute.thickness = (t/2.0, t/2.0)
i = i+1
it.increment()
t = sin((1-c) * omega) / sinhyp * maxT + sin(c * omega) / sinhyp * self._thicknessMin
svert.attribute.thickness = (t / 2.0, t / 2.0)
class pyTVertexThickenerShader(StrokeShader): ## FIXME
class pyTVertexThickenerShader(StrokeShader):
"""
Thickens TVertices (visual intersections between two edges)
"""
@@ -312,46 +275,22 @@ class pyTVertexThickenerShader(StrokeShader): ## FIXME
self._n = n
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
predTVertex = pyVertexNatureUP0D(Nature.T_VERTEX)
while not it.is_end:
if predTVertex(it) == 1:
it2 = StrokeVertexIterator(it)
it2.increment()
if not (it.is_begin or it2.is_end):
it.increment()
continue
n = self._n
a = self._a
if it.is_begin:
it3 = StrokeVertexIterator(it)
count = 0
while (not it3.is_end) and count < n:
att = it3.object.attribute
(tr, tl) = att.thickness
r = (a-1.0)/float(n-1)*(float(n)/float(count+1) - 1) + 1
#r = (1.0-a)/float(n-1)*count + a
att.thickness = (r*tr, r*tl)
it3.increment()
count = count + 1
if it2.is_end:
it4 = StrokeVertexIterator(it)
count = 0
while (not it4.is_begin) and count < n:
att = it4.object.attribute
(tr, tl) = att.thickness
r = (a-1.0)/float(n-1)*(float(n)/float(count+1) - 1) + 1
#r = (1.0-a)/float(n-1)*count + a
att.thickness = (r*tr, r*tl)
it4.decrement()
count = count + 1
if it4.is_begin:
att = it4.object.attribute
(tr, tl) = att.thickness
r = (a-1.0)/float(n-1)*(float(n)/float(count+1) - 1) + 1
#r = (1.0-a)/float(n-1)*count + a
att.thickness = (r*tr, r*tl)
it.increment()
n = self._n
a = self._a
term = (a - 1.0) / (n - 1.0)
if (stroke[0].nature & Nature.T_VERTEX):
for count, svert in zip(range(n), stroke):
r = term * (n / (count + 1.0) - 1.0) + 1.0
(tr, tl) = svert.attribute.thickness
svert.attribute.thickness = (r * tr, r * tl)
if (stroke[-1].nature & Nature.T_VERTEX):
for count, svert in zip(range(n), reversed(stroke)):
r = term * (n / (count + 1.0) - 1.0) + 1.0
(tr, tl) = svert.attribute.thickness
svert.attribute.thickness = (r * tr, r * tl)
class pyImportance2DThicknessShader(StrokeShader):
@@ -362,26 +301,18 @@ class pyImportance2DThicknessShader(StrokeShader):
"""
def __init__(self, x, y, w, kmin, kmax):
StrokeShader.__init__(self)
self._x = x
self._y = y
self._w = float(w)
self._kmin = float(kmin)
self._kmax = float(kmax)
self._origin = Vector((x, y))
self._w = w
self._kmin, self._kmax = kmin, kmax
def shade(self, stroke):
origin = Vector((self._x, self._y))
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
d = (v.point_2d - self._origin).length
if d > self._w:
k = self._kmin
else:
k = (self._kmax*(self._w-d) + self._kmin*d)/self._w
att = v.attribute
(tr, tl) = att.thickness
att.thickness = (k*tr/2.0, k*tl/2.0)
it.increment()
for svert in stroke:
d = (svert.point_2d - self._origin).length
k = (self._kmin if (d > self._w) else
(self._kmax * (self._w-d) + self._kmin * d) / self._w)
(tr, tl) = svert.attribute.thickness
svert.attribute.thickness = (k*tr/2.0, k*tl/2.0)
class pyImportance3DThicknessShader(StrokeShader):
@@ -390,28 +321,18 @@ class pyImportance3DThicknessShader(StrokeShader):
"""
def __init__(self, x, y, z, w, kmin, kmax):
StrokeShader.__init__(self)
self._x = x
self._y = y
self._z = z
self._w = float(w)
self._kmin = float(kmin)
self._kmax = float(kmax)
self._origin = Vector((x, y, z))
self._w = w
self._kmin, self._kmax = kmin, kmax
def shade(self, stroke):
origin = Vector((self._x, self._y, self._z))
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
p = v.point_3d
d = (p-origin).length
if d > self._w:
k = self._kmin
else:
k = (self._kmax*(self._w-d) + self._kmin*d)/self._w
att = v.attribute
(tr, tl) = att.thickness
att.thickness = (k*tr/2.0, k*tl/2.0)
it.increment()
for svert in stroke:
d = (svert.point_3d - self._origin).length
k = (self._kmin if (d > self._w) else
(self._kmax * (self._w-d) + self._kmin * d) / self._w)
(tr, tl) = svert.attribute.thickness
svert.attribute.thickness = (k*tr/2.0, k*tl/2.0)
class pyZDependingThicknessShader(StrokeShader):
@@ -423,49 +344,35 @@ class pyZDependingThicknessShader(StrokeShader):
StrokeShader.__init__(self)
self.__min = min
self.__max = max
self.__func = GetProjectedZF0D()
self.func = GetProjectedZF0D()
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
z_min = 1
z_max = 0
while not it.is_end:
z = self.__func(Interface0DIterator(it))
if z < z_min:
z_min = z
if z > z_max:
z_max = z
it.increment()
it = Interface0DIterator(stroke)
z_indices = tuple(self.func(it) for _ in it)
z_min, z_max = min(1, *z_indices), max(0, *z_indices)
z_diff = 1 / (z_max - z_min)
it = stroke.stroke_vertices_begin()
while not it.is_end:
z = (self.__func(Interface0DIterator(it)) - z_min) * z_diff
for svert, z_index in zip(stroke, z_indices):
z = (z_index - z_min) * z_diff
thickness = (1 - z) * self.__max + z * self.__min
it.object.attribute.thickness = (thickness, thickness)
it.increment()
svert.attribute.thickness = (thickness, thickness)
## color modifiers
##################
# -- Color & Alpha Stroke Shaders -- #
class pyConstantColorShader(StrokeShader):
"""
Assigns a constant color to the stroke
"""
def __init__(self, r, g, b, a=1):
def __init__(self,r,g,b, a = 1):
StrokeShader.__init__(self)
self._r = r
self._g = g
self._b = b
self._color = (r, g, b)
self._a = a
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
while not it.is_end:
att = it.object.attribute
att.color = (self._r, self._g, self._b)
att.alpha = self._a
it.increment()
for svert in stroke:
svert.attribute.color = self._color
svert.attribute.alpha = self._a
class pyIncreasingColorShader(StrokeShader):
@@ -474,23 +381,18 @@ class pyIncreasingColorShader(StrokeShader):
"""
def __init__(self,r1,g1,b1,a1, r2,g2,b2,a2):
StrokeShader.__init__(self)
self._c1 = [r1,g1,b1,a1]
self._c2 = [r2,g2,b2,a2]
# use 4d vector to simplify math
self._c1 = Vector((r1, g1 ,b1, a1))
self._c2 = Vector((r2, g2, b2, a2))
def shade(self, stroke):
n = stroke.stroke_vertices_size() - 1
inc = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
att = it.object.attribute
c = float(inc) / float(n)
n = len(stroke) - 1
att.color = ((1.0 - c) * self._c1[0] + c * self._c2[0],
(1.0 - c) * self._c1[1] + c * self._c2[1],
(1.0 - c) * self._c1[2] + c * self._c2[2])
att.alpha = (1.0 - c) * self._c1[3] + c * self._c2[3]
inc = inc + 1
it.increment()
for i, svert in enumerate(stroke):
c = i / n
color = (1 - c) * self._c1 + c * self._c2
svert.attribute.color = color[:3]
svert.attribute.alpha = color[3]
class pyInterpolateColorShader(StrokeShader):
@@ -499,23 +401,32 @@ class pyInterpolateColorShader(StrokeShader):
"""
def __init__(self,r1,g1,b1,a1, r2,g2,b2,a2):
StrokeShader.__init__(self)
self._c1 = [r1,g1,b1,a1]
self._c2 = [r2,g2,b2,a2]
# use 4d vector to simplify math
self._c1 = Vector((r1, g1 ,b1, a1))
self._c2 = Vector((r2, g2, b2, a2))
def shade(self, stroke):
n = stroke.stroke_vertices_size() - 1
inc = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
att = it.object.attribute
u = float(inc) / float(n)
c = 1.0 - 2.0 * abs(u - 0.5)
att.color = ((1.0 - c) * self._c1[0] + c * self._c2[0],
(1.0 - c) * self._c1[1] + c * self._c2[1],
(1.0 - c) * self._c1[2] + c * self._c2[2])
att.alpha = (1.0-c) * self._c1[3] + c * self._c2[3]
inc = inc+1
it.increment()
n = len(stroke) - 1
for i, svert in enumerate(stroke):
c = 1.0 - 2.0 * abs((i / n) - 0.5)
color = (1.0 - c) * self._c1 + c * self._c2
svert.attribute.color = color[:3]
svert.attribute.alpha = color[3]
class pyModulateAlphaShader(StrokeShader):
"""
Limits the stroke's alpha between a min and max value.
"""
def __init__(self, min=0, max=1):
StrokeShader.__init__(self)
self.__min = min
self.__max = max
def shade(self, stroke):
for svert in stroke:
alpha = svert.attribute.alpha
alpha = bound(self.__min, alpha * svert.point.y * 0.0025, self.__max)
svert.attribute.alpha = alpha
class pyMaterialColorShader(StrokeShader):
@@ -525,61 +436,59 @@ class pyMaterialColorShader(StrokeShader):
def __init__(self, threshold=50):
StrokeShader.__init__(self)
self._threshold = threshold
self._func = MaterialF0D()
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
func = MaterialF0D()
xn = 0.312713
yn = 0.329016
Yn = 1.0
un = 4.* xn / (-2.*xn + 12.*yn + 3.)
vn= 9.* yn / (-2.*xn + 12.*yn +3.)
while not it.is_end:
mat = func(Interface0DIterator(it))
un = 4.0 * xn / (-2.0 * xn + 12.0 * yn + 3.0)
vn = 9.0 * yn / (-2.0 * xn + 12.0 * yn + 3.0)
r = mat.diffuse[0]
g = mat.diffuse[1]
b = mat.diffuse[2]
it = Interface0DIterator(stroke)
for svert in it:
mat = self._func(it)
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
r, g, b, *_ = mat.diffuse
if (X, Y, Z) == (0, 0, 0):
X = 0.01
Y = 0.01
Z = 0.01
u = 4.*X / (X + 15.*Y + 3.*Z)
v = 9.*Y / (X + 15.*Y + 3.*Z)
X = 0.412453 * r + 0.35758 * g + 0.180423 * b
Y = 0.212671 * r + 0.71516 * g + 0.072169 * b
Z = 0.019334 * r + 0.11919 * g + 0.950227 * b
L= 116. * pow((Y/Yn),(1./3.)) -16
if not any((X, Y, Z)):
X = Y = Z = 0.01
u = 4.0 * X / (X + 15.0 * Y + 3.0 * Z)
v = 9.0 * Y / (X + 15.0 * Y + 3.0 * Z)
L= 116. * pow((Y/Yn),(1./3.)) - 16
U = 13. * L * (u - un)
V = 13. * L * (v - vn)
if L > self._threshold:
L = L/1.3
U = U+10
L /= 1.3
U += 10.
else:
L = L +2.5*(100-L)/5.
U = U/3.0
V = V/3.0
u = U / (13. * L) + un
v = V / (13. * L) + vn
L = L + 2.5 * (100-L) * 0.2
U /= 3.0
V /= 3.0
u = U / (13.0 * L) + un
v = V / (13.0 * L) + vn
Y = Yn * pow(((L+16.)/116.), 3.)
X = -9.0 * Y * u / ((u - 4.0) * v - u * v)
Z = (9.0 * Y - 15.0 * v * Y - v * X) / (3.0 * v)
X = -9. * Y * u / ((u - 4.)* v - u * v)
Z = (9. * Y - 15*v*Y - v*X) /( 3. * v)
r = 3.240479 * X - 1.53715 * Y - 0.498535 * Z
g = -0.969256 * X + 1.875991 * Y + 0.041556 * Z
b = 0.055648 * X - 0.204043 * Y + 1.057311 * Z
r = max(0,r)
g = max(0,g)
b = max(0,b)
r = max(0, r)
g = max(0, g)
b = max(0, b)
it.object.attribute.color = (r, g, b)
it.increment()
svert.attribute.color = (r, g, b)
class pyRandomColorShader(StrokeShader):
@@ -588,18 +497,14 @@ class pyRandomColorShader(StrokeShader):
"""
def __init__(self, s=1):
StrokeShader.__init__(self)
random.seed(s)
random.seed = s
def shade(self, stroke):
## pick a random color
c0 = float(random.uniform(15,75))/100.0
c1 = float(random.uniform(15,75))/100.0
c2 = float(random.uniform(15,75))/100.0
#print(c0, c1, c2)
it = stroke.stroke_vertices_begin()
while not it.is_end:
it.object.attribute.color = (c0,c1,c2)
it.increment()
c = (random.uniform(15, 75) * 0.01,
random.uniform(15, 75) * 0.01,
random.uniform(15, 75) * 0.01)
for svert in stroke:
svert.attribute.color = c
class py2DCurvatureColorShader(StrokeShader):
@@ -608,15 +513,14 @@ class py2DCurvatureColorShader(StrokeShader):
A higher curvature will yield a brighter color
"""
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
func = Curvature2DAngleF0D()
while not it.is_end:
c = func(Interface0DIterator(it))
if c < 0:
print("negative 2D curvature")
color = 10.0 * c/3.1415
it.object.attribute.color = (color, color, color)
it.increment()
it = Interface0DIterator(stroke)
for svert in it:
c = func(it)
if c < 0 and bpy.app.debug_freestyle:
print("py2DCurvatureColorShader: negative 2D curvature")
color = 10.0 * c / pi
svert.attribute.color = (color, color, color)
class pyTimeColorShader(StrokeShader):
@@ -627,13 +531,13 @@ class pyTimeColorShader(StrokeShader):
def __init__(self, step=0.01):
StrokeShader.__init__(self)
self._step = step
def shade(self, stroke):
for i, svert in enumerate(iter(stroke)):
for i, svert in enumerate(stroke):
c = i * self._step
svert.attribute.color = (c,c,c)
svert.attribute.color = (c, c, c)
## geometry modifiers
# -- Geometry Stroke Shaders -- #
class pySamplingShader(StrokeShader):
@@ -659,89 +563,55 @@ class pyBackboneStretcherShader(StrokeShader):
self._l = l
def shade(self, stroke):
it0 = stroke.stroke_vertices_begin()
it1 = StrokeVertexIterator(it0)
it1.increment()
itn = stroke.stroke_vertices_end()
itn.decrement()
itn_1 = StrokeVertexIterator(itn)
itn_1.decrement()
v0 = it0.object
v1 = it1.object
vn_1 = itn_1.object
vn = itn.object
p0 = v0.point_2d
pn = vn.point_2d
p1 = v1.point_2d
pn_1 = vn_1.point_2d
d1 = (p0 - p1).normalized()
dn = (pn - pn_1).normalized()
newFirst = p0+d1*float(self._l)
newLast = pn+dn*float(self._l)
v0.point = newFirst
vn.point = newLast
# get start and end points
v0, vn = stroke[0], stroke[-1]
p0, pn = v0.point, vn.point
# get the direction
d1 = (p0 - stroke[ 1].point).normalized()
dn = (pn - stroke[-2].point).normalized()
v0.point += d1 * self._l
vn.point += dn * self._l
stroke.update_length()
class pyLengthDependingBackboneStretcherShader(StrokeShader):
"""
Stretches the stroke's backbone proportional to the stroke's length
NOTE: you'll probably want an l somewhere between (0.5 - 0). A value that
is too high may yield unexpected results.
"""
def __init__(self, l):
StrokeShader.__init__(self)
self._l = l
def shade(self, stroke):
l = stroke.length_2d
stretch = self._l*l
it0 = stroke.stroke_vertices_begin()
it1 = StrokeVertexIterator(it0)
it1.increment()
itn = stroke.stroke_vertices_end()
itn.decrement()
itn_1 = StrokeVertexIterator(itn)
itn_1.decrement()
v0 = it0.object
v1 = it1.object
vn_1 = itn_1.object
vn = itn.object
p0 = v0.point_2d
pn = vn.point_2d
p1 = v1.point_2d
pn_1 = vn_1.point_2d
d1 = (p0 - p1).normalized()
dn = (pn - pn_1).normalized()
newFirst = p0+d1*float(stretch)
newLast = pn+dn*float(stretch)
v0.point = newFirst
vn.point = newLast
# get start and end points
v0, vn = stroke[0], stroke[-1]
p0, pn = v0.point, vn.point
# get the direction
d1 = (p0 - stroke[ 1].point).normalized()
dn = (pn - stroke[-2].point).normalized()
v0.point += d1 * self._l * stroke.length_2d
vn.point += dn * self._l * stroke.length_2d
stroke.update_length()
class pyGuidingLineShader(StrokeShader):
"""
Replaces the stroke by its corresponding tangent
"""
def shade(self, stroke):
it = stroke.stroke_vertices_begin() ## get the first vertex
itlast = stroke.stroke_vertices_end() ##
itlast.decrement() ## get the last one
t = itlast.object.point - it.object.point ## tangent direction
itmiddle = StrokeVertexIterator(it) ##
while itmiddle.object.u < 0.5: ## look for the stroke middle vertex
itmiddle.increment() ##
# get the tangent direction
t = stroke[-1].point - stroke[0].point
# look for the stroke middle vertex
itmiddle = iter(stroke)
while itmiddle.object.u < 0.5:
itmiddle.increment()
center_vertex = itmiddle.object
# position all the vertices along the tangent for the right part
it = StrokeVertexIterator(itmiddle)
it.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
for svert in it:
svert.point = center_vertex.point + t * (svert.u - center_vertex.u)
# position all the vertices along the tangent for the left part
it = StrokeVertexIterator(itmiddle).reversed()
for svert in it:
svert.point = center_vertex.point - t * (center_vertex.u - svert.u)
stroke.update_length()
@@ -754,25 +624,18 @@ class pyBackboneStretcherNoCuspShader(StrokeShader):
self._l = l
def shade(self, stroke):
it0 = stroke.stroke_vertices_begin()
it1 = StrokeVertexIterator(it0)
it1.increment()
itn = stroke.stroke_vertices_end()
itn.decrement()
itn_1 = StrokeVertexIterator(itn)
itn_1.decrement()
v0 = it0.object
v1 = it1.object
if (v0.nature & Nature.CUSP) == 0 and (v1.nature & Nature.CUSP) == 0:
v0, v1 = stroke[0], stroke[1]
vn, vn_1 = stroke[-1], stroke[-2]
if not (v0.nature & v1.nature & Nature.CUSP):
d1 = (v0.point - v1.point).normalized()
newFirst = v0.point+d1*float(self._l)
v0.point = newFirst
vn_1 = itn_1.object
vn = itn.object
if (vn.nature & Nature.CUSP) == 0 and (vn_1.nature & Nature.CUSP) == 0:
v0.point += d1 * self._l
if not (vn.nature & vn_1.nature & Nature.CUSP):
dn = (vn.point - vn_1.point).normalized()
newLast = vn.point + dn * float(self._l)
vn.point = newLast
vn.point += dn * self._l
stroke.update_length()
@@ -792,13 +655,9 @@ class pyDiffusion2Shader(StrokeShader):
def shade(self, stroke):
for i in range (1, self._nbIter):
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
p1 = v.point
p2 = self._normalInfo(Interface0DIterator(it))*self._lambda*self._curvatureInfo(Interface0DIterator(it))
v.point = p1+p2
it.increment()
it = Interface0DIterator(stroke)
for svert in it:
svert.point += self._normalInfo(it) * self._lambda * self._curvatureInfo(it)
stroke.update_length()
@@ -810,33 +669,36 @@ class pyTipRemoverShader(StrokeShader):
StrokeShader.__init__(self)
self._l = l
@staticmethod
def check_vertex(v, length):
"""
Returns True if the given strokevertex is less than self._l away
from the stroke's tip and therefore should be removed.
"""
return (v.curvilinear_abscissa < length or v.stroke_length-v.curvilinear_abscissa < length)
def shade(self, stroke):
originalSize = stroke.stroke_vertices_size()
if originalSize < 4:
n = len(stroke)
if n < 4:
return
verticesToRemove = []
oldAttributes = []
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
if v.curvilinear_abscissa < self._l or v.stroke_length-v.curvilinear_abscissa < self._l:
verticesToRemove.append(v)
oldAttributes.append(StrokeAttribute(v.attribute))
it.increment()
if originalSize-len(verticesToRemove) < 2:
verticesToRemove = tuple(svert for svert in stroke if self.check_vertex(svert, self._l))
# explicit conversion to StrokeAttribute is needed
oldAttributes = (StrokeAttribute(svert.attribute) for svert in stroke)
if n - len(verticesToRemove) < 2:
return
for sv in verticesToRemove:
stroke.remove_vertex(sv)
stroke.update_length()
stroke.resample(originalSize)
if stroke.stroke_vertices_size() != originalSize:
stroke.resample(n)
if len(stroke) != n and bpy.app.debug_freestyle:
print("pyTipRemover: Warning: resampling problem")
it = stroke.stroke_vertices_begin()
for a in oldAttributes:
if it.is_end:
break
it.object.attribute = a
it.increment()
for svert, a in zip(stroke, oldAttributes):
svert.attribute = a
stroke.update_length()
@@ -845,145 +707,31 @@ class pyTVertexRemoverShader(StrokeShader):
Removes t-vertices from the stroke
"""
def shade(self, stroke):
if stroke.stroke_vertices_size() <= 3:
if len(stroke) < 4:
return
predTVertex = pyVertexNatureUP0D(Nature.T_VERTEX)
it = stroke.stroke_vertices_begin()
itlast = stroke.stroke_vertices_end()
itlast.decrement()
if predTVertex(it):
stroke.remove_vertex(it.object)
if predTVertex(itlast):
stroke.remove_vertex(itlast.object)
v0, vn = stroke[0], stroke[-1]
if (v0.nature & Nature.T_VERTEX):
stroke.remove_vertex(v0)
if (vn.nature & Nature.T_VERTEX):
stroke.remove_vertex(vn)
stroke.update_length()
#class pyExtremitiesOrientationShader(StrokeShader):
# def __init__(self, x1,y1,x2=0,y2=0):
# StrokeShader.__init__(self)
# self._v1 = Vector((x1,y1))
# self._v2 = Vector((x2,y2))
# def shade(self, stroke):
# #print(self._v1.x,self._v1.y)
# stroke.setBeginningOrientation(self._v1.x,self._v1.y)
# stroke.setEndingOrientation(self._v2.x,self._v2.y)
class pyHLRShader(StrokeShader):
"""
Controlls visibility based upon the quantative invisibility (QI)
based on hidden line removal (HLR)
"""
def shade(self, stroke):
originalSize = stroke.stroke_vertices_size()
if originalSize < 4:
if len(stroke) < 4:
return
it = stroke.stroke_vertices_begin()
invisible = 0
it2 = StrokeVertexIterator(it)
it2.increment()
fe = self.get_fedge(it.object, it2.object)
if fe.viewedge.qi != 0:
invisible = 1
while not it2.is_end:
v = it.object
vnext = it2.object
if (v.nature & Nature.VIEW_VERTEX) != 0:
#if (v.nature & Nature.T_VERTEX) != 0:
fe = self.get_fedge(v, vnext)
qi = fe.viewedge.qi
if qi != 0:
invisible = 1
else:
invisible = 0
if invisible:
v.attribute.visible = False
it.increment()
it2.increment()
def get_fedge(self, it1, it2):
return it1.get_fedge(it2)
# broken and a mess
class pyTVertexOrientationShader(StrokeShader):
def __init__(self):
StrokeShader.__init__(self)
self._Get2dDirection = Orientation2DF1D()
## finds the TVertex orientation from the TVertex and
## the previous or next edge
def findOrientation(self, tv, ve):
mateVE = tv.get_mate(ve)
if ve.qi != 0 or mateVE.qi != 0:
ait = AdjacencyIterator(tv,1,0)
winner = None
incoming = True
while not ait.is_end:
ave = ait.object
if ave.id != ve.id and ave.id != mateVE.id:
winner = ait.object
if not ait.isIncoming(): # FIXME
incoming = False
break
ait.increment()
if winner is not None:
if not incoming:
direction = self._Get2dDirection(winner.last_fedge)
else:
direction = self._Get2dDirection(winner.first_fedge)
return direction
return None
def castToTVertex(self, cp):
if cp.t2d() == 0.0:
return cp.first_svertex.viewvertex
elif cp.t2d() == 1.0:
return cp.second_svertex.viewvertex
return None
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
it2 = StrokeVertexIterator(it)
it2.increment()
## case where the first vertex is a TVertex
v = it.object
if (v.nature & Nature.T_VERTEX) != 0:
tv = self.castToTVertex(v)
if tv is not None:
ve = self.get_fedge(v, it2.object).viewedge
dir = self.findOrientation(tv, ve)
if dir is not None:
#print(dir.x, dir.y)
v.attribute.set_attribute_vec2("orientation", dir)
while not it2.is_end:
vprevious = it.object
v = it2.object
if (v.nature & Nature.T_VERTEX) != 0:
tv = self.castToTVertex(v)
if tv is not None:
ve = self.get_fedge(vprevious, v).viewedge
dir = self.findOrientation(tv, ve)
if dir is not None:
#print(dir.x, dir.y)
v.attribute.set_attribute_vec2("orientation", dir)
it.increment()
it2.increment()
## case where the last vertex is a TVertex
v = it.object
if (v.nature & Nature.T_VERTEX) != 0:
itPrevious = StrokeVertexIterator(it)
itPrevious.decrement()
tv = self.castToTVertex(v)
if tv is not None:
ve = self.get_fedge(itPrevious.object, v).viewedge
dir = self.findOrientation(tv, ve)
if dir is not None:
#print(dir.x, dir.y)
v.attribute.set_attribute_vec2("orientation", dir)
def get_fedge(self, it1, it2):
return it1.get_fedge(it2)
it = iter(stroke)
for v1, v2 in zip(it, it.incremented()):
if (v1.nature & Nature.VIEW_VERTEX):
visible = (v1.get_fedge(v2).viewedge.qi != 0)
v1.attribute.visible = not visible
class pySinusDisplacementShader(StrokeShader):
@@ -997,19 +745,12 @@ class pySinusDisplacementShader(StrokeShader):
self._getNormal = Normal2DF0D()
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
#print(self._getNormal.name)
n = self._getNormal(Interface0DIterator(it))
p = v.point
u = v.u
a = self._a*(1-2*(abs(u-0.5)))
n = n*a*cos(self._f*u*6.28)
#print(n.x, n.y)
v.point = p+n
#v.point = v.point+n*a*cos(f*v.u)
it.increment()
it = Interface0DIterator(stroke)
for svert in it:
normal = self._getNormal(it)
a = self._a * (1 - 2 * (abs(svert.u - 0.5)))
n = normal * a * cos(self._f * svert.u * 6.28)
svert.point += n
stroke.update_length()
@@ -1027,13 +768,10 @@ class pyPerlinNoise1DShader(StrokeShader):
self.__oct = oct
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
i = v.projected_x + v.projected_y
nres = self.__noise.turbulence1(i, self.__freq, self.__amp, self.__oct)
v.point = (v.projected_x + nres, v.projected_y + nres)
it.increment()
for svert in stroke:
s = svert.projected_x + svert.projected_y
nres = self.__noise.turbulence1(s, self.__freq, self.__amp, self.__oct)
svert.point = (svert.projected_x + nres, svert.projected_y + nres)
stroke.update_length()
@@ -1053,12 +791,9 @@ class pyPerlinNoise2DShader(StrokeShader):
self.__oct = oct
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
while not it.is_end:
v = it.object
nres = self.__noise.turbulence2(v.point_2d, self.__freq, self.__amp, self.__oct)
v.point = (v.projected_x + nres, v.projected_y + nres)
it.increment()
for svert in stroke:
nres = self.__noise.turbulence2(svert.point_2d, self.__freq, self.__amp, self.__oct)
svert.point = (svert.projected_x + nres, svert.projected_y + nres)
stroke.update_length()
@@ -1073,66 +808,52 @@ class pyBluePrintCirclesShader(StrokeShader):
self.__random_radius = random_radius
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
if it.is_end:
return
p_min = it.object.point.copy()
p_max = it.object.point.copy()
while not it.is_end:
p = it.object.point
if p.x < p_min.x:
p_min.x = p.x
if p.x > p_max.x:
p_max.x = p.x
if p.y < p_min.y:
p_min.y = p.y
if p.y > p_max.y:
p_max.y = p.y
it.increment()
# get minimum and maximum coordinates
p_min, p_max = bounding_box(stroke)
stroke.resample(32 * self.__turns)
sv_nb = stroke.stroke_vertices_size()
# print("min :", p_min.x, p_min.y) # DEBUG
# print("mean :", p_sum.x, p_sum.y) # DEBUG
# print("max :", p_max.x, p_max.y) # DEBUG
# print("----------------------") # DEBUG
#######################################################
sv_nb = sv_nb // self.__turns
sv_nb = len(stroke) // self.__turns
center = (p_min + p_max) / 2
radius = (center.x - p_min.x + center.y - p_min.y) / 2
p_new = Vector((0.0, 0.0))
#######################################################
R = self.__random_radius
C = self.__random_center
i = 0
it = stroke.stroke_vertices_begin()
# The directions (and phases) are calculated using a seperate
# function decorated with an lru-cache. This guarantees that
# the directions (involving sin and cos) are calculated as few
# times as possible.
#
# This works because the phases and directions are only
# dependant on the stroke length, and the chance that
# stroke.resample() above produces strokes of the same length
# is quite high.
#
# In tests, the amount of calls to sin() and cos() went from
# over 21000 to just 32 times, yielding a speedup of over 100%
directions = phase_to_direction(sv_nb)
it = iter(stroke)
for j in range(self.__turns):
prev_radius = radius
prev_center = center
radius = radius + randint(-R, R)
center = center + Vector((randint(-C, C), randint(-C, C)))
while i < sv_nb and not it.is_end:
t = float(i) / float(sv_nb - 1)
r = prev_radius + (radius - prev_radius) * t
c = prev_center + (center - prev_center) * t
p_new.x = c.x + r * cos(2 * pi * t)
p_new.y = c.y + r * sin(2 * pi * t)
it.object.point = p_new
i = i + 1
it.increment()
i = 1
verticesToRemove = []
while not it.is_end:
verticesToRemove.append(it.object)
radius += randint(-R, R)
center += Vector((randint(-C, C), randint(-C, C)))
for (phase, direction), svert in zip(directions, it):
r = prev_radius + (radius - prev_radius) * phase
c = prev_center + (center - prev_center) * phase
svert.point = c + r * direction
if not it.is_end:
it.increment()
for sv in verticesToRemove:
stroke.remove_vertex(sv)
for sv in tuple(it):
stroke.remove_vertex(sv)
stroke.update_length()
class pyBluePrintEllipsesShader(StrokeShader):
"""
Draws the silhouette of the object as an ellips
"""
def __init__(self, turns=1, random_radius=3, random_center=5):
StrokeShader.__init__(self)
self.__turns = turns
@@ -1140,311 +861,300 @@ class pyBluePrintEllipsesShader(StrokeShader):
self.__random_radius = random_radius
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
if it.is_end:
return
p_min = it.object.point.copy()
p_max = it.object.point.copy()
while not it.is_end:
p = it.object.point
if p.x < p_min.x:
p_min.x = p.x
if p.x > p_max.x:
p_max.x = p.x
if p.y < p_min.y:
p_min.y = p.y
if p.y > p_max.y:
p_max.y = p.y
it.increment()
p_min, p_max = bounding_box(stroke)
stroke.resample(32 * self.__turns)
sv_nb = stroke.stroke_vertices_size()
sv_nb = sv_nb // self.__turns
sv_nb = len(stroke) // self.__turns
center = (p_min + p_max) / 2
radius = center - p_min
p_new = Vector((0.0, 0.0))
#######################################################
R = self.__random_radius
C = self.__random_center
i = 0
it = stroke.stroke_vertices_begin()
# for description of the line below, see pyBluePrintCirclesShader
directions = phase_to_direction(sv_nb)
it = iter(stroke)
for j in range(self.__turns):
prev_radius = radius
prev_center = center
radius = radius + Vector((randint(-R, R), randint(-R, R)))
center = center + Vector((randint(-C, C), randint(-C, C)))
while i < sv_nb and not it.is_end:
t = float(i) / float(sv_nb - 1)
r = prev_radius + (radius - prev_radius) * t
c = prev_center + (center - prev_center) * t
p_new.x = c.x + r.x * cos(2 * pi * t)
p_new.y = c.y + r.y * sin(2 * pi * t)
it.object.point = p_new
i = i + 1
it.increment()
i = 1
verticesToRemove = []
while not it.is_end:
verticesToRemove.append(it.object)
for (phase, direction), svert in zip(directions, it):
r = prev_radius + (radius - prev_radius) * phase
c = prev_center + (center - prev_center) * phase
svert.point = (c.x + r.x * direction.x, c.y + r.y * direction.y)
# remove exessive vertices
if not it.is_end:
it.increment()
for sv in verticesToRemove:
stroke.remove_vertex(sv)
for sv in tuple(it):
stroke.remove_vertex(sv)
stroke.update_length()
class pyBluePrintSquaresShader(StrokeShader):
"""
Draws the silhouette of the object as a square
"""
def __init__(self, turns=1, bb_len=10, bb_rand=0):
StrokeShader.__init__(self)
self.__turns = turns
self.__turns = turns # does not have any effect atm
self.__bb_len = bb_len
self.__bb_rand = bb_rand
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
if it.is_end:
# this condition will lead to errors later, end now
if len(stroke) < 1:
return
p_min = it.object.point.copy()
p_max = it.object.point.copy()
while not it.is_end:
p = it.object.point
if p.x < p_min.x:
p_min.x = p.x
if p.x > p_max.x:
p_max.x = p.x
if p.y < p_min.y:
p_min.y = p.y
if p.y > p_max.y:
p_max.y = p.y
it.increment()
# get minimum and maximum coordinates
p_min, p_max = bounding_box(stroke)
stroke.resample(32 * self.__turns)
sv_nb = stroke.stroke_vertices_size()
#######################################################
sv_nb = sv_nb // self.__turns
first = sv_nb // 4
second = 2 * first
third = 3 * first
fourth = sv_nb
p_first = Vector((p_min.x - self.__bb_len, p_min.y))
p_first_end = Vector((p_max.x + self.__bb_len, p_min.y))
p_second = Vector((p_max.x, p_min.y - self.__bb_len))
p_second_end = Vector((p_max.x, p_max.y + self.__bb_len))
p_third = Vector((p_max.x + self.__bb_len, p_max.y))
p_third_end = Vector((p_min.x - self.__bb_len, p_max.y))
p_fourth = Vector((p_min.x, p_max.y + self.__bb_len))
p_fourth_end = Vector((p_min.x, p_min.y - self.__bb_len))
#######################################################
R = self.__bb_rand
r = self.__bb_rand // 2
it = stroke.stroke_vertices_begin()
visible = True
num_segments = len(stroke) // self.__turns
f = num_segments // 4
# indices of the vertices that will form corners
first, second, third, fourth = (f, f * 2, f * 3, num_segments)
# construct points of the backbone
bb_len = self.__bb_len
points = (
Vector((p_min.x - bb_len, p_min.y)),
Vector((p_max.x + bb_len, p_min.y)),
Vector((p_max.x, p_min.y - bb_len)),
Vector((p_max.x, p_max.y + bb_len)),
Vector((p_max.x + bb_len, p_max.y)),
Vector((p_min.x - bb_len, p_max.y)),
Vector((p_min.x, p_max.y + bb_len)),
Vector((p_min.x, p_min.y - bb_len)),
)
# add randomization to the points (if needed)
if self.__bb_rand:
R, r = self.__bb_rand, self.__bb_rand // 2
randomization_mat = (
Vector((randint(-R, R), randint(-r, r))),
Vector((randint(-R, R), randint(-r, r))),
Vector((randint(-r, r), randint(-R, R))),
Vector((randint(-r, r), randint(-R, R))),
Vector((randint(-R, R), randint(-r, r))),
Vector((randint(-R, R), randint(-r, r))),
Vector((randint(-r, r), randint(-R, R))),
Vector((randint(-r, r), randint(-R, R))),
)
# combine both tuples
points = tuple(p + rand for (p, rand) in zip(points, randomization_mat))
# substract even from uneven; result is length four tuple of vectors
it = iter(points)
old_vecs = tuple(next(it) - current for current in it)
it = iter(stroke)
verticesToRemove = list()
for j in range(self.__turns):
p_first = p_first + Vector((randint(-R, R), randint(-r, r)))
p_first_end = p_first_end + Vector((randint(-R, R), randint(-r, r)))
p_second = p_second + Vector((randint(-r, r), randint(-R, R)))
p_second_end = p_second_end + Vector((randint(-r, r), randint(-R, R)))
p_third = p_third + Vector((randint(-R, R), randint(-r, r)))
p_third_end = p_third_end + Vector((randint(-R, R), randint(-r, r)))
p_fourth = p_fourth + Vector((randint(-r, r), randint(-R, R)))
p_fourth_end = p_fourth_end + Vector((randint(-r, r), randint(-R, R)))
vec_first = p_first_end - p_first
vec_second = p_second_end - p_second
vec_third = p_third_end - p_third
vec_fourth = p_fourth_end - p_fourth
i = 0
while i < sv_nb and not it.is_end:
for i, svert in zip(range(num_segments), it):
if i < first:
p_new = p_first + vec_first * float(i)/float(first - 1)
if i == first - 1:
visible = False
svert.point = points[0] + old_vecs[0] * i / (first - 1)
svert.attribute.visible = (i != first - 1)
elif i < second:
p_new = p_second + vec_second * float(i - first)/float(second - first - 1)
if i == second - 1:
visible = False
svert.point = points[2] + old_vecs[1] * (i - first) / (second - first - 1)
svert.attribute.visible = (i != second - 1)
elif i < third:
p_new = p_third + vec_third * float(i - second)/float(third - second - 1)
if i == third - 1:
visible = False
svert.point = points[4] + old_vecs[2] * (i - second) / (third - second - 1)
svert.attribute.visible = (i != third - 1)
elif i < fourth:
svert.point = points[6] + old_vecs[3] * (i - third) / (fourth - third - 1)
svert.attribute.visible = (i != fourth - 1)
else:
p_new = p_fourth + vec_fourth * float(i - third)/float(fourth - third - 1)
if i == fourth - 1:
visible = False
if it.object is None:
i = i + 1
it.increment()
if not visible:
visible = True
continue
it.object.point = p_new
it.object.attribute.visible = visible
if not visible:
visible = True
i = i + 1
it.increment()
verticesToRemove = []
while not it.is_end:
verticesToRemove.append(it.object)
# special case; remove these vertices
verticesToRemove.append(svert)
# remove exessive vertices (if any)
if not it.is_end:
it.increment()
for sv in verticesToRemove:
stroke.remove_vertex(sv)
verticesToRemove += [svert for svert in it]
for sv in verticesToRemove:
stroke.remove_vertex(sv)
stroke.update_length()
# needs a docstring
class pyBluePrintDirectedSquaresShader(StrokeShader):
"""
Replaces the stroke with a directed square
"""
def __init__(self, turns=1, bb_len=10, mult=1):
StrokeShader.__init__(self)
self.__mult = mult
self.__turns = turns
self.__bb_len = 1 + float(bb_len) / 100
self.__bb_len = 1 + bb_len * 0.01
def shade(self, stroke):
stroke.resample(32 * self.__turns)
p_mean = Vector((0.0, 0.0))
it = stroke.stroke_vertices_begin()
while not it.is_end:
p = it.object.point
p_mean = p_mean + p
it.increment()
sv_nb = stroke.stroke_vertices_size()
p_mean = p_mean / sv_nb
p_var_xx = 0
p_var_yy = 0
p_var_xy = 0
it = stroke.stroke_vertices_begin()
while not it.is_end:
p = it.object.point
p_var_xx = p_var_xx + pow(p.x - p_mean.x, 2)
p_var_yy = p_var_yy + pow(p.y - p_mean.y, 2)
p_var_xy = p_var_xy + (p.x - p_mean.x) * (p.y - p_mean.y)
it.increment()
p_var_xx = p_var_xx / sv_nb
p_var_yy = p_var_yy / sv_nb
p_var_xy = p_var_xy / sv_nb
## print(p_var_xx, p_var_yy, p_var_xy)
trace = p_var_xx + p_var_yy
det = p_var_xx * p_var_yy - p_var_xy * p_var_xy
n = len(stroke)
p_mean = (1 / n) * sum((svert.point for svert in stroke), Vector((0.0, 0.0)))
p_var = Vector((0, 0))
p_var_xy = 0.0
for d in (svert.point - p_mean for svert in stroke):
p_var += Vector((d.x ** 2, d.y ** 2))
p_var_xy += d.x * d.y
# divide by number of vertices
p_var /= n
p_var_xy /= n
trace = p_var.x + p_var.y
det = p_var.x * p_var.y - pow(p_var_xy, 2)
sqrt_coeff = sqrt(trace * trace - 4 * det)
lambda1 = (trace + sqrt_coeff) / 2
lambda2 = (trace - sqrt_coeff) / 2
## print(lambda1, lambda2)
theta = atan(2 * p_var_xy / (p_var_xx - p_var_yy)) / 2
## print(theta)
if p_var_yy > p_var_xx:
lambda1, lambda2 = (trace + sqrt_coeff) / 2, (trace - sqrt_coeff) / 2
# make sure those numers aren't to small, if they are, rooting them will yield complex numbers
lambda1, lambda2 = max(1e-12, lambda1), max(1e-12, lambda2)
theta = atan(2 * p_var_xy / (p_var.x - p_var.y)) / 2
if p_var.y > p_var.x:
e1 = Vector((cos(theta + pi / 2), sin(theta + pi / 2))) * sqrt(lambda1) * self.__mult
e2 = Vector((cos(theta + pi), sin(theta + pi))) * sqrt(lambda2) * self.__mult
e2 = Vector((cos(theta + pi ), sin(theta + pi ))) * sqrt(lambda2) * self.__mult
else:
e1 = Vector((cos(theta), sin(theta))) * sqrt(lambda1) * self.__mult
e1 = Vector((cos(theta), sin(theta))) * sqrt(lambda1) * self.__mult
e2 = Vector((cos(theta + pi / 2), sin(theta + pi / 2))) * sqrt(lambda2) * self.__mult
#######################################################
sv_nb = sv_nb // self.__turns
first = sv_nb // 4
second = 2 * first
third = 3 * first
fourth = sv_nb
# partition the stroke
num_segments = len(stroke) // self.__turns
f = num_segments // 4
# indices of the vertices that will form corners
first, second, third, fourth = (f, f * 2, f * 3, num_segments)
bb_len1 = self.__bb_len
bb_len2 = 1 + (bb_len1 - 1) * sqrt(lambda1 / lambda2)
p_first = p_mean - e1 - e2 * bb_len2
p_second = p_mean - e1 * bb_len1 + e2
p_third = p_mean + e1 + e2 * bb_len2
p_fourth = p_mean + e1 * bb_len1 - e2
vec_first = e2 * bb_len2 * 2
vec_second = e1 * bb_len1 * 2
vec_third = vec_first * -1
vec_fourth = vec_second * -1
#######################################################
it = stroke.stroke_vertices_begin()
visible = True
points = (
p_mean - e1 - e2 * bb_len2,
p_mean - e1 * bb_len1 + e2,
p_mean + e1 + e2 * bb_len2,
p_mean + e1 * bb_len1 - e2,
)
old_vecs = (
e2 * bb_len2 * 2,
e1 * bb_len1 * 2,
-e2 * bb_len2 * 2,
-e1 * bb_len1 * 2,
)
it = iter(stroke)
verticesToRemove = list()
for j in range(self.__turns):
i = 0
while i < sv_nb:
for i, svert in zip(range(num_segments), it):
if i < first:
p_new = p_first + vec_first * float(i)/float(first - 1)
if i == first - 1:
visible = False
svert.point = points[0] + old_vecs[0] * i / (first - 1)
svert.attribute.visible = (i != first - 1)
elif i < second:
p_new = p_second + vec_second * float(i - first)/float(second - first - 1)
if i == second - 1:
visible = False
svert.point = points[1] + old_vecs[1] * (i - first) / (second - first - 1)
svert.attribute.visible = (i != second - 1)
elif i < third:
p_new = p_third + vec_third * float(i - second)/float(third - second - 1)
if i == third - 1:
visible = False
svert.point = points[2] + old_vecs[2] * (i - second) / (third - second - 1)
svert.attribute.visible = (i != third - 1)
elif i < fourth:
svert.point = points[3] + old_vecs[3] * (i - third) / (fourth - third - 1)
svert.attribute.visible = (i != fourth - 1)
else:
p_new = p_fourth + vec_fourth * float(i - third)/float(fourth - third - 1)
if i == fourth - 1:
visible = False
it.object.point = p_new
it.object.attribute.visible = visible
if not visible:
visible = True
i = i + 1
it.increment()
verticesToRemove = []
while not it.is_end:
verticesToRemove.append(it.object)
# special case; remove these vertices
verticesToRemove.append(svert)
# remove exessive vertices
if not it.is_end:
it.increment()
for sv in verticesToRemove:
stroke.remove_vertex(sv)
verticesToRemove += [svert for svert in it]
for sv in verticesToRemove:
stroke.remove_vertex(sv)
stroke.update_length()
class pyModulateAlphaShader(StrokeShader):
"""
Limits the stroke's alpha between a min and max value
"""
def __init__(self, min=0, max=1):
StrokeShader.__init__(self)
self.__min = min
self.__max = max
# -- various (used in the parameter editor) -- #
class RoundCapShader(StrokeShader):
def round_cap_thickness(self, x):
x = max(0.0, min(x, 1.0))
return pow(1.0 - (x ** 2.0), 0.5)
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()
# save the location and attribute of stroke vertices
buffer = tuple((Vector(sv.point), StrokeAttribute(sv.attribute)) for sv in stroke)
nverts = len(buffer)
if nverts < 2:
return
# calculate the number of additional vertices to form caps
thickness_beg = sum(stroke[0].attribute.thickness)
caplen_beg = thickness_beg / 2.0
nverts_beg = max(5, int(thickness_beg))
thickness_end = sum(stroke[-1].attribute.thickness)
caplen_end = (thickness_end) / 2.0
nverts_end = max(5, int(thickness_end))
# adjust the total number of stroke vertices
stroke.resample(nverts + nverts_beg + nverts_end)
# restore the location and attribute of the original vertices
for i, (p, attr) in enumerate(buffer):
stroke[nverts_beg + i].point = p
stroke[nverts_beg + i].attribute = attr
# reshape the cap at the beginning of the stroke
q, attr = buffer[1]
p, attr = buffer[0]
direction = (p - q).normalized() * caplen_beg
n = 1.0 / nverts_beg
R, L = attr.thickness
for t, svert in zip(range(nverts_beg, 0, -1), stroke):
r = self.round_cap_thickness((t + 1) * n)
svert.point = p + direction * t * n
svert.attribute = attr
svert.attribute.thickness = (R * r, L * r)
# reshape the cap at the end of the stroke
q, attr = buffer[-2]
p, attr = buffer[-1]
direction = (p - q).normalized() * caplen_beg
n = 1.0 / nverts_end
R, L = attr.thickness
for t, svert in zip(range(nverts_end, 0, -1), reversed(stroke)):
r = self.round_cap_thickness((t + 1) * n)
svert.point = p + direction * t * n
svert.attribute = attr
svert.attribute.thickness = (R * r, L * r)
# update the curvilinear 2D length of each vertex
stroke.update_length()
## various
class pyDummyShader(StrokeShader):
class SquareCapShader(StrokeShader):
def shade(self, stroke):
it = stroke.stroke_vertices_begin()
while not it.is_end:
toto = Interface0DIterator(it)
att = it.object.attribute
att.color = (0.3, 0.4, 0.4)
att.thickness = (0, 5)
it.increment()
# save the location and attribute of stroke vertices
buffer = tuple((Vector(sv.point), StrokeAttribute(sv.attribute)) for sv in stroke)
nverts = len(buffer)
if nverts < 2:
return
# calculate the number of additional vertices to form caps
caplen_beg = sum(stroke[0].attribute.thickness) / 2.0
nverts_beg = 1
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)
caplen_end = sum(stroke[-1].attribute.thickness) / 2.0
nverts_end = 1
# adjust the total number of stroke vertices
stroke.resample(nverts + nverts_beg + nverts_end)
# restore the location and attribute of the original vertices
for i, (p, attr) in zip(range(nverts), buffer):
stroke[nverts_beg + i].point = p
stroke[nverts_beg + i].attribute = attr
# reshape the cap at the beginning of the stroke
q, attr = buffer[1]
p, attr = buffer[0]
stroke[0].point += (p - q).normalized() * caplen_beg
stroke[0].attribute = attr
# reshape the cap at the end of the stroke
q, attr = buffer[-2]
p, attr = buffer[-1]
stroke[-1].point += (p - q).normalized() * caplen_end
stroke[-1].attribute = attr
# update the curvilinear 2D length of each vertex
stroke.update_length()

View File

@@ -27,11 +27,275 @@ from _freestyle import (
integrate,
)
# constructs for definition of helper functions in Python
from freestyle.types import (
StrokeVertexIterator,
)
import mathutils
from mathutils import Vector
from functools import lru_cache
from math import cos, sin, pi
# -- real utility functions -- #
def rgb_to_bw(r, g, b):
""" Method to convert rgb to a bw intensity value. """
return 0.35 * r + 0.45 * g + 0.2 * b
def bound(lower, x, higher):
""" Returns x bounded by a maximum and minimum value. equivalent to:
return min(max(x, lower), higher)
"""
# this is about 50% quicker than min(max(x, lower), higher)
return (lower if x <= lower else higher if x >= higher else x)
def bounding_box(stroke):
"""
Returns the maximum and minimum coordinates (the bounding box) of the stroke's vertices
"""
x, y = zip(*(svert.point for svert in stroke))
return (Vector((min(x), min(y))), Vector((max(x), max(y))))
# -- General helper functions -- #
@lru_cache(maxsize=32)
def phase_to_direction(length):
"""
Returns a list of tuples each containing:
- the phase
- a Vector with the values of the cosine and sine of 2pi * phase (the direction)
"""
results = list()
for i in range(length):
phase = i / (length - 1)
results.append((phase, Vector((cos(2 * pi * phase), sin(2 * pi * phase)))))
return results
# -- helper functions for chaining -- #
def get_chain_length(ve, orientation):
"""Returns the 2d length of a given ViewEdge """
from freestyle.chainingiterators import pyChainSilhouetteGenericIterator
length = 0.0
# setup iterator
_it = pyChainSilhouetteGenericIterator(False, False)
_it.begin = ve
_it.current_edge = ve
_it.orientation = orientation
_it.init()
# run iterator till end of chain
while not (_it.is_end):
length += _it.object.length_2d
if (_it.is_begin):
# _it has looped back to the beginning;
# break to prevent infinite loop
break
_it.increment()
# reset iterator
_it.begin = ve
_it.current_edge = ve
_it.orientation = orientation
# run iterator till begin of chain
if not _it.is_begin:
_it.decrement()
while not (_it.is_end or _it.is_begin):
length += _it.object.length_2d
_it.decrement()
return length
def find_matching_vertex(id, it):
"""Finds the matching vertexn, or returns None """
return next((ve for ve in it if ve.id == id), None)
# -- helper functions for iterating -- #
def iter_current_previous(stroke):
"""
iterates over the given iterator. yields a tuple of the form
(it, prev, current)
"""
prev = stroke[0]
it = Interface0DIterator(stroke)
for current in it:
yield (it, prev, current)
def iter_t2d_along_stroke(stroke):
"""
Yields the distance between two stroke vertices
relative to the total stroke length.
"""
total = stroke.length_2d
distance = 0.0
for it, prev, svert in iter_current_previous(stroke):
distance += (prev.point - svert.point).length
t = min(distance / total, 1.0) if total > 0.0 else 0.0
yield (it, t)
def iter_distance_from_camera(stroke, range_min, range_max):
"""
Yields the distance to the camera relative to the maximum
possible distance for every stroke vertex, constrained by
given minimum and maximum values.
"""
normfac = range_max - range_min # normalization factor
it = Interface0DIterator(stroke)
for svert in it:
distance = svert.point_3d.length # in the camera coordinate
if distance < range_min:
t = 0.0
elif distance > range_max:
t = 1.0
else:
t = (distance - range_min) / normfac
yield (it, t)
def iter_distance_from_object(stroke, object, range_min, range_max):
"""
yields the distance to the given object relative to the maximum
possible distance for every stroke vertex, constrained by
given minimum and maximum values.
"""
scene = getCurrentScene()
mv = scene.camera.matrix_world.copy().inverted() # model-view matrix
loc = mv * object.location # loc in the camera coordinate
normfac = range_max - range_min # normalization factor
it = Interface0DIterator(stroke)
for svert in it:
distance = (svert.point_3d - loc).length # in the camera coordinate
if distance < range_min:
t = 0.0
elif distance > range_max:
t = 1.0
else:
t = (distance - range_min) / normfac
yield (it, t)
def iter_material_color(stroke, material_attribute):
"""
yields the specified material attribute for every stroke vertex.
the material is taken from the object behind the vertex.
"""
func = CurveMaterialF0D()
it = Interface0DIterator(stroke)
for inter in it:
material = func(it)
if material_attribute == 'DIFF':
color = material.diffuse[0:3]
elif material_attribute == 'SPEC':
color = material.specular[0:3]
else:
raise ValueError("unexpected material attribute: " + material_attribute)
yield (it, color)
def iter_material_value(stroke, material_attribute):
"""
yields a specific material attribute
from the vertex' underlying material.
"""
func = CurveMaterialF0D()
it = Interface0DIterator(stroke)
for svert in it:
material = func(it)
if material_attribute == 'DIFF':
t = rgb_to_bw(*material.diffuse[0:3])
elif material_attribute == 'DIFF_R':
t = material.diffuse[0]
elif material_attribute == 'DIFF_G':
t = material.diffuse[1]
elif material_attribute == 'DIFF_B':
t = material.diffuse[2]
elif material_attribute == 'SPEC':
t = rgb_to_bw(*material.specular[0:3])
elif material_attribute == 'SPEC_R':
t = material.specular[0]
elif material_attribute == 'SPEC_G':
t = material.specular[1]
elif material_attribute == 'SPEC_B':
t = material.specular[2]
elif material_attribute == 'SPEC_HARDNESS':
t = material.shininess
elif material_attribute == 'ALPHA':
t = material.diffuse[3]
else:
raise ValueError("unexpected material attribute: " + material_attribute)
yield (it, t)
def iter_distance_along_stroke(stroke):
"""
yields the absolute distance between
the current and preceding vertex.
"""
distance = 0.0
prev = stroke[0]
it = Interface0DIterator(stroke)
for svert in it:
p = svert.point
distance += (prev - p).length
prev = p.copy() # need a copy because the point can be altered
yield it, distance
def iter_triplet(it):
"""
Iterates over it, yielding a tuple containing
the current vertex and its immediate neighbors
"""
prev = next(it)
current = next(it)
for succ in it:
yield prev, current, succ
prev, current = current, succ
# -- mathmatical operations -- #
def stroke_curvature(it):
"""
Compute the 2D curvature at the stroke vertex pointed by the iterator 'it'.
K = 1 / R
where R is the radius of the circle going through the current vertex and its neighbors
"""
if it.is_end or it.is_begin:
return 0.0
next = it.incremented().point
prev = it.decremented().point
current = it.object.point
ab = (current - prev)
bc = (next - current)
ac = (prev - next)
a, b, c = ab.length, bc.length, ac.length
try:
area = 0.5 * ab.cross(ac)
K = (4 * area) / (a * b * c)
K = bound(0.0, K, 1.0)
except ZeroDivisionError:
K = 0.0
return K
def stroke_normal(it):
@@ -42,28 +306,21 @@ def stroke_normal(it):
they have already been modified by stroke geometry modifiers.
"""
# first stroke segment
it_next = StrokeVertexIterator(it)
it_next.increment()
it_next = it.incremented()
if it.is_begin:
e = it_next.object.point_2d - it.object.point_2d
n = mathutils.Vector((e[1], -e[0]))
n.normalize()
return n
n = Vector((e[1], -e[0]))
return n.normalized()
# last stroke segment
it_prev = StrokeVertexIterator(it)
it_prev.decrement()
it_prev = it.decremented()
if it_next.is_end:
e = it.object.point_2d - it_prev.object.point_2d
n = mathutils.Vector((e[1], -e[0]))
n.normalize()
return n
n = Vector((e[1], -e[0]))
return n.normalized()
# two subsequent stroke segments
e1 = it_next.object.point_2d - it.object.point_2d
e2 = it.object.point_2d - it_prev.object.point_2d
n1 = mathutils.Vector((e1[1], -e1[0]))
n2 = mathutils.Vector((e2[1], -e2[0]))
n1.normalize()
n2.normalize()
n = n1 + n2
n.normalize()
return n
n1 = Vector((e1[1], -e1[0])).normalized()
n2 = Vector((e2[1], -e2[0])).normalized()
n = (n1 + n2)
return n.normalized()