Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module provides data types of view map components (0D and 1D
|
||||
elements), base classes for defining line stylization rules
|
||||
(predicates, functions, chaining iterators, and stroke shaders),
|
||||
as well as helper functions for style module writing.
|
||||
"""
|
||||
|
||||
|
||||
# module members
|
||||
from . import chainingiterators, functions, predicates, shaders, types, utils
|
||||
@@ -0,0 +1,737 @@
|
||||
# SPDX-FileCopyrightText: 2014-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(stayInSelection=True, stayInUnvisited=True)
|
||||
|
||||
Builds a pyChainSilhouetteGenericIterator object.
|
||||
|
||||
:param stayInSelection: True if it is allowed to go out of the selection
|
||||
:type stayInSelection: bool
|
||||
:param 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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Tests whether a ViewEdge belongs to the external contour.
|
||||
|
||||
:param ve: The ViewEdge to test.
|
||||
:type ve: :class:`ViewEdge`
|
||||
:param orientation: Iteration orientation.
|
||||
:type orientation: bool
|
||||
:rtype: bool
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(nRounds=3,stayInSelection=True)
|
||||
|
||||
Builds a pySketchyChainSilhouetteIterator object.
|
||||
|
||||
:param nRounds: Number of times every Viewedge is chained.
|
||||
:type nRounds: int
|
||||
:param 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 sketchy effect by causing the chain to run from
|
||||
the start again. (loop over itself again)
|
||||
|
||||
:param ve: The candidate ViewEdge, or None to fall back to the current edge.
|
||||
:type ve: :class:`ViewEdge` | None
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
if ve is None:
|
||||
ve = self.current_edge
|
||||
if ve.chaining_time_stamp == self._timeStamp:
|
||||
return None
|
||||
return ve
|
||||
|
||||
def traverse(self, iter):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(percent)
|
||||
|
||||
Builds a pyFillOcclusionsRelativeChainingIterator object.
|
||||
|
||||
:param 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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(length)
|
||||
|
||||
Builds a pyFillOcclusionsAbsoluteChainingIterator object.
|
||||
|
||||
:param 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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(percent, l)
|
||||
|
||||
Builds a pyFillOcclusionsAbsoluteAndRelativeChainingIterator object.
|
||||
|
||||
:param percent: The maximal length of the occluded part as a
|
||||
percentage of the total chain length.
|
||||
:type percent: float
|
||||
:param 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 reinitialize the chain length here:
|
||||
self._length = 0.0
|
||||
|
||||
def traverse(self, iter):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(percent, l)
|
||||
|
||||
Builds a pyFillQi0AbsoluteAndRelativeChainingIterator object.
|
||||
|
||||
:param percent: The maximal length of the occluded part as a
|
||||
percentage of the total chain length.
|
||||
:type percent: float
|
||||
:param 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 preferably be evaluated only once.
|
||||
# Therefore, the chain length is reset here.
|
||||
self._length = 0.0
|
||||
|
||||
def traverse(self, iter):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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__(stayInSelection=True)
|
||||
|
||||
Builds a pyNoIdChainSilhouetteIterator object.
|
||||
|
||||
:param 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):
|
||||
"""
|
||||
Returns the next ViewEdge to chain.
|
||||
|
||||
:param iter: An adjacency iterator over the candidate ViewEdges.
|
||||
:type iter: :class:`AdjacencyIterator`
|
||||
:return: The next ViewEdge, or None to stop chaining.
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
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
|
||||
313
blender-5.2.0/scripts/freestyle/modules/freestyle/functions.py
Normal file
313
blender-5.2.0/scripts/freestyle/modules/freestyle/functions.py
Normal file
@@ -0,0 +1,313 @@
|
||||
# SPDX-FileCopyrightText: 2014-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains functions operating on vertices (0D elements) and
|
||||
polylines (1D elements). The module is also intended to be a
|
||||
collection of examples for function definition in Python.
|
||||
|
||||
User-defined functions inherit one of the following base classes,
|
||||
depending on the object type (0D or 1D) to operate on and the return
|
||||
value type:
|
||||
|
||||
- :class:`freestyle.types.UnaryFunction0DDouble`
|
||||
- :class:`freestyle.types.UnaryFunction0DEdgeNature`
|
||||
- :class:`freestyle.types.UnaryFunction0DFloat`
|
||||
- :class:`freestyle.types.UnaryFunction0DId`
|
||||
- :class:`freestyle.types.UnaryFunction0DMaterial`
|
||||
- :class:`freestyle.types.UnaryFunction0DUnsigned`
|
||||
- :class:`freestyle.types.UnaryFunction0DVec2f`
|
||||
- :class:`freestyle.types.UnaryFunction0DVec3f`
|
||||
- :class:`freestyle.types.UnaryFunction0DVectorViewShape`
|
||||
- :class:`freestyle.types.UnaryFunction0DViewShape`
|
||||
- :class:`freestyle.types.UnaryFunction1DDouble`
|
||||
- :class:`freestyle.types.UnaryFunction1DEdgeNature`
|
||||
- :class:`freestyle.types.UnaryFunction1DFloat`
|
||||
- :class:`freestyle.types.UnaryFunction1DUnsigned`
|
||||
- :class:`freestyle.types.UnaryFunction1DVec2f`
|
||||
- :class:`freestyle.types.UnaryFunction1DVec3f`
|
||||
- :class:`freestyle.types.UnaryFunction1DVectorViewShape`
|
||||
- :class:`freestyle.types.UnaryFunction1DVoid`
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"ChainingTimeStampF1D",
|
||||
"Curvature2DAngleF0D",
|
||||
"Curvature2DAngleF1D",
|
||||
"CurveMaterialF0D",
|
||||
"CurveNatureF0D",
|
||||
"CurveNatureF1D",
|
||||
"DensityF0D",
|
||||
"DensityF1D",
|
||||
"GetCompleteViewMapDensityF1D",
|
||||
"GetCurvilinearAbscissaF0D",
|
||||
"GetDirectionalViewMapDensityF1D",
|
||||
"GetOccludeeF0D",
|
||||
"GetOccludeeF1D",
|
||||
"GetOccludersF0D",
|
||||
"GetOccludersF1D",
|
||||
"GetParameterF0D",
|
||||
"GetProjectedXF0D",
|
||||
"GetProjectedXF1D",
|
||||
"GetProjectedYF0D",
|
||||
"GetProjectedYF1D",
|
||||
"GetProjectedZF0D",
|
||||
"GetProjectedZF1D",
|
||||
"GetShapeF0D",
|
||||
"GetShapeF1D",
|
||||
"GetSteerableViewMapDensityF1D",
|
||||
"GetViewMapGradientNormF0D",
|
||||
"GetViewMapGradientNormF1D",
|
||||
"GetXF0D",
|
||||
"GetXF1D",
|
||||
"GetYF0D",
|
||||
"GetYF1D",
|
||||
"GetZF0D",
|
||||
"GetZF1D",
|
||||
"IncrementChainingTimeStampF1D",
|
||||
"LocalAverageDepthF0D",
|
||||
"LocalAverageDepthF1D",
|
||||
"MaterialF0D",
|
||||
"Normal2DF0D",
|
||||
"Normal2DF1D",
|
||||
"Orientation2DF1D",
|
||||
"Orientation3DF1D",
|
||||
"QuantitativeInvisibilityF0D",
|
||||
"QuantitativeInvisibilityF1D",
|
||||
"ReadCompleteViewMapPixelF0D",
|
||||
"ReadMapPixelF0D",
|
||||
"ReadSteerableViewMapPixelF0D",
|
||||
"ShapeIdF0D",
|
||||
"TimeStampF1D",
|
||||
"VertexOrientation2DF0D",
|
||||
"VertexOrientation3DF0D",
|
||||
"ZDiscontinuityF0D",
|
||||
"ZDiscontinuityF1D",
|
||||
"pyCurvilinearLengthF0D",
|
||||
"pyDensityAnisotropyF0D",
|
||||
"pyDensityAnisotropyF1D",
|
||||
"pyGetInverseProjectedZF1D",
|
||||
"pyGetSquareInverseProjectedZF1D",
|
||||
"pyInverseCurvature2DAngleF0D",
|
||||
"pyViewMapGradientNormF0D",
|
||||
"pyViewMapGradientNormF1D",
|
||||
"pyViewMapGradientVectorF0D",
|
||||
)
|
||||
|
||||
|
||||
# module members
|
||||
from _freestyle import (
|
||||
ChainingTimeStampF1D,
|
||||
Curvature2DAngleF0D,
|
||||
Curvature2DAngleF1D,
|
||||
CurveNatureF0D,
|
||||
CurveNatureF1D,
|
||||
DensityF0D,
|
||||
DensityF1D,
|
||||
GetCompleteViewMapDensityF1D,
|
||||
GetCurvilinearAbscissaF0D,
|
||||
GetDirectionalViewMapDensityF1D,
|
||||
GetOccludeeF0D,
|
||||
GetOccludeeF1D,
|
||||
GetOccludersF0D,
|
||||
GetOccludersF1D,
|
||||
GetParameterF0D,
|
||||
GetProjectedXF0D,
|
||||
GetProjectedXF1D,
|
||||
GetProjectedYF0D,
|
||||
GetProjectedYF1D,
|
||||
GetProjectedZF0D,
|
||||
GetProjectedZF1D,
|
||||
GetShapeF0D,
|
||||
GetShapeF1D,
|
||||
GetSteerableViewMapDensityF1D,
|
||||
GetViewMapGradientNormF0D,
|
||||
GetViewMapGradientNormF1D,
|
||||
GetXF0D,
|
||||
GetXF1D,
|
||||
GetYF0D,
|
||||
GetYF1D,
|
||||
GetZF0D,
|
||||
GetZF1D,
|
||||
IncrementChainingTimeStampF1D,
|
||||
LocalAverageDepthF0D,
|
||||
LocalAverageDepthF1D,
|
||||
MaterialF0D,
|
||||
Normal2DF0D,
|
||||
Normal2DF1D,
|
||||
Orientation2DF1D,
|
||||
Orientation3DF1D,
|
||||
QuantitativeInvisibilityF0D,
|
||||
QuantitativeInvisibilityF1D,
|
||||
ReadCompleteViewMapPixelF0D,
|
||||
ReadMapPixelF0D,
|
||||
ReadSteerableViewMapPixelF0D,
|
||||
ShapeIdF0D,
|
||||
TimeStampF1D,
|
||||
VertexOrientation2DF0D,
|
||||
VertexOrientation3DF0D,
|
||||
ZDiscontinuityF0D,
|
||||
ZDiscontinuityF1D,
|
||||
)
|
||||
|
||||
# constructs for function definition in Python
|
||||
from freestyle.types import (
|
||||
CurvePoint,
|
||||
IntegrationType,
|
||||
UnaryFunction0DDouble,
|
||||
UnaryFunction0DMaterial,
|
||||
UnaryFunction0DVec2f,
|
||||
UnaryFunction1DDouble,
|
||||
)
|
||||
from freestyle.utils import ContextFunctions as CF
|
||||
from freestyle.utils import integrate
|
||||
|
||||
from mathutils import Vector
|
||||
|
||||
# -- Functions for 0D elements (vertices) -- #
|
||||
|
||||
|
||||
class CurveMaterialF0D(UnaryFunction0DMaterial):
|
||||
"""
|
||||
A replacement of the built-in MaterialF0D for stroke creation.
|
||||
MaterialF0D does not work with Curves and Strokes. Line color
|
||||
priority is used to pick one of the two materials at material
|
||||
boundaries.
|
||||
|
||||
Notes: expects instances of CurvePoint to be iterated over
|
||||
can return None if no fedge can be found
|
||||
"""
|
||||
|
||||
def __call__(self, inter):
|
||||
fe = inter.object.fedge
|
||||
if fe is None:
|
||||
return None
|
||||
if fe.is_smooth:
|
||||
return fe.material
|
||||
else:
|
||||
right, left = fe.material_right, fe.material_left
|
||||
return right if (right.priority > left.priority) else left
|
||||
|
||||
|
||||
class pyInverseCurvature2DAngleF0D(UnaryFunction0DDouble):
|
||||
def __call__(self, inter):
|
||||
func = Curvature2DAngleF0D()
|
||||
c = func(inter)
|
||||
return (3.1415 - c)
|
||||
|
||||
|
||||
class pyCurvilinearLengthF0D(UnaryFunction0DDouble):
|
||||
def __call__(self, inter):
|
||||
cp = inter.object
|
||||
assert isinstance(cp, CurvePoint)
|
||||
return cp.t2d
|
||||
|
||||
|
||||
class pyDensityAnisotropyF0D(UnaryFunction0DDouble):
|
||||
"""Estimates the anisotropy of density."""
|
||||
|
||||
def __init__(self, level):
|
||||
UnaryFunction0DDouble.__init__(self)
|
||||
self.IsoDensity = ReadCompleteViewMapPixelF0D(level)
|
||||
self.d0Density = ReadSteerableViewMapPixelF0D(0, level)
|
||||
self.d1Density = ReadSteerableViewMapPixelF0D(1, level)
|
||||
self.d2Density = ReadSteerableViewMapPixelF0D(2, level)
|
||||
self.d3Density = ReadSteerableViewMapPixelF0D(3, level)
|
||||
|
||||
def __call__(self, inter):
|
||||
c_iso = self.IsoDensity(inter)
|
||||
c_0 = self.d0Density(inter)
|
||||
c_1 = self.d1Density(inter)
|
||||
c_2 = self.d2Density(inter)
|
||||
c_3 = self.d3Density(inter)
|
||||
cMax = max(max(c_0, c_1), max(c_2, c_3))
|
||||
cMin = min(min(c_0, c_1), min(c_2, c_3))
|
||||
return 0 if (c_iso == 0) else (cMax - cMin) / c_iso
|
||||
|
||||
|
||||
class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f):
|
||||
"""
|
||||
Returns the gradient vector for a pixel.
|
||||
|
||||
.. method:: __init__(level)
|
||||
|
||||
Builds a pyViewMapGradientVectorF0D object.
|
||||
|
||||
:param level: the level at which to compute the gradient
|
||||
:type level: int
|
||||
"""
|
||||
|
||||
def __init__(self, level):
|
||||
UnaryFunction0DVec2f.__init__(self)
|
||||
self._l = level
|
||||
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))
|
||||
return Vector((gx, gy))
|
||||
|
||||
|
||||
class pyViewMapGradientNormF0D(UnaryFunction0DDouble):
|
||||
def __init__(self, l):
|
||||
UnaryFunction0DDouble.__init__(self)
|
||||
self._l = 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))
|
||||
return Vector((gx, gy)).length
|
||||
|
||||
# -- Functions for 1D elements (curves) -- #
|
||||
|
||||
|
||||
class pyGetInverseProjectedZF1D(UnaryFunction1DDouble):
|
||||
def __call__(self, inter):
|
||||
func = GetProjectedZF1D()
|
||||
z = func(inter)
|
||||
return (1.0 - z)
|
||||
|
||||
|
||||
class pyGetSquareInverseProjectedZF1D(UnaryFunction1DDouble):
|
||||
def __call__(self, inter):
|
||||
func = GetProjectedZF1D()
|
||||
z = func(inter)
|
||||
return (1.0 - pow(z, 2))
|
||||
|
||||
|
||||
class pyDensityAnisotropyF1D(UnaryFunction1DDouble):
|
||||
def __init__(self, level, integrationType=IntegrationType.MEAN, sampling=2.0):
|
||||
UnaryFunction1DDouble.__init__(self, integrationType)
|
||||
self._func = pyDensityAnisotropyF0D(level)
|
||||
self._integration = integrationType
|
||||
self._sampling = sampling
|
||||
|
||||
def __call__(self, inter):
|
||||
v = integrate(
|
||||
self._func, inter.points_begin(
|
||||
self._sampling), inter.points_end(
|
||||
self._sampling), self._integration)
|
||||
return v
|
||||
|
||||
|
||||
class pyViewMapGradientNormF1D(UnaryFunction1DDouble):
|
||||
def __init__(self, l, integrationType, sampling=2.0):
|
||||
UnaryFunction1DDouble.__init__(self, integrationType)
|
||||
self._func = pyViewMapGradientNormF0D(l)
|
||||
self._integration = integrationType
|
||||
self._sampling = sampling
|
||||
|
||||
def __call__(self, inter):
|
||||
v = integrate(
|
||||
self._func, inter.points_begin(
|
||||
self._sampling), inter.points_end(
|
||||
self._sampling), self._integration)
|
||||
return v
|
||||
670
blender-5.2.0/scripts/freestyle/modules/freestyle/predicates.py
Normal file
670
blender-5.2.0/scripts/freestyle/modules/freestyle/predicates.py
Normal file
@@ -0,0 +1,670 @@
|
||||
# SPDX-FileCopyrightText: 2014-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains predicates operating on vertices (0D elements)
|
||||
and polylines (1D elements). It is also intended to be a collection
|
||||
of examples for predicate definition in Python.
|
||||
|
||||
User-defined predicates inherit one of the following base classes,
|
||||
depending on the object type (0D or 1D) to operate on and the arity
|
||||
(unary or binary):
|
||||
|
||||
- :class:`freestyle.types.BinaryPredicate0D`
|
||||
- :class:`freestyle.types.BinaryPredicate1D`
|
||||
- :class:`freestyle.types.UnaryPredicate0D`
|
||||
- :class:`freestyle.types.UnaryPredicate1D`
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"AndBP1D",
|
||||
"AndUP1D",
|
||||
"ContourUP1D",
|
||||
"DensityLowerThanUP1D",
|
||||
"EqualToChainingTimeStampUP1D",
|
||||
"EqualToTimeStampUP1D",
|
||||
"ExternalContourUP1D",
|
||||
"FalseBP1D",
|
||||
"FalseUP0D",
|
||||
"FalseUP1D",
|
||||
"Length2DBP1D",
|
||||
"MaterialBP1D",
|
||||
"NotBP1D",
|
||||
"NotUP1D",
|
||||
"ObjectNamesUP1D",
|
||||
"OrBP1D",
|
||||
"OrUP1D",
|
||||
"QuantitativeInvisibilityRangeUP1D",
|
||||
"QuantitativeInvisibilityUP1D",
|
||||
"SameShapeIdBP1D",
|
||||
"ShapeUP1D",
|
||||
"TrueBP1D",
|
||||
"TrueUP0D",
|
||||
"TrueUP1D",
|
||||
"ViewMapGradientNormBP1D",
|
||||
"WithinImageBoundaryUP1D",
|
||||
"pyBackTVertexUP0D",
|
||||
"pyClosedCurveUP1D",
|
||||
"pyDensityFunctorUP1D",
|
||||
"pyDensityUP1D",
|
||||
"pyDensityVariableSigmaUP1D",
|
||||
"pyHighDensityAnisotropyUP1D",
|
||||
"pyHighDirectionalViewMapDensityUP1D",
|
||||
"pyHighSteerableViewMapDensityUP1D",
|
||||
"pyHighViewMapDensityUP1D",
|
||||
"pyHighViewMapGradientNormUP1D",
|
||||
"pyHigherCurvature2DAngleUP0D",
|
||||
"pyHigherLengthUP1D",
|
||||
"pyHigherNumberOfTurnsUP1D",
|
||||
"pyIsInOccludersListUP1D",
|
||||
"pyIsOccludedByIdListUP1D",
|
||||
"pyIsOccludedByItselfUP1D",
|
||||
"pyIsOccludedByUP1D",
|
||||
"pyLengthBP1D",
|
||||
"pyLowDirectionalViewMapDensityUP1D",
|
||||
"pyLowSteerableViewMapDensityUP1D",
|
||||
"pyNFirstUP1D",
|
||||
"pyNatureBP1D",
|
||||
"pyNatureUP1D",
|
||||
"pyParameterUP0D",
|
||||
"pyParameterUP0DGoodOne",
|
||||
"pyProjectedXBP1D",
|
||||
"pyProjectedYBP1D",
|
||||
"pyShapeIdListUP1D",
|
||||
"pyShapeIdUP1D",
|
||||
"pyShuffleBP1D",
|
||||
"pySilhouetteFirstBP1D",
|
||||
"pyUEqualsUP0D",
|
||||
"pyVertexNatureUP0D",
|
||||
"pyViewMapGradientNormBP1D",
|
||||
"pyZBP1D",
|
||||
"pyZDiscontinuityBP1D",
|
||||
"pyZSmallerUP1D",
|
||||
)
|
||||
|
||||
|
||||
# module members
|
||||
from _freestyle import (
|
||||
ContourUP1D,
|
||||
DensityLowerThanUP1D,
|
||||
EqualToChainingTimeStampUP1D,
|
||||
EqualToTimeStampUP1D,
|
||||
ExternalContourUP1D,
|
||||
FalseBP1D,
|
||||
FalseUP0D,
|
||||
FalseUP1D,
|
||||
Length2DBP1D,
|
||||
QuantitativeInvisibilityUP1D,
|
||||
SameShapeIdBP1D,
|
||||
ShapeUP1D,
|
||||
TrueBP1D,
|
||||
TrueUP0D,
|
||||
TrueUP1D,
|
||||
ViewMapGradientNormBP1D,
|
||||
WithinImageBoundaryUP1D,
|
||||
)
|
||||
|
||||
# constructs for predicate definition in Python
|
||||
from freestyle.types import (
|
||||
BinaryPredicate1D,
|
||||
Id,
|
||||
IntegrationType,
|
||||
Interface0DIterator,
|
||||
Nature,
|
||||
TVertex,
|
||||
UnaryPredicate0D,
|
||||
UnaryPredicate1D,
|
||||
)
|
||||
from freestyle.functions import (
|
||||
Curvature2DAngleF0D,
|
||||
CurveNatureF1D,
|
||||
DensityF1D,
|
||||
GetCompleteViewMapDensityF1D,
|
||||
GetCurvilinearAbscissaF0D,
|
||||
GetDirectionalViewMapDensityF1D,
|
||||
GetOccludersF1D,
|
||||
GetProjectedXF1D,
|
||||
GetProjectedYF1D,
|
||||
GetProjectedZF1D,
|
||||
GetShapeF1D,
|
||||
GetSteerableViewMapDensityF1D,
|
||||
GetZF1D,
|
||||
QuantitativeInvisibilityF0D,
|
||||
ZDiscontinuityF1D,
|
||||
pyCurvilinearLengthF0D,
|
||||
pyDensityAnisotropyF1D,
|
||||
pyViewMapGradientNormF1D,
|
||||
)
|
||||
|
||||
from freestyle.utils import material_from_fedge
|
||||
|
||||
import random
|
||||
|
||||
|
||||
# -- Unary predicates for 0D elements (vertices) -- #
|
||||
|
||||
class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D):
|
||||
def __init__(self, a):
|
||||
UnaryPredicate0D.__init__(self)
|
||||
self._a = a
|
||||
self.func = Curvature2DAngleF0D()
|
||||
|
||||
def __call__(self, inter):
|
||||
return (self.func(inter) > self._a)
|
||||
|
||||
|
||||
class pyUEqualsUP0D(UnaryPredicate0D):
|
||||
def __init__(self, u, w):
|
||||
UnaryPredicate0D.__init__(self)
|
||||
self._u = u
|
||||
self._w = w
|
||||
self._func = pyCurvilinearLengthF0D()
|
||||
|
||||
def __call__(self, inter):
|
||||
u = self._func(inter)
|
||||
return (u > (self._u - self._w)) and (u < (self._u + self._w))
|
||||
|
||||
|
||||
class pyVertexNatureUP0D(UnaryPredicate0D):
|
||||
def __init__(self, nature):
|
||||
UnaryPredicate0D.__init__(self)
|
||||
self._nature = nature
|
||||
|
||||
def __call__(self, inter):
|
||||
return bool(inter.object.nature & self._nature)
|
||||
|
||||
|
||||
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 not (iter.object.nature & Nature.T_VERTEX) or iter.is_end:
|
||||
return False
|
||||
return self._getQI(iter) != 0
|
||||
|
||||
|
||||
class pyParameterUP0DGoodOne(UnaryPredicate0D):
|
||||
def __init__(self, pmin, pmax):
|
||||
UnaryPredicate0D.__init__(self)
|
||||
self._m = pmin
|
||||
self._M = pmax
|
||||
|
||||
def __call__(self, inter):
|
||||
u = inter.u
|
||||
return ((u >= self._m) and (u <= self._M))
|
||||
|
||||
|
||||
class pyParameterUP0D(UnaryPredicate0D):
|
||||
def __init__(self, pmin, pmax):
|
||||
UnaryPredicate0D.__init__(self)
|
||||
self._m = pmin
|
||||
self._M = pmax
|
||||
self._func = Curvature2DAngleF0D()
|
||||
|
||||
def __call__(self, inter):
|
||||
c = self._func(inter)
|
||||
b1 = (c > 0.1)
|
||||
u = inter.u
|
||||
b = ((u >= self._m) and (u <= self._M))
|
||||
return (b and b1)
|
||||
|
||||
|
||||
# -- Unary predicates for 1D elements (curves) -- #
|
||||
|
||||
class AndUP1D(UnaryPredicate1D):
|
||||
def __init__(self, *predicates):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self.predicates = predicates
|
||||
correct_types = all(isinstance(p, UnaryPredicate1D) for p in self.predicates)
|
||||
if not (correct_types and predicates):
|
||||
raise TypeError("%s: Expected one or more UnaryPredicate1D, got %r" %
|
||||
(self.__class__.__name__, self.predicates))
|
||||
|
||||
def __call__(self, inter):
|
||||
return all(pred(inter) for pred in self.predicates)
|
||||
|
||||
|
||||
class OrUP1D(UnaryPredicate1D):
|
||||
def __init__(self, *predicates):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self.predicates = predicates
|
||||
correct_types = all(isinstance(p, UnaryPredicate1D) for p in self.predicates)
|
||||
if not (correct_types and predicates):
|
||||
raise TypeError("%s: Expected one or more UnaryPredicate1D, got %r" %
|
||||
(self.__class__.__name__, self.predicates))
|
||||
|
||||
def __call__(self, inter):
|
||||
return any(pred(inter) for pred in self.predicates)
|
||||
|
||||
|
||||
class NotUP1D(UnaryPredicate1D):
|
||||
def __init__(self, pred):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self.predicate = pred
|
||||
|
||||
def __call__(self, inter):
|
||||
return not self.predicate(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)
|
||||
self.__n = n
|
||||
self.__count = 0
|
||||
|
||||
def __call__(self, inter):
|
||||
self.__count += 1
|
||||
return (self.__count <= self.__n)
|
||||
|
||||
|
||||
class pyHigherLengthUP1D(UnaryPredicate1D):
|
||||
def __init__(self, l):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._l = l
|
||||
|
||||
def __call__(self, inter):
|
||||
return (inter.length_2d > self._l)
|
||||
|
||||
|
||||
class pyNatureUP1D(UnaryPredicate1D):
|
||||
def __init__(self, nature):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._nature = nature
|
||||
self._getNature = CurveNatureF1D()
|
||||
|
||||
def __call__(self, inter):
|
||||
return bool(self._getNature(inter) & self._nature)
|
||||
|
||||
|
||||
class pyHigherNumberOfTurnsUP1D(UnaryPredicate1D):
|
||||
def __init__(self, n, a):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._n = n
|
||||
self._a = a
|
||||
self.func = Curvature2DAngleF0D()
|
||||
|
||||
def __call__(self, inter):
|
||||
it = Interface0DIterator(inter)
|
||||
# sum the turns, check against n
|
||||
return sum(1 for _ in it if self.func(it) > self._a) > self._n
|
||||
# interesting fact, the line above is 70% faster than:
|
||||
# return sum(self.func(it) > self._a for _ in it) > self._n
|
||||
|
||||
|
||||
class pyDensityUP1D(UnaryPredicate1D):
|
||||
def __init__(self, wsize, threshold, integration=IntegrationType.MEAN, sampling=2.0):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._wsize = wsize
|
||||
self._threshold = threshold
|
||||
self._integration = integration
|
||||
self._func = DensityF1D(self._wsize, self._integration, sampling)
|
||||
|
||||
def __call__(self, inter):
|
||||
return (self._func(inter) < self._threshold)
|
||||
|
||||
|
||||
class pyLowSteerableViewMapDensityUP1D(UnaryPredicate1D):
|
||||
def __init__(self, threshold, level, integration=IntegrationType.MEAN):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._threshold = threshold
|
||||
self._level = level
|
||||
self._integration = integration
|
||||
|
||||
def __call__(self, inter):
|
||||
func = GetSteerableViewMapDensityF1D(self._level, self._integration)
|
||||
return (func(inter) < self._threshold)
|
||||
|
||||
|
||||
class pyLowDirectionalViewMapDensityUP1D(UnaryPredicate1D):
|
||||
def __init__(self, threshold, orientation, level, integration=IntegrationType.MEAN):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._threshold = threshold
|
||||
self._orientation = orientation
|
||||
self._level = level
|
||||
self._integration = integration
|
||||
|
||||
def __call__(self, inter):
|
||||
func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration)
|
||||
return (func(inter) < self._threshold)
|
||||
|
||||
|
||||
class pyHighSteerableViewMapDensityUP1D(UnaryPredicate1D):
|
||||
def __init__(self, threshold, level, integration=IntegrationType.MEAN):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._threshold = threshold
|
||||
self._func = GetSteerableViewMapDensityF1D(level, integration)
|
||||
|
||||
def __call__(self, inter):
|
||||
return (self._func(inter) > self._threshold)
|
||||
|
||||
|
||||
class pyHighDirectionalViewMapDensityUP1D(UnaryPredicate1D):
|
||||
def __init__(self, threshold, orientation, level, integration=IntegrationType.MEAN, sampling=2.0):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._threshold = threshold
|
||||
self._func = GetDirectionalViewMapDensityF1D(orientation, level, integration, sampling)
|
||||
|
||||
def __call__(self, inter):
|
||||
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._func = GetCompleteViewMapDensityF1D(level, integration, sampling)
|
||||
|
||||
def __call__(self, inter):
|
||||
return (self._func(inter) > self._threshold)
|
||||
|
||||
|
||||
class pyDensityFunctorUP1D(UnaryPredicate1D):
|
||||
def __init__(self, wsize, threshold, functor, funcmin=0.0, funcmax=1.0, integration=IntegrationType.MEAN):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._threshold = float(threshold)
|
||||
self._functor = functor
|
||||
self._funcmin = float(funcmin)
|
||||
self._funcmax = float(funcmax)
|
||||
self._func = DensityF1D(wsize, integration)
|
||||
|
||||
def __call__(self, inter):
|
||||
res = self._functor(inter)
|
||||
k = (res - self._funcmin) / (self._funcmax - self._funcmin)
|
||||
return (func(inter) < (self._threshold * k))
|
||||
|
||||
|
||||
class pyZSmallerUP1D(UnaryPredicate1D):
|
||||
def __init__(self, z, integration=IntegrationType.MEAN):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._z = z
|
||||
self.func = GetProjectedZF1D(integration)
|
||||
|
||||
def __call__(self, inter):
|
||||
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):
|
||||
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()
|
||||
|
||||
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()
|
||||
if any(ve.id == self._id for (ve, incoming) in eit):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class pyIsInOccludersListUP1D(UnaryPredicate1D):
|
||||
def __init__(self, id):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._id = id
|
||||
|
||||
def __call__(self, inter):
|
||||
occluders = GetOccludersF1D()(inter)
|
||||
return any(a.id == self._id for a in occluders)
|
||||
|
||||
|
||||
class pyIsOccludedByItselfUP1D(UnaryPredicate1D):
|
||||
def __init__(self):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self.__func1 = GetOccludersF1D()
|
||||
self.__func2 = GetShapeF1D()
|
||||
|
||||
def __call__(self, inter):
|
||||
lst1 = self.__func1(inter)
|
||||
lst2 = self.__func2(inter)
|
||||
return any(vs1.id == vs2.id for vs1 in lst1 for vs2 in lst2)
|
||||
|
||||
|
||||
class pyIsOccludedByIdListUP1D(UnaryPredicate1D):
|
||||
def __init__(self, idlist):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._idlist = idlist
|
||||
self.__func1 = GetOccludersF1D()
|
||||
|
||||
def __call__(self, inter):
|
||||
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._funcs = tuple(ShapeUP1D(_id, 0) for _id in idlist)
|
||||
|
||||
def __call__(self, inter):
|
||||
return any(func(inter) for func in self._funcs)
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
class pyShapeIdUP1D(UnaryPredicate1D):
|
||||
def __init__(self, _id):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._id = _id
|
||||
|
||||
def __call__(self, inter):
|
||||
shapes = GetShapeF1D()(inter)
|
||||
return any(a.id == self._id for a in shapes)
|
||||
|
||||
|
||||
class pyHighDensityAnisotropyUP1D(UnaryPredicate1D):
|
||||
def __init__(self, threshold, level, sampling=2.0):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._l = threshold
|
||||
self.func = pyDensityAnisotropyF1D(level, IntegrationType.MEAN, sampling)
|
||||
|
||||
def __call__(self, inter):
|
||||
return (self.func(inter) > self._l)
|
||||
|
||||
|
||||
class pyHighViewMapGradientNormUP1D(UnaryPredicate1D):
|
||||
def __init__(self, threshold, l, sampling=2.0):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._threshold = threshold
|
||||
self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN)
|
||||
|
||||
def __call__(self, inter):
|
||||
gn = self._GetGradient(inter)
|
||||
return (gn > self._threshold)
|
||||
|
||||
|
||||
class pyDensityVariableSigmaUP1D(UnaryPredicate1D):
|
||||
def __init__(self, functor, sigmaMin, sigmaMax, lmin, lmax, tmin,
|
||||
tmax, integration=IntegrationType.MEAN, sampling=2.0):
|
||||
UnaryPredicate1D.__init__(self)
|
||||
self._functor = functor
|
||||
self._sigmaMin = float(sigmaMin)
|
||||
self._sigmaMax = float(sigmaMax)
|
||||
self._lmin = float(lmin)
|
||||
self._lmax = float(lmax)
|
||||
self._tmin = tmin
|
||||
self._tmax = tmax
|
||||
self._integration = integration
|
||||
self._sampling = sampling
|
||||
|
||||
def __call__(self, inter):
|
||||
result = self._functor(inter) - self._lmin
|
||||
sigma = (self._sigmaMax - self._sigmaMin) / (self._lmax - self._lmin) * result + self._sigmaMin
|
||||
t = (self._tmax - self._tmin) / (self._lmax - self._lmin) * result + self._tmin
|
||||
sigma = max(sigma, self._sigmaMin)
|
||||
self._func = DensityF1D(sigma, self._integration, self._sampling)
|
||||
return (self._func(inter) < t)
|
||||
|
||||
|
||||
class pyClosedCurveUP1D(UnaryPredicate1D):
|
||||
def __call__(self, inter):
|
||||
it = inter.vertices_begin()
|
||||
itlast = inter.vertices_end()
|
||||
itlast.decrement()
|
||||
return (next(it).id == next(itlast).id)
|
||||
|
||||
|
||||
# -- Binary predicates for 1D elements (curves) -- #
|
||||
|
||||
class AndBP1D(BinaryPredicate1D):
|
||||
def __init__(self, *predicates):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self.predicates = tuple(predicates)
|
||||
correct_types = all(isinstance(p, BinaryPredicate1D) for p in self.predicates)
|
||||
if not (correct_types and predicates):
|
||||
raise TypeError("%s: Expected one or more BinaryPredicate1D, got %r" %
|
||||
(self.__class__.__name__, self.predicates))
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return all(pred(i1, i2) for pred in self.predicates)
|
||||
|
||||
|
||||
class OrBP1D(BinaryPredicate1D):
|
||||
def __init__(self, *predicates):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self.predicates = tuple(predicates)
|
||||
correct_types = all(isinstance(p, BinaryPredicate1D) for p in self.predicates)
|
||||
if not (correct_types and predicates):
|
||||
raise TypeError("%s: Expected one or more BinaryPredicate1D, got %r" %
|
||||
(self.__class__.__name__, self.predicates))
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return any(pred(i1, i2) for pred in self.predicates)
|
||||
|
||||
|
||||
class NotBP1D(BinaryPredicate1D):
|
||||
def __init__(self, predicate):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self.predicate = predicate
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return (not self.predicate(i1, i2))
|
||||
|
||||
|
||||
class pyZBP1D(BinaryPredicate1D):
|
||||
def __init__(self, iType=IntegrationType.MEAN):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self.func = GetZF1D(iType)
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return (self.func(i1) > self.func(i2))
|
||||
|
||||
|
||||
class pyProjectedXBP1D(BinaryPredicate1D):
|
||||
def __init__(self, iType=IntegrationType.MEAN):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self.func = GetProjectedXF1D(iType)
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return (self.func(i1) > self.func(i2))
|
||||
|
||||
|
||||
class pyProjectedYBP1D(BinaryPredicate1D):
|
||||
def __init__(self, iType=IntegrationType.MEAN):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self.func = GetProjectedYF1D(iType)
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return (self.func(i1) > self.func(i2))
|
||||
|
||||
|
||||
class pyZDiscontinuityBP1D(BinaryPredicate1D):
|
||||
def __init__(self, iType=IntegrationType.MEAN):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self._GetZDiscontinuity = ZDiscontinuityF1D(iType)
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return (self._GetZDiscontinuity(i1) > self._GetZDiscontinuity(i2))
|
||||
|
||||
|
||||
class pyLengthBP1D(BinaryPredicate1D):
|
||||
def __call__(self, i1, i2):
|
||||
return (i1.length_2d > i2.length_2d)
|
||||
|
||||
|
||||
class pySilhouetteFirstBP1D(BinaryPredicate1D):
|
||||
def __call__(self, inter1, inter2):
|
||||
bpred = SameShapeIdBP1D()
|
||||
if (not bpred(inter1, inter2)):
|
||||
return False
|
||||
if (inter1.nature & Nature.SILHOUETTE):
|
||||
return bool(inter2.nature & Nature.SILHOUETTE)
|
||||
return (inter1.nature == inter2.nature)
|
||||
|
||||
|
||||
class pyNatureBP1D(BinaryPredicate1D):
|
||||
def __call__(self, inter1, inter2):
|
||||
return (inter1.nature & inter2.nature)
|
||||
|
||||
|
||||
class pyViewMapGradientNormBP1D(BinaryPredicate1D):
|
||||
def __init__(self, l, sampling=2.0):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN)
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
return (self._GetGradient(i1) > self._GetGradient(i2))
|
||||
|
||||
|
||||
class pyShuffleBP1D(BinaryPredicate1D):
|
||||
def __init__(self):
|
||||
BinaryPredicate1D.__init__(self)
|
||||
random.seed = 1
|
||||
|
||||
def __call__(self, inter1, inter2):
|
||||
return (random.uniform(0, 1) < random.uniform(0, 1))
|
||||
|
||||
|
||||
class MaterialBP1D(BinaryPredicate1D):
|
||||
"""Checks whether the two supplied ViewEdges have the same material."""
|
||||
|
||||
def __call__(self, i1, i2):
|
||||
fedges = (fe for ve in (i1, i2) for fe in (ve.first_fedge, ve.last_fedge))
|
||||
materials = {material_from_fedge(fe) for fe in fedges}
|
||||
return len(materials) < 2
|
||||
1412
blender-5.2.0/scripts/freestyle/modules/freestyle/shaders.py
Normal file
1412
blender-5.2.0/scripts/freestyle/modules/freestyle/shaders.py
Normal file
File diff suppressed because it is too large
Load Diff
159
blender-5.2.0/scripts/freestyle/modules/freestyle/types.py
Normal file
159
blender-5.2.0/scripts/freestyle/modules/freestyle/types.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# SPDX-FileCopyrightText: 2014-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains core classes of the Freestyle Python API,
|
||||
including data types of view map components (0D and 1D elements), base
|
||||
classes for user-defined line stylization rules (predicates,
|
||||
functions, chaining iterators, and stroke shaders), and operators.
|
||||
|
||||
Class hierarchy:
|
||||
|
||||
- :class:`BBox`
|
||||
- :class:`BinaryPredicate0D`
|
||||
- :class:`BinaryPredicate1D`
|
||||
- :class:`Id`
|
||||
- :class:`Interface0D`
|
||||
|
||||
- :class:`CurvePoint`
|
||||
|
||||
- :class:`StrokeVertex`
|
||||
|
||||
- :class:`SVertex`
|
||||
- :class:`ViewVertex`
|
||||
|
||||
- :class:`NonTVertex`
|
||||
- :class:`TVertex`
|
||||
|
||||
- :class:`Interface1D`
|
||||
|
||||
- :class:`Curve`
|
||||
|
||||
- :class:`Chain`
|
||||
|
||||
- :class:`FEdge`
|
||||
|
||||
- :class:`FEdgeSharp`
|
||||
- :class:`FEdgeSmooth`
|
||||
|
||||
- :class:`Stroke`
|
||||
- :class:`ViewEdge`
|
||||
|
||||
- :class:`Iterator`
|
||||
|
||||
- :class:`AdjacencyIterator`
|
||||
- :class:`CurvePointIterator`
|
||||
- :class:`Interface0DIterator`
|
||||
- :class:`SVertexIterator`
|
||||
- :class:`StrokeVertexIterator`
|
||||
- :class:`ViewEdgeIterator`
|
||||
|
||||
- :class:`ChainingIterator`
|
||||
|
||||
- :class:`orientedViewEdgeIterator`
|
||||
|
||||
- :class:`Material`
|
||||
- :class:`Noise`
|
||||
- :class:`Operators`
|
||||
- :class:`SShape`
|
||||
- :class:`StrokeAttribute`
|
||||
- :class:`StrokeShader`
|
||||
- :class:`UnaryFunction0D`
|
||||
|
||||
- :class:`UnaryFunction0DDouble`
|
||||
- :class:`UnaryFunction0DEdgeNature`
|
||||
- :class:`UnaryFunction0DFloat`
|
||||
- :class:`UnaryFunction0DId`
|
||||
- :class:`UnaryFunction0DMaterial`
|
||||
- :class:`UnaryFunction0DUnsigned`
|
||||
- :class:`UnaryFunction0DVec2f`
|
||||
- :class:`UnaryFunction0DVec3f`
|
||||
- :class:`UnaryFunction0DVectorViewShape`
|
||||
- :class:`UnaryFunction0DViewShape`
|
||||
|
||||
- :class:`UnaryFunction1D`
|
||||
|
||||
- :class:`UnaryFunction1DDouble`
|
||||
- :class:`UnaryFunction1DEdgeNature`
|
||||
- :class:`UnaryFunction1DFloat`
|
||||
- :class:`UnaryFunction1DUnsigned`
|
||||
- :class:`UnaryFunction1DVec2f`
|
||||
- :class:`UnaryFunction1DVec3f`
|
||||
- :class:`UnaryFunction1DVectorViewShape`
|
||||
- :class:`UnaryFunction1DVoid`
|
||||
|
||||
- :class:`UnaryPredicate0D`
|
||||
- :class:`UnaryPredicate1D`
|
||||
- :class:`ViewMap`
|
||||
- :class:`ViewShape`
|
||||
- :class:`IntegrationType`
|
||||
- :class:`MediumType`
|
||||
- :class:`Nature`
|
||||
"""
|
||||
|
||||
|
||||
# module members
|
||||
from _freestyle import (
|
||||
AdjacencyIterator,
|
||||
BBox,
|
||||
BinaryPredicate0D,
|
||||
BinaryPredicate1D,
|
||||
Chain,
|
||||
ChainingIterator,
|
||||
Curve,
|
||||
CurvePoint,
|
||||
CurvePointIterator,
|
||||
FEdge,
|
||||
FEdgeSharp,
|
||||
FEdgeSmooth,
|
||||
Id,
|
||||
IntegrationType,
|
||||
Interface0D,
|
||||
Interface0DIterator,
|
||||
Interface1D,
|
||||
Iterator,
|
||||
Material,
|
||||
MediumType,
|
||||
Nature,
|
||||
Noise,
|
||||
NonTVertex,
|
||||
Operators,
|
||||
SShape,
|
||||
SVertex,
|
||||
SVertexIterator,
|
||||
Stroke,
|
||||
StrokeAttribute,
|
||||
StrokeShader,
|
||||
StrokeVertex,
|
||||
StrokeVertexIterator,
|
||||
TVertex,
|
||||
UnaryFunction0D,
|
||||
UnaryFunction0DDouble,
|
||||
UnaryFunction0DEdgeNature,
|
||||
UnaryFunction0DFloat,
|
||||
UnaryFunction0DId,
|
||||
UnaryFunction0DMaterial,
|
||||
UnaryFunction0DUnsigned,
|
||||
UnaryFunction0DVec2f,
|
||||
UnaryFunction0DVec3f,
|
||||
UnaryFunction0DVectorViewShape,
|
||||
UnaryFunction0DViewShape,
|
||||
UnaryFunction1D,
|
||||
UnaryFunction1DDouble,
|
||||
UnaryFunction1DEdgeNature,
|
||||
UnaryFunction1DFloat,
|
||||
UnaryFunction1DUnsigned,
|
||||
UnaryFunction1DVec2f,
|
||||
UnaryFunction1DVec3f,
|
||||
UnaryFunction1DVectorViewShape,
|
||||
UnaryFunction1DVoid,
|
||||
UnaryPredicate0D,
|
||||
UnaryPredicate1D,
|
||||
ViewEdge,
|
||||
ViewEdgeIterator,
|
||||
ViewMap,
|
||||
ViewShape,
|
||||
ViewVertex,
|
||||
orientedViewEdgeIterator,
|
||||
)
|
||||
682
blender-5.2.0/scripts/freestyle/modules/freestyle/utils.py
Normal file
682
blender-5.2.0/scripts/freestyle/modules/freestyle/utils.py
Normal file
@@ -0,0 +1,682 @@
|
||||
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains helper functions used for Freestyle style module
|
||||
writing.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"angle_x_normal",
|
||||
"bound",
|
||||
"bounding_box",
|
||||
"BoundingBox",
|
||||
"ContextFunctions",
|
||||
"curvature_from_stroke_vertex",
|
||||
"find_matching_vertex",
|
||||
"get_chain_length",
|
||||
"get_object_name",
|
||||
"get_strokes",
|
||||
"get_test_stroke",
|
||||
"getCurrentScene",
|
||||
"integrate",
|
||||
"is_poly_clockwise",
|
||||
"iter_distance_along_stroke",
|
||||
"iter_distance_from_camera",
|
||||
"iter_distance_from_object",
|
||||
"iter_material_value",
|
||||
"iter_t2d_along_stroke",
|
||||
"material_from_fedge",
|
||||
"normal_at_I0D",
|
||||
"pairwise",
|
||||
"phase_to_direction",
|
||||
"rgb_to_bw",
|
||||
"simplify",
|
||||
"stroke_curvature",
|
||||
"stroke_normal",
|
||||
"StrokeCollector",
|
||||
"tripplewise",
|
||||
)
|
||||
|
||||
# module members
|
||||
from _freestyle import (
|
||||
ContextFunctions,
|
||||
getCurrentScene,
|
||||
integrate,
|
||||
)
|
||||
|
||||
# constructs for helper functions in Python
|
||||
from freestyle.types import (
|
||||
Interface0DIterator,
|
||||
Stroke,
|
||||
StrokeShader,
|
||||
StrokeVertexIterator,
|
||||
Operators,
|
||||
)
|
||||
|
||||
from mathutils import Vector
|
||||
from functools import lru_cache, namedtuple
|
||||
from math import cos, sin, pi, atan2
|
||||
from itertools import tee, compress
|
||||
|
||||
|
||||
# -- types -- #
|
||||
|
||||
# A named tuple primitive used for storing data that has an upper and
|
||||
# lower bound (e.g., thickness, range and certain other values)
|
||||
class BoundedProperty(namedtuple("BoundedProperty", ["min", "max", "delta"])):
|
||||
def __new__(cls, minimum, maximum, delta=None):
|
||||
if delta is None:
|
||||
delta = abs(maximum - minimum)
|
||||
return super().__new__(cls, minimum, maximum, delta)
|
||||
|
||||
def interpolate(self, val):
|
||||
result = (self.max - val) / self.delta
|
||||
return 1.0 - bound(0, result, 1)
|
||||
|
||||
|
||||
# -- real utility functions -- #
|
||||
|
||||
def rgb_to_bw(r, g, b):
|
||||
"""Method to convert rgb to a bw intensity value.
|
||||
|
||||
:param r: Red channel (0..1).
|
||||
:type r: float
|
||||
:param g: Green channel (0..1).
|
||||
:type g: float
|
||||
:param b: Blue channel (0..1).
|
||||
:type b: float
|
||||
:rtype: float
|
||||
"""
|
||||
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)
|
||||
|
||||
:param lower: Lower bound.
|
||||
:type lower: float
|
||||
:param x: Value to clamp.
|
||||
:type x: float
|
||||
:param higher: Upper bound.
|
||||
:type higher: float
|
||||
:rtype: float
|
||||
"""
|
||||
# this is about 50% quicker than min(max(x, lower), higher)
|
||||
return (lower if x <= lower else higher if x >= higher else x)
|
||||
|
||||
|
||||
def get_strokes():
|
||||
"""Get all strokes that are currently available"""
|
||||
return tuple(map(Operators.get_stroke_from_index, range(Operators.get_strokes_size())))
|
||||
|
||||
|
||||
def is_poly_clockwise(stroke):
|
||||
"""True if the stroke is orientated in a clockwise way, False otherwise
|
||||
|
||||
:param stroke: A stroke whose orientation is tested.
|
||||
:type stroke: :class:`Stroke`
|
||||
:rtype: bool
|
||||
"""
|
||||
v = sum((v2.point.x - v1.point.x) * (v1.point.y + v2.point.y) for v1, v2 in pairwise(stroke))
|
||||
v1, v2 = stroke[0], stroke[-1]
|
||||
if (v1.point - v2.point).length > 1e-3:
|
||||
v += (v2.point.x - v1.point.x) * (v1.point.y + v2.point.y)
|
||||
return v > 0
|
||||
|
||||
|
||||
def get_object_name(stroke):
|
||||
"""Returns the name of the object that this stroke is drawn on.
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
:rtype: str | None
|
||||
"""
|
||||
fedge = stroke[0].fedge
|
||||
if fedge is None:
|
||||
return None
|
||||
return fedge.viewedge.viewshape.name
|
||||
|
||||
|
||||
def material_from_fedge(fe):
|
||||
"""Get the diffuse RGBA color from an FEdge.
|
||||
|
||||
:param fe: An FEdge.
|
||||
:type fe: :class:`FEdge`
|
||||
:rtype: :class:`Material` | None
|
||||
"""
|
||||
if fe is None:
|
||||
return None
|
||||
if fe.is_smooth:
|
||||
material = fe.material
|
||||
else:
|
||||
right, left = fe.material_right, fe.material_left
|
||||
material = right if (right.priority > left.priority) else left
|
||||
return material
|
||||
|
||||
|
||||
def bounding_box(stroke):
|
||||
"""
|
||||
Returns the maximum and minimum coordinates (the bounding box) of the stroke's vertices
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
:rtype: tuple[:class:`mathutils.Vector`, :class:`mathutils.Vector`]
|
||||
"""
|
||||
x, y = zip(*(svert.point for svert in stroke))
|
||||
return (Vector((min(x), min(y))), Vector((max(x), max(y))))
|
||||
|
||||
|
||||
def normal_at_I0D(it: Interface0DIterator) -> Vector:
|
||||
"""Normal at an Interface0D object. In contrast to Normal2DF0D this
|
||||
function uses the actual data instead of underlying Fedge objects.
|
||||
|
||||
:param it: An iterator over Interface0D objects.
|
||||
:type it: :class:`Interface0DIterator`
|
||||
:rtype: :class:`mathutils.Vector`
|
||||
"""
|
||||
if it.at_last and it.is_begin:
|
||||
# corner-case
|
||||
return Vector((0, 0))
|
||||
elif it.at_last:
|
||||
it.decrement()
|
||||
a, b = it.object, next(it)
|
||||
elif it.is_begin:
|
||||
a, b = it.object, next(it)
|
||||
# give iterator back in original state
|
||||
it.decrement()
|
||||
elif it.is_end:
|
||||
# Just fail hard: this should not happen.
|
||||
raise StopIteration()
|
||||
else:
|
||||
# this case sometimes has a small difference with Normal2DF0D (1e-3 -ish)
|
||||
it.decrement()
|
||||
a = it.object
|
||||
_curr, b = next(it), next(it)
|
||||
# give iterator back in original state
|
||||
it.decrement()
|
||||
return (b.point - a.point).orthogonal().normalized()
|
||||
|
||||
|
||||
def angle_x_normal(it: Interface0DIterator):
|
||||
"""unsigned angle between a Point's normal and the X axis, in radians
|
||||
|
||||
:param it: An iterator over Interface0D objects.
|
||||
:type it: :class:`Interface0DIterator`
|
||||
:rtype: float
|
||||
"""
|
||||
normal = normal_at_I0D(it)
|
||||
return abs(atan2(normal[1], normal[0]))
|
||||
|
||||
|
||||
def curvature_from_stroke_vertex(svert):
|
||||
"""The 3D curvature of an stroke vertex' underlying geometry
|
||||
The result is None or in the range [-inf, inf]
|
||||
|
||||
:param svert: A stroke vertex.
|
||||
:type svert: :class:`StrokeVertex`
|
||||
:rtype: float | None
|
||||
"""
|
||||
c1 = svert.first_svertex.curvatures
|
||||
c2 = svert.second_svertex.curvatures
|
||||
if c1 is None and c2 is None:
|
||||
Kr = None
|
||||
elif c1 is None:
|
||||
Kr = c2[4]
|
||||
elif c2 is None:
|
||||
Kr = c1[4]
|
||||
else:
|
||||
Kr = c1[4] + svert.t2d * (c2[4] - c1[4])
|
||||
return Kr
|
||||
|
||||
|
||||
# -- 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
|
||||
|
||||
|
||||
# Simplification of a set of points; based on `simplify.js`:
|
||||
# See: https://mourner.github.io/simplify-js
|
||||
|
||||
def getSquareSegmentDistance(p, p1, p2):
|
||||
"""
|
||||
Square distance between point and a segment
|
||||
"""
|
||||
x, y = p1
|
||||
|
||||
dx, dy = (p2 - p1)
|
||||
|
||||
if dx or dy:
|
||||
t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
|
||||
|
||||
if t > 1:
|
||||
x, y = p2
|
||||
elif t > 0:
|
||||
x += dx * t
|
||||
y += dy * t
|
||||
|
||||
dx, dy = p.x - x, p.y - y
|
||||
return dx * dx + dy * dy
|
||||
|
||||
|
||||
def simplifyDouglasPeucker(points, tolerance):
|
||||
length = len(points)
|
||||
markers = [0] * length
|
||||
|
||||
first = 0
|
||||
last = length - 1
|
||||
|
||||
first_stack = []
|
||||
last_stack = []
|
||||
|
||||
markers[first] = 1
|
||||
markers[last] = 1
|
||||
|
||||
while last:
|
||||
max_sqdist = 0
|
||||
|
||||
for i in range(first, last):
|
||||
sqdist = getSquareSegmentDistance(points[i], points[first], points[last])
|
||||
|
||||
if sqdist > max_sqdist:
|
||||
index = i
|
||||
max_sqdist = sqdist
|
||||
|
||||
if max_sqdist > tolerance:
|
||||
markers[index] = 1
|
||||
|
||||
first_stack.append(first)
|
||||
last_stack.append(index)
|
||||
|
||||
first_stack.append(index)
|
||||
last_stack.append(last)
|
||||
|
||||
first = first_stack.pop() if first_stack else None
|
||||
last = last_stack.pop() if last_stack else None
|
||||
|
||||
return tuple(compress(points, markers))
|
||||
|
||||
|
||||
def simplify(points, tolerance):
|
||||
"""Simplifies a set of points.
|
||||
|
||||
:param points: Points to simplify.
|
||||
:type points: Sequence[:class:`mathutils.Vector`]
|
||||
:param tolerance: Maximum allowed deviation from the original curve.
|
||||
:type tolerance: float
|
||||
:rtype: tuple
|
||||
"""
|
||||
return simplifyDouglasPeucker(points, tolerance * tolerance)
|
||||
|
||||
|
||||
class BoundingBox:
|
||||
"""Object representing a bounding box consisting out of 2 2D vectors"""
|
||||
|
||||
__slots__ = (
|
||||
"minimum",
|
||||
"maximum",
|
||||
"size",
|
||||
"corners",
|
||||
)
|
||||
|
||||
def __init__(self, minimum: Vector, maximum: Vector):
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
if len(minimum) != len(maximum):
|
||||
raise TypeError("Expected two vectors of size 2, got", minimum, maximum)
|
||||
self.size = len(minimum)
|
||||
self.corners = (minimum, maximum)
|
||||
|
||||
def __repr__(self):
|
||||
return "BoundingBox({!r}, {!r})".format(self.minimum, self.maximum)
|
||||
|
||||
@classmethod
|
||||
def from_sequence(cls, sequence):
|
||||
"""BoundingBox from sequence of 2D or 3D Vector objects.
|
||||
|
||||
:param sequence: An iterable of vectors to compute the box from.
|
||||
:type sequence: Iterable[:class:`mathutils.Vector`]
|
||||
:rtype: :class:`BoundingBox`
|
||||
"""
|
||||
x, y = zip(*sequence)
|
||||
mini = Vector((min(x), min(y)))
|
||||
maxi = Vector((max(x), max(y)))
|
||||
return cls(mini, maxi)
|
||||
|
||||
def inside(self, other):
|
||||
"""True if self inside other, False otherwise.
|
||||
|
||||
:param other: Another bounding box to test containment against.
|
||||
:type other: :class:`BoundingBox`
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.size != other.size:
|
||||
raise TypeError("Expected two BoundingBox of the same size, got", self, other)
|
||||
return (self.minimum.x >= other.minimum.x and self.minimum.y >= other.minimum.y and
|
||||
self.maximum.x <= other.maximum.x and self.maximum.y <= other.maximum.y)
|
||||
|
||||
|
||||
class StrokeCollector(StrokeShader):
|
||||
"""Collects and Stores stroke objects"""
|
||||
|
||||
def __init__(self):
|
||||
StrokeShader.__init__(self)
|
||||
self.strokes = []
|
||||
|
||||
def shade(self, stroke):
|
||||
"""
|
||||
:param stroke: The stroke to collect.
|
||||
:type stroke: :class:`Stroke`
|
||||
"""
|
||||
self.strokes.append(stroke)
|
||||
|
||||
|
||||
# -- helper functions for chaining -- #
|
||||
|
||||
def get_chain_length(ve, orientation):
|
||||
"""Returns the 2d length of a given ViewEdge.
|
||||
|
||||
:param ve: The ViewEdge whose chain length to compute.
|
||||
:type ve: :class:`ViewEdge`
|
||||
:param orientation: Direction in which to traverse the chain.
|
||||
:type orientation: bool
|
||||
:rtype: float
|
||||
"""
|
||||
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 vertex, or returns None.
|
||||
|
||||
:param id: The ID to match.
|
||||
:type id: :class:`Id`
|
||||
:param it: An iterator over candidate ViewEdges.
|
||||
:type it: :class:`AdjacencyIterator`
|
||||
:rtype: :class:`ViewEdge` | None
|
||||
"""
|
||||
return next((ve for ve in it if ve.id == id), None)
|
||||
|
||||
|
||||
# -- helper functions for iterating -- #
|
||||
|
||||
def pairwise(iterable, types=None):
|
||||
"""Yields a tuple containing the previous and current object.
|
||||
|
||||
:param iterable: An iterable of items.
|
||||
:type iterable: Iterable[Any]
|
||||
:param types: Container types for which the iterator's ``incremented()``
|
||||
method is used instead of standard tee-based pairing. When ``None``
|
||||
defaults to ``(Stroke, StrokeVertexIterator)``.
|
||||
:type types: tuple[type, ...] | None
|
||||
"""
|
||||
# use .incremented() for types that support it
|
||||
if types is None:
|
||||
types = (Stroke, StrokeVertexIterator)
|
||||
if type(iterable) in types:
|
||||
it = iter(iterable)
|
||||
return zip(it, it.incremented())
|
||||
else:
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
def tripplewise(iterable):
|
||||
"""Yields a tuple containing the current object and its immediate neighbors.
|
||||
|
||||
:param iterable: An iterable of items.
|
||||
:type iterable: Iterable[Any]
|
||||
"""
|
||||
a, b, c = tee(iterable)
|
||||
next(b, None)
|
||||
next(c, None)
|
||||
return zip(a, b, c)
|
||||
|
||||
|
||||
def iter_t2d_along_stroke(stroke):
|
||||
"""Yields the progress along the stroke.
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
"""
|
||||
total = stroke.length_2d
|
||||
distance = 0.0
|
||||
# yield for the comparison from the first vertex to itself
|
||||
yield 0.0
|
||||
for prev, svert in pairwise(stroke):
|
||||
distance += (prev.point - svert.point).length
|
||||
yield min(distance / total, 1.0) if total != 0.0 else 0.0
|
||||
|
||||
|
||||
def iter_distance_from_camera(stroke, range_min, range_max, normfac):
|
||||
"""
|
||||
Yields the distance to the camera relative to the maximum
|
||||
possible distance for every stroke vertex, constrained by
|
||||
given minimum and maximum values.
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
:param range_min: Distances below this value are clamped to 0.
|
||||
:type range_min: float
|
||||
:param range_max: Distances above this value are clamped to 1.
|
||||
:type range_max: float
|
||||
:param normfac: Normalization factor applied to ``distance - range_min``.
|
||||
:type normfac: float
|
||||
"""
|
||||
for svert in stroke:
|
||||
# length in the camera coordinate
|
||||
distance = svert.point_3d.length
|
||||
if range_min < distance < range_max:
|
||||
yield (svert, (distance - range_min) / normfac)
|
||||
else:
|
||||
yield (svert, 0.0) if range_min > distance else (svert, 1.0)
|
||||
|
||||
|
||||
def iter_distance_from_object(stroke, location, range_min, range_max, normfac):
|
||||
"""
|
||||
yields the distance to the given object relative to the maximum
|
||||
possible distance for every stroke vertex, constrained by
|
||||
given minimum and maximum values.
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
:param location: Reference location in 3D space.
|
||||
:type location: :class:`mathutils.Vector`
|
||||
:param range_min: Distances below this value are clamped to 0.
|
||||
:type range_min: float
|
||||
:param range_max: Distances above this value are clamped to 1.
|
||||
:type range_max: float
|
||||
:param normfac: Normalization factor applied to ``distance - range_min``.
|
||||
:type normfac: float
|
||||
"""
|
||||
for svert in stroke:
|
||||
distance = (svert.point_3d - location).length # in the camera coordinate
|
||||
if range_min < distance < range_max:
|
||||
yield (svert, (distance - range_min) / normfac)
|
||||
else:
|
||||
yield (svert, 0.0) if distance < range_min else (svert, 1.0)
|
||||
|
||||
|
||||
def iter_material_value(stroke, func, attribute):
|
||||
"""Yields a specific material attribute from the vertex' underlying material.
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
:param func: A function returning a material for the iterator's current vertex.
|
||||
:type func: Callable[[:class:`Interface0DIterator`], :class:`Material`]
|
||||
:param attribute: The material attribute name (e.g. ``LINE``, ``DIFF``, ``ALPHA``).
|
||||
:type attribute: str
|
||||
"""
|
||||
it = Interface0DIterator(stroke)
|
||||
for svert in it:
|
||||
material = func(it)
|
||||
# main
|
||||
if attribute == 'LINE':
|
||||
value = rgb_to_bw(*material.line[0:3])
|
||||
elif attribute == 'DIFF':
|
||||
value = rgb_to_bw(*material.diffuse[0:3])
|
||||
elif attribute == 'SPEC':
|
||||
value = rgb_to_bw(*material.specular[0:3])
|
||||
# line separate
|
||||
elif attribute == 'LINE_R':
|
||||
value = material.line[0]
|
||||
elif attribute == 'LINE_G':
|
||||
value = material.line[1]
|
||||
elif attribute == 'LINE_B':
|
||||
value = material.line[2]
|
||||
elif attribute == 'LINE_A':
|
||||
value = material.line[3]
|
||||
# diffuse separate
|
||||
elif attribute == 'DIFF_R':
|
||||
value = material.diffuse[0]
|
||||
elif attribute == 'DIFF_G':
|
||||
value = material.diffuse[1]
|
||||
elif attribute == 'DIFF_B':
|
||||
value = material.diffuse[2]
|
||||
elif attribute == 'ALPHA':
|
||||
value = material.diffuse[3]
|
||||
# specular separate
|
||||
elif attribute == 'SPEC_R':
|
||||
value = material.specular[0]
|
||||
elif attribute == 'SPEC_G':
|
||||
value = material.specular[1]
|
||||
elif attribute == 'SPEC_B':
|
||||
value = material.specular[2]
|
||||
elif attribute == 'SPEC_HARDNESS':
|
||||
value = material.shininess
|
||||
else:
|
||||
raise ValueError("unexpected material attribute: " + attribute)
|
||||
yield (svert, value)
|
||||
|
||||
|
||||
def iter_distance_along_stroke(stroke):
|
||||
"""Yields the absolute distance along the stroke up to the current vertex.
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
"""
|
||||
distance = 0.0
|
||||
# the positions need to be copied, because they are changed in the calling function
|
||||
points = tuple(svert.point.copy() for svert in stroke)
|
||||
yield distance
|
||||
for prev, curr in pairwise(points):
|
||||
distance += (prev - curr).length
|
||||
yield distance
|
||||
|
||||
|
||||
# -- mathematical 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
|
||||
|
||||
:param it: An iterator over a stroke's vertices.
|
||||
:type it: :class:`StrokeVertexIterator`
|
||||
"""
|
||||
for _ in it:
|
||||
if (it.is_begin or it.is_end):
|
||||
yield 0.0
|
||||
continue
|
||||
else:
|
||||
it.decrement()
|
||||
prev, current, succ = it.object.point.copy(), next(it).point.copy(), next(it).point.copy()
|
||||
# return the iterator in an unchanged state
|
||||
it.decrement()
|
||||
|
||||
ab = (current - prev)
|
||||
bc = (succ - current)
|
||||
ac = (prev - succ)
|
||||
|
||||
a, b, c = ab.length, bc.length, ac.length
|
||||
|
||||
try:
|
||||
area = 0.5 * ab.cross(ac)
|
||||
K = (4 * area) / (a * b * c)
|
||||
except ZeroDivisionError:
|
||||
K = 0.0
|
||||
|
||||
yield abs(K)
|
||||
|
||||
|
||||
def stroke_normal(stroke):
|
||||
"""
|
||||
Compute the 2D normal at the stroke vertex pointed by the iterator
|
||||
'it'. It is noted that Normal2DF0D computes normals based on
|
||||
underlying FEdges instead, which is inappropriate for strokes when
|
||||
they have already been modified by stroke geometry modifiers.
|
||||
|
||||
The returned normals are dynamic: they update when the
|
||||
vertex position (and therefore the vertex normal) changes.
|
||||
for use in geometry modifiers it is advised to
|
||||
cast this generator function to a tuple or list
|
||||
|
||||
:param stroke: A stroke.
|
||||
:type stroke: :class:`Stroke`
|
||||
"""
|
||||
it = iter(stroke)
|
||||
yield from (normal_at_I0D(it) for _ in it)
|
||||
|
||||
|
||||
def get_test_stroke():
|
||||
"""Returns a static stroke object for testing """
|
||||
from freestyle.types import Stroke, Interface0DIterator, StrokeVertexIterator, SVertex, Id, StrokeVertex
|
||||
# points for our fake stroke
|
||||
points = (Vector((1.0, 5.0, 3.0)), Vector((1.0, 2.0, 9.0)),
|
||||
Vector((6.0, 2.0, 3.0)), Vector((7.0, 2.0, 3.0)),
|
||||
Vector((2.0, 6.0, 3.0)), Vector((2.0, 8.0, 3.0)))
|
||||
ids = (Id(0, 0), Id(1, 1), Id(2, 2), Id(3, 3), Id(4, 4), Id(5, 5))
|
||||
|
||||
stroke = Stroke()
|
||||
it = iter(stroke)
|
||||
|
||||
for svert in map(SVertex, points, ids):
|
||||
stroke.insert_vertex(StrokeVertex(svert), it)
|
||||
it = iter(stroke)
|
||||
|
||||
stroke.update_length()
|
||||
return stroke
|
||||
1593
blender-5.2.0/scripts/freestyle/modules/parameter_editor.py
Normal file
1593
blender-5.2.0/scripts/freestyle/modules/parameter_editor.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user