Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'release/scripts/freestyle')
-rw-r--r--release/scripts/freestyle/modules/freestyle/functions.py6
-rw-r--r--release/scripts/freestyle/modules/freestyle/predicates.py53
-rw-r--r--release/scripts/freestyle/modules/freestyle/shaders.py8
-rw-r--r--release/scripts/freestyle/modules/freestyle/utils.py110
-rw-r--r--release/scripts/freestyle/modules/parameter_editor.py15
5 files changed, 147 insertions, 45 deletions
diff --git a/release/scripts/freestyle/modules/freestyle/functions.py b/release/scripts/freestyle/modules/freestyle/functions.py
index 48d9b2e2b39..426d344e8ab 100644
--- a/release/scripts/freestyle/modules/freestyle/functions.py
+++ b/release/scripts/freestyle/modules/freestyle/functions.py
@@ -189,11 +189,13 @@ class CurveMaterialF0D(UnaryFunction0DMaterial):
priority is used to pick one of the two materials at material
boundaries.
- Note: expects instances of CurvePoint to be iterated over
+ 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
- assert(fe is not None), "CurveMaterialF0D: fe is None"
+ if fe is None:
+ return None
if fe.is_smooth:
return fe.material
else:
diff --git a/release/scripts/freestyle/modules/freestyle/predicates.py b/release/scripts/freestyle/modules/freestyle/predicates.py
index 54656e450d9..5cbe577b956 100644
--- a/release/scripts/freestyle/modules/freestyle/predicates.py
+++ b/release/scripts/freestyle/modules/freestyle/predicates.py
@@ -43,6 +43,7 @@ __all__ = (
"FalseUP0D",
"FalseUP1D",
"Length2DBP1D",
+ "MaterialBP1D",
"NotBP1D",
"NotUP1D",
"ObjectNamesUP1D",
@@ -150,12 +151,13 @@ from freestyle.functions import (
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)
@@ -234,9 +236,10 @@ class AndUP1D(UnaryPredicate1D):
def __init__(self, *predicates):
UnaryPredicate1D.__init__(self)
self.predicates = predicates
- # there are cases in which only one predicate is supplied (in the parameter editor)
- if len(self.predicates) < 1:
- raise ValueError("Expected one or more UnaryPredicate1D, got ", len(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)
@@ -246,9 +249,10 @@ class OrUP1D(UnaryPredicate1D):
def __init__(self, *predicates):
UnaryPredicate1D.__init__(self)
self.predicates = predicates
- # there are cases in which only one predicate is supplied (in the parameter editor)
- if len(self.predicates) < 1:
- raise ValueError("Expected one or more UnaryPredicate1D, got ", len(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)
@@ -257,10 +261,10 @@ class OrUP1D(UnaryPredicate1D):
class NotUP1D(UnaryPredicate1D):
def __init__(self, pred):
UnaryPredicate1D.__init__(self)
- self.__pred = pred
+ self.predicate = pred
def __call__(self, inter):
- return not self.__pred(inter)
+ return not self.predicate(inter)
class ObjectNamesUP1D(UnaryPredicate1D):
@@ -563,32 +567,36 @@ class pyClosedCurveUP1D(UnaryPredicate1D):
class AndBP1D(BinaryPredicate1D):
def __init__(self, *predicates):
BinaryPredicate1D.__init__(self)
- self._predicates = predicates
- if len(self.predicates) < 2:
- raise ValueError("Expected two or more BinaryPredicate1D, got ", len(predictates))
+ 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)
+ return all(pred(i1, i2) for pred in self.predicates)
class OrBP1D(BinaryPredicate1D):
def __init__(self, *predicates):
BinaryPredicate1D.__init__(self)
- self._predicates = predicates
- if len(self.predicates) < 2:
- raise ValueError("Expected two or more BinaryPredicate1D, got ", len(predictates))
+ 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)
+ return any(pred(i1, i2) for pred in self.predicates)
class NotBP1D(BinaryPredicate1D):
def __init__(self, predicate):
BinaryPredicate1D.__init__(self)
- self._predicate = predicate
+ self.predicate = predicate
def __call__(self, i1, i2):
- return (not self._predicate(i1, i2))
+ return (not self.predicate(i1, i2))
class pyZBP1D(BinaryPredicate1D):
@@ -663,3 +671,10 @@ class pyShuffleBP1D(BinaryPredicate1D):
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
diff --git a/release/scripts/freestyle/modules/freestyle/shaders.py b/release/scripts/freestyle/modules/freestyle/shaders.py
index 61365e8dd87..127db3fcd4b 100644
--- a/release/scripts/freestyle/modules/freestyle/shaders.py
+++ b/release/scripts/freestyle/modules/freestyle/shaders.py
@@ -138,7 +138,7 @@ from freestyle.predicates import (
from freestyle.utils import (
bound,
- bounding_box,
+ BoundingBox,
phase_to_direction,
)
@@ -865,7 +865,7 @@ class pyBluePrintCirclesShader(StrokeShader):
def shade(self, stroke):
# get minimum and maximum coordinates
- p_min, p_max = bounding_box(stroke)
+ p_min, p_max = BoundingBox.from_sequence(svert.point for svert in stroke).corners
stroke.resample(32 * self.__turns)
sv_nb = len(stroke) // self.__turns
@@ -917,7 +917,7 @@ class pyBluePrintEllipsesShader(StrokeShader):
self.__random_radius = random_radius
def shade(self, stroke):
- p_min, p_max = bounding_box(stroke)
+ p_min, p_max = BoundingBox.from_sequence(svert.point for svert in stroke).corners
stroke.resample(32 * self.__turns)
sv_nb = len(stroke) // self.__turns
@@ -964,7 +964,7 @@ class pyBluePrintSquaresShader(StrokeShader):
return
# get minimum and maximum coordinates
- p_min, p_max = bounding_box(stroke)
+ p_min, p_max = BoundingBox.from_sequence(svert.point for svert in stroke).corners
stroke.resample(32 * self.__turns)
num_segments = len(stroke) // self.__turns
diff --git a/release/scripts/freestyle/modules/freestyle/utils.py b/release/scripts/freestyle/modules/freestyle/utils.py
index 224734d5bfb..41d2297f723 100644
--- a/release/scripts/freestyle/modules/freestyle/utils.py
+++ b/release/scripts/freestyle/modules/freestyle/utils.py
@@ -22,24 +22,29 @@ writing.
"""
__all__ = (
- "ContextFunctions",
"bound",
- "bounding_box",
+ "BoundingBox",
+ "ContextFunctions",
"find_matching_vertex",
- "getCurrentScene",
"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",
"pairwise",
"phase_to_direction",
"rgb_to_bw",
"stroke_curvature",
"stroke_normal",
+ "StrokeCollector",
"tripplewise",
)
@@ -55,6 +60,7 @@ from _freestyle import (
from freestyle.types import (
Interface0DIterator,
Stroke,
+ StrokeShader,
StrokeVertexIterator,
)
@@ -79,12 +85,38 @@ def bound(lower, x, higher):
return (lower if x <= lower else higher if x >= higher else x)
-def bounding_box(stroke):
- """
- Returns the maximum and minimum coordinates (the bounding box) of the stroke's vertices
- """
- x, y = zip(*(svert.point for svert in stroke))
- return (Vector((min(x), min(y))), Vector((max(x), max(y))))
+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"""
+ 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."""
+ 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"
+ 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
# -- General helper functions -- #
@@ -106,6 +138,54 @@ def phase_to_direction(length):
# lower bound (e.g., thickness, range and certain values)
BoundedProperty = namedtuple("BoundedProperty", ["min", "max", "delta"])
+
+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)" % (self.minimum, self.maximum)
+
+ @classmethod
+ def from_sequence(cls, sequence):
+ """BoundingBox from sequence of 2D or 3D Vector objects"""
+ 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"""
+ 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):
+ self.strokes.append(stroke)
+
+
# -- helper functions for chaining -- #
def get_chain_length(ve, orientation):
@@ -147,6 +227,7 @@ def find_matching_vertex(id, it):
"""Finds the matching vertex, or returns None."""
return next((ve for ve in it if ve.id == id), None)
+
# -- helper functions for iterating -- #
def pairwise(iterable, types={Stroke, StrokeVertexIterator}):
@@ -210,7 +291,7 @@ def iter_distance_from_object(stroke, location, range_min, range_max, normfac):
def iter_material_value(stroke, func, attribute):
- "Yields a specific material attribute from the vertex' underlying material."
+ """Yields a specific material attribute from the vertex' underlying material."""
it = Interface0DIterator(stroke)
for svert in it:
material = func(it)
@@ -252,8 +333,9 @@ def iter_material_value(stroke, func, attribute):
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."
+ """Yields the absolute distance along the stroke up to the current vertex."""
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)
@@ -295,6 +377,7 @@ def stroke_curvature(it):
yield abs(K)
+
def stroke_normal(stroke):
"""
Compute the 2D normal at the stroke vertex pointed by the iterator
@@ -304,7 +387,7 @@ def stroke_normal(stroke):
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
+ for use in geometry modifiers it is advised to
cast this generator function to a tuple or list
"""
n = len(stroke) - 1
@@ -323,12 +406,13 @@ def stroke_normal(stroke):
n2 = Vector((e2[1], -e2[0])).normalized()
yield (n1 + n2).normalized()
+
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((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))
diff --git a/release/scripts/freestyle/modules/parameter_editor.py b/release/scripts/freestyle/modules/parameter_editor.py
index d4765847450..3c11c33a39d 100644
--- a/release/scripts/freestyle/modules/parameter_editor.py
+++ b/release/scripts/freestyle/modules/parameter_editor.py
@@ -960,11 +960,11 @@ def process(layer_name, lineset_name):
if lineset.select_external_contour:
upred = ExternalContourUP1D()
edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_external_contour else upred)
- if lineset.edge_type_combination == 'OR':
- upred = OrUP1D(*edge_type_criteria)
- else:
- upred = AndUP1D(*edge_type_criteria)
- if upred is not None:
+ if edge_type_criteria:
+ if lineset.edge_type_combination == 'OR':
+ upred = OrUP1D(*edge_type_criteria)
+ else:
+ upred = AndUP1D(*edge_type_criteria)
if lineset.edge_type_negation == 'EXCLUSIVE':
upred = NotUP1D(upred)
selection_criteria.append(upred)
@@ -989,8 +989,9 @@ def process(layer_name, lineset_name):
upred = WithinImageBoundaryUP1D(*ContextFunctions.get_border())
selection_criteria.append(upred)
# select feature edges
- upred = AndUP1D(*selection_criteria)
- if upred is None:
+ if selection_criteria:
+ upred = AndUP1D(*selection_criteria)
+ else:
upred = TrueUP1D()
Operators.select(upred)
# join feature edges to form chains