Commit: 3ca0870023bb71bc929925a8fc8d172c09df710f Author: Tamito Kajiyama Date: Sun May 31 17:46:58 2015 +0900 Branches: master https://developer.blender.org/rB3ca0870023bb71bc929925a8fc8d172c09df710f
Improvements to the Freestyle Python API (needed by the SVG Exporter) This patch adds some new functionality to the Freestyle Python API, notably: - MaterialBP1D, checks whether the supplied arguments have the same material - Fixes a potential crash in CurvePoint.fedge (due to NULL pointer) - Makes (error handling in) boolean predicates more robust - Adds a BoundingBox type, to make working with bounding boxes easier - Adds several new functions (get_object_name, get_strokes, is_poly_clockwise, material_from_fedge) - Adds a StrokeCollector StrokeShader, that collects all the strokes from a specific call to Operators.create() - Adds hashing and rich comparison to the FrsMaterial type These new features (most of them, anyway) are needed for making a more robust SVG exporter that supports holes in fills. Reviewers: kjym3, campbellbarton Subscribers: campbellbarton Projects: #bf_blender Differential Revision: https://developer.blender.org/D1245 =================================================================== M release/scripts/freestyle/modules/freestyle/functions.py M release/scripts/freestyle/modules/freestyle/predicates.py M release/scripts/freestyle/modules/freestyle/shaders.py M release/scripts/freestyle/modules/freestyle/utils.py M source/blender/freestyle/intern/python/BPy_FrsMaterial.cpp M source/blender/freestyle/intern/python/Interface0D/BPy_CurvePoint.cpp =================================================================== diff --git a/release/scripts/freestyle/modules/freestyle/functions.py b/release/scripts/freestyle/modules/freestyle/functions.py index 48d9b2e..426d344 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 2439cb0..5cbe577 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(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(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 61365e8..127db3f 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 224734d..41d2297 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.priorit @@ Diff output truncated at 10240 characters. @@ _______________________________________________ Bf-blender-cvs mailing list Bf-blender-cvs@blender.org http://lists.blender.org/mailman/listinfo/bf-blender-cvs