[Bf-blender-cvs] [eeeb845] master: Freestyle: new stroke modifiers

Folkert de Vries noreply at git.blender.org
Fri Jul 10 16:16:34 CEST 2015


Commit: eeeb845d33e81afbc8ed127e6ab4ae7b18472a54
Author: Folkert de Vries
Date:   Fri Jul 10 21:57:23 2015 +0900
Branches: master
https://developer.blender.org/rBeeeb845d33e81afbc8ed127e6ab4ae7b18472a54

Freestyle: new stroke modifiers

This patch introduces a couple new stroke modifiers. The ones currently implemented are based on prototypes by @kjym3 and myself.

The new modifiers:
  - Tangent
  - Thickness noise
  - Crease Angle
  - Simplification
  - Curvature 3D

The documentation for these new modifier types can be found [[ http://www.blender.org/manual/render/freestyle/parameter_editor/index.html | in the manual ]]:

{F134441}
(left: AnisotropicThicknessShader, right: NoiseThicknessShader)

{F140499}
(left: Curvature 3D, right: Simplification)

Author: Folkert de Vries (flokkievids)

Reviewers: kjym3

Subscribers: #user_interface, plasmasolutions, kjym3

Projects: #bf_blender

Differential Revision: https://developer.blender.org/D963

===================================================================

M	release/scripts/freestyle/modules/freestyle/utils.py
M	release/scripts/freestyle/modules/parameter_editor.py
M	release/scripts/startup/bl_ui/properties_freestyle.py
M	source/blender/blenkernel/intern/linestyle.c
M	source/blender/blenloader/intern/readfile.c
M	source/blender/blenloader/intern/writefile.c
M	source/blender/freestyle/intern/python/BPy_FrsNoise.cpp
M	source/blender/freestyle/intern/python/BPy_FrsNoise.h
M	source/blender/freestyle/intern/stroke/BasicStrokeShaders.cpp
M	source/blender/makesdna/DNA_linestyle_types.h
M	source/blender/makesrna/RNA_access.h
M	source/blender/makesrna/intern/rna_linestyle.c

===================================================================

diff --git a/release/scripts/freestyle/modules/freestyle/utils.py b/release/scripts/freestyle/modules/freestyle/utils.py
index 41d2297..c664268 100644
--- a/release/scripts/freestyle/modules/freestyle/utils.py
+++ b/release/scripts/freestyle/modules/freestyle/utils.py
@@ -39,9 +39,11 @@ __all__ = (
     "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",
@@ -66,8 +68,22 @@ from freestyle.types import (
 
 from mathutils import Vector
 from functools import lru_cache, namedtuple
-from math import cos, sin, pi
-from itertools import tee
+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  -- #
@@ -76,7 +92,6 @@ def rgb_to_bw(r, g, b):
     """Method to convert rgb to a bw intensity value."""
     return 0.35 * r + 0.45 * g + 0.2 * b
 
-
 def bound(lower, x, higher):
     """Returns x bounded by a maximum and minimum value. Equivalent to:
     return min(max(x, lower), higher)
@@ -84,7 +99,6 @@ def bound(lower, x, higher):
     # this is about 50% quicker than min(max(x, lower), higher)
     return (lower if x <= lower else higher if x >= higher else x)
 
-
 def get_strokes():
     """Get all strokes that are currently available"""
     return tuple(map(Operators().get_stroke_from_index, range(Operators().get_strokes_size())))
@@ -118,8 +132,60 @@ def material_from_fedge(fe):
         material = right if (right.priority > left.priority) else left
     return material
 
-# -- General helper functions -- #
+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 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. 
+    """
+    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 shouldn 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"""
+    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]"""
+    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):
@@ -134,9 +200,74 @@ def phase_to_direction(length):
         results.append((phase, Vector((cos(2 * pi * phase), sin(2 * pi * phase)))))
     return results
 
-# A named tuple primitive used for storing data that has an upper and
-# lower bound (e.g., thickness, range and certain values)
-BoundedProperty = namedtuple("BoundedProperty", ["min", "max", "delta"])
+
+
+# -- simplification of a set of points; based on simplify.js by Vladimir Agafonkin -- 
+#    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 = []
+
+    new_points = []
+
+    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"""
+    return simplifyDouglasPeucker(points, tolerance * tolerance)
 
 
 class BoundingBox:
@@ -346,7 +477,6 @@ def iter_distance_along_stroke(stroke):
 
 # -- mathematical operations -- #
 
-
 def stroke_curvature(it):
     """
     Compute the 2D curvature at the stroke vertex pointed by the iterator 'it'.
@@ -390,21 +520,23 @@ def stroke_normal(stroke):
     for use in geometry modifiers it is advised to
     cast this generator function to a tuple or list
     """
-    n = len(stroke) - 1
-
-    for i, svert in enumerate(stroke):
-        if i == 0:
-            e = stroke[i + 1].point - svert.point
-            yield Vector((e[1], -e[0])).normalized()
-        elif i == n:
-            e = svert.point - stroke[i - 1].point
-            yield Vector((e[1], -e[0])).normalized()
-        else:
-            e1 = stroke[i + 1].point - svert.point
-            e2 = svert.point - stroke[i - 1].point
-            n1 = Vector((e1[1], -e1[0])).normalized()
-            n2 = Vector((e2[1], -e2[0])).normalized()
-            yield (n1 + n2).normalized()
+    # n = len(stroke) - 1
+    it = iter(stroke)
+    yield from (normal_at_I0D(it) for _ in it)
+
+    #for i, svert in enumerate(stroke):
+    #    if i == 0:
+    #        e = stroke[i + 1].point - svert.point
+    #        yield Vector((e[1], -e[0])).normalized()
+    #    elif i == n:
+    #        e = svert.point - stroke[i - 1].point
+    #        yield Vector((e[1], -e[0])).normalized()
+    #    else:
+    #        e1 = stroke[i + 1].point - svert.point
+    #        e2 = svert.point - stroke[i - 1].point
+    #        n1 = Vector((e1[1], -e1[0])).normalized()
+    #        n2 = Vector((e2[1], -e2[0])).normalized()
+    #        yield (n1 + n2).normalized()
 
 
 def get_test_stroke():
diff --git a/release/scripts/freestyle/modules/parameter_editor.py b/release/scripts/freestyle/modules/parameter_editor.py
index 3c11c33..a1d0528 100644
--- a/release/scripts/freestyle/modules/parameter_editor.py
+++ b/release/scripts/freestyle/modules/parameter_editor.py
@@ -75,29 +75,34 @@ from freestyle.shaders import (
     ConstantColorShader,
     GuidingLinesShader,
     PolygonalizationShader,
-    SamplingShader,
-    SpatialNoiseShader,
-    StrokeShader,
-    StrokeTextureStepShader,
-    TipRemoverShader,
     pyBluePrintCirclesShader,
     pyBluePrintEllipsesShader,
     pyBluePrintSquaresShader,
     RoundCapShader,
+    SamplingShader,
+    SpatialNoiseShader,
     SquareCapShader,
+    StrokeShader,
+    StrokeTextureStepShader,
+    ThicknessNoiseShader as thickness_noise,
+    TipRemoverShader,
     )
 from freestyle.utils import (
+    angle_x_normal,
+    bound,
+    BoundedProperty,
     ContextFunctions,
+    curvature_from_stroke_vertex,
     getCurrentScene,
     iter_distance_along_stroke,
-    iter_t2d_along_stroke,
     iter_distance_from_camera,
     iter_distance_from_object,
     iter_material_value,
-    stroke_normal,
-    bound,
+    iter_t2d_along_stroke,
+    normal_at_I0D,
     pairwise,
-    BoundedProperty,
+    simplify,
+    stroke_normal,
     )
 from _freestyle import (
     blendRamp,
@@ -106,12 +111,16 @@ from _freestyle import (
     )
 
 import time
+import bpy
+import random
+
 from mathutils import Vector
-from math import pi, sin, cos, acos, radians
+from math import pi, sin, cos, acos, radians, atan2
 from itertools import cycle, tee
 
-# lists of callback functions
 # WARNING: highly experimental, not a stable API
+# lists of callback functions
+# used by the render_freestyle_svg addon
 callbacks_lineset_pre = []
 callbacks_modifiers_post = []
 callbacks_lineset_post = []
@@ -176,7 +185,13 @@ class CurveMappingModifier(ScalarBlendModifier):
         return (1.0 - t) if self.invert else t
 
     def CURVE(self, t):
-        return evaluateCurveMappingF(self.curve, 0, t)
+        # deprecated: return evaluateCurveMappingF(self.curve, 0, t)
+        curve = self.curve
+        curve.initialize()
+        result = curve.curves[0].evaluate(t)
+        # float precision errors in t can give a very weird result for evaluate.
+        # therefore, bound the result by the curve's min 

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list