This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/release/scripts/freestyle/modules/freestyle/chainingiterators.py
2015-02-27 15:57:59 +11:00

652 lines
22 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
"""
This module contains chaining iterators used for the chaining
operation to construct long strokes by concatenating feature edges
according to selected chaining rules. The module is also intended to
be a collection of examples for defining chaining iterators in Python.
"""
__all__ = (
"ChainPredicateIterator",
"ChainSilhouetteIterator",
"pyChainSilhouetteIterator",
"pyChainSilhouetteGenericIterator",
"pyExternalContourChainingIterator",
"pySketchyChainSilhouetteIterator",
"pySketchyChainingIterator",
"pyFillOcclusionsRelativeChainingIterator",
"pyFillOcclusionsAbsoluteChainingIterator",
"pyFillOcclusionsAbsoluteAndRelativeChainingIterator",
"pyFillQi0AbsoluteAndRelativeChainingIterator",
"pyNoIdChainSilhouetteIterator",
)
# module members
from _freestyle import (
ChainPredicateIterator,
ChainSilhouetteIterator,
)
# constructs for predicate definition in Python
from freestyle.types import (
AdjacencyIterator,
ChainingIterator,
Nature,
TVertex,
)
from freestyle.predicates import (
ExternalContourUP1D,
)
from freestyle.utils import (
ContextFunctions as CF,
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 that follows the edges of the same nature
following the topology of objects, with decreasing priority for
silhouettes, then borders, then suggestive contours, then all other edge
types. A ViewEdge is only chained once.
"""
def __init__(self, stayInSelection=True):
ChainingIterator.__init__(self, stayInSelection, True, None, True)
def init(self):
pass
def traverse(self, iter):
it = AdjacencyIterator(iter)
## 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):
"""
Natural chaining iterator that follows the edges of the same nature
following the topology of objects, with decreasing priority for
silhouettes, then borders, then suggestive contours, then all other
edge types.
.. method:: __init__(self, stayInSelection=True, stayInUnvisited=True)
Builds a pyChainSilhouetteGenericIterator object.
:arg stayInSelection: True if it is allowed to go out of the selection
:type stayInSelection: bool
:arg stayInUnvisited: May the same ViewEdge be chained twice
:type stayInUnvisited: bool
"""
def __init__(self, stayInSelection=True, stayInUnvisited=True):
ChainingIterator.__init__(self, stayInSelection, stayInUnvisited, None, True)
def init(self):
pass
def traverse(self, iter):
it = AdjacencyIterator(iter)
## 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):
"""Chains by external contour"""
def __init__(self):
ChainingIterator.__init__(self, False, True, None, True)
self.ExternalContour = ExternalContourUP1D()
def init(self):
self._nEdges = 0
def checkViewEdge(self, ve, orientation):
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 result
def traverse(self, iter):
winner = None
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
if winner is None:
it = AdjacencyIterator(iter)
for ve in it:
if self.checkViewEdge(ve, not it.is_incoming):
winner = ve
return winner
class pySketchyChainSilhouetteIterator(ChainingIterator):
"""
Natural chaining iterator with a sketchy multiple touch. It chains the
same ViewEdge multiple times to achieve a sketchy effect.
.. method:: __init__(self, nRounds=3,stayInSelection=True)
Builds a pySketchyChainSilhouetteIterator object.
:arg nRounds: Number of times every Viewedge is chained.
:type nRounds: int
:arg stayInSelection: if False, edges outside of the selection can be chained.
:type stayInSelection: bool
"""
def __init__(self, nRounds=3,stayInSelection=True):
ChainingIterator.__init__(self, stayInSelection, False, None, True)
self._timeStamp = CF.get_time_stamp() + nRounds
self._nRounds = nRounds
def init(self):
self._timeStamp = CF.get_time_stamp() + self._nRounds
# 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):
it = AdjacencyIterator(iter)
## 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):
"""
Chaining iterator designed for sketchy style. It chains the same
ViewEdge several times in order to produce multiple strokes per
ViewEdge.
"""
def __init__(self, nRounds=3, stayInSelection=True):
ChainingIterator.__init__(self, stayInSelection, False, None, True)
self._timeStamp = CF.get_time_stamp() + nRounds
self._nRounds = nRounds
self.t = False
def init(self):
self._timeStamp = CF.get_time_stamp() + self._nRounds
def traverse(self, iter):
winner = None
found = False
for ve in AdjacencyIterator(iter):
if self.current_edge.id == ve.id:
found = True
continue
winner = ve
if not found:
# This is a fatal error condition: self.current_edge must be found
# among the edges seen by the AdjacencyIterator [bug #35695].
if bpy.app.debug_freestyle:
print('pySketchyChainingIterator: current edge not found')
return None
if winner is None:
winner = self.current_edge
if winner.chaining_time_stamp == self._timeStamp:
return None
return winner
class pyFillOcclusionsRelativeChainingIterator(ChainingIterator):
"""
Chaining iterator that fills small occlusions
.. method:: __init__(self, percent)
Builds a pyFillOcclusionsRelativeChainingIterator object.
:arg percent: The maximal length of the occluded part, expressed
in a percentage of the total chain length.
:type percent: float
"""
def __init__(self, percent):
ChainingIterator.__init__(self, False, True, None, True)
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.0
def traverse(self, iter):
winner = None
winnerOrientation = False
it = AdjacencyIterator(iter)
## 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:
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
break
# check timestamp to see if this edge was part of the selection
if winner is not None and winner.time_stamp != self.timestamp:
# if the edge wasn't part of the selection, let's see
# whether it's short enough (with respect to self.percent)
# to be included.
if self._length == 0.0:
self._length = get_chain_length(winner, winnerOrientation)
# check if the gap can be bridged
connexl = 0.0
_cit = pyChainSilhouetteGenericIterator(False, False)
_cit.begin = winner
_cit.current_edge = winner
_cit.orientation = winnerOrientation
_cit.init()
while (not _cit.is_end) and _cit.object.time_stamp != self.timestamp:
connexl += _cit.object.length_2d
_cit.increment()
if _cit.is_begin: break
if connexl > self._percent * self._length:
return None
return winner
class pyFillOcclusionsAbsoluteChainingIterator(ChainingIterator):
"""
Chaining iterator that fills small occlusions
.. method:: __init__(self, length)
Builds a pyFillOcclusionsAbsoluteChainingIterator object.
:arg length: The maximum length of the occluded part in pixels.
:type length: int
"""
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
def traverse(self, iter):
winner = None
winnerOrientation = False
it = AdjacencyIterator(iter)
## 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:
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
break
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
class pyFillOcclusionsAbsoluteAndRelativeChainingIterator(ChainingIterator):
"""
Chaining iterator that fills small occlusions regardless of the
selection.
.. method:: __init__(self, percent, l)
Builds a pyFillOcclusionsAbsoluteAndRelativeChainingIterator object.
:arg percent: The maximal length of the occluded part as a
percentage of the total chain length.
:type percent: float
:arg l: Absolute length.
:type l: float
"""
def __init__(self, percent, l):
ChainingIterator.__init__(self, False, True, None, True)
self._length = 0.0
self._absLength = l
self._percent = float(percent)
def init(self):
# each time we're evaluating a chain length
# we try to do it once. Thus we reinit
# the chain length here:
self._length = 0.0
def traverse(self, iter):
winner = None
winnerOrientation = False
it = AdjacencyIterator(iter)
## 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:
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
break
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 (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):
return None
return winner
class pyFillQi0AbsoluteAndRelativeChainingIterator(ChainingIterator):
"""
Chaining iterator that fills small occlusions regardless of the
selection.
.. method:: __init__(self, percent, l)
Builds a pyFillQi0AbsoluteAndRelativeChainingIterator object.
:arg percent: The maximal length of the occluded part as a
percentage of the total chain length.
:type percent: float
:arg l: Absolute length.
:type l: float
"""
def __init__(self, percent, l):
ChainingIterator.__init__(self, False, True, None, True)
self._length = 0.0
self._absLength = l
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.0
def traverse(self, iter):
winner = None
winnerOrientation = False
it = AdjacencyIterator(iter)
## 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:
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
break
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:
connexl += _cit.object.length_2d
_cit.increment()
if _cit.is_begin: break
if (connexl > self._percent * self._length) or (connexl > self._absLength):
return None
return winner
class pyNoIdChainSilhouetteIterator(ChainingIterator):
"""
Natural chaining iterator that follows the edges of the same nature
following the topology of objects, with decreasing priority for
silhouettes, then borders, then suggestive contours, then all other edge
types. It won't chain the same ViewEdge twice.
.. method:: __init__(self, stayInSelection=True)
Builds a pyNoIdChainSilhouetteIterator object.
:arg stayInSelection: True if it is allowed to go out of the selection
:type stayInSelection: bool
"""
def __init__(self, stayInSelection=True):
ChainingIterator.__init__(self, stayInSelection, True, None, True)
def init(self):
pass
def traverse(self, iter):
winner = None
it = AdjacencyIterator(iter)
# 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:
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:
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:
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:
return ve
return None
## case of NonTVertex
else:
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