Freestyle & Python 

Building Freestyle-related tools in Python

About me

My work on Freestyle

  • API improvements
  • for loop constructs
  • SVG exporter

Today

introduction

starting projects

lunch

continue coding

finishing up

presenting results

end

What you'll need

  • Recent version of blender (builder.blender.org/download/)
  • good text editor
  • GIT

Today's material can be found at:

github.com/folkertdev/BlenderBrussels

Freestyle

Freestyle is an edge-based non-photo realistic render engine

image by @reigully

image by Karlis Stigis

Freestyle Python API

  • Functions
  • Predicates
  • Stroke Shaders
  • Iterators

Basic Building Blocks

The documentation can be found at

blender.org/api/blender_python_api_2_72_release/freestyle.html

Functions

class pyGetInverseProjectedZF1D(UnaryFunction1DDouble):
    def __call__(self, inter):
        func = GetProjectedZF1D()
        z = func(inter)
        return (1.0 - z)

Definition

Functions

class pyZDependingThicknessShader(StrokeShader):
    def __init__(self, min, max):
        ...
        self.func = GetProjectedZF0D()

    def shade(self, stroke):
        it = Interface0DIterator(stroke)
        # note that the function is applied to the iterator object itself
        z_indices = tuple(self.func(it) for _ in it)
        ...

Usage

Predicates

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)

Definition

Predicates

# select visible edges with the 'Contour' edge nature
Operators.select(QuantitativeInvisibilityUP1D(0))

Usage

Stroke Shaders

Freestyle's stroke shaders apply a series of manipulations to every stroke vertex

class MyShader(StrokeShader):
    def __init__(self, *args):
        #initialize all the stuff
        Strokeshader.__init__(self)

    def shade(self, stroke):
        # give every stroke vertex a red color
        for sv in stroke:
            sv.attribute.color = (1, 0, 0)

Definition

Stroke Shaders

class pyIncreasingThicknessShader(StrokeShader):
    """
    Increasingly thickens the stroke.
    """
    def __init__(self, thicknessMin, thicknessMax):
        StrokeShader.__init__(self)
        self._thicknessMin = thicknessMin
        self._thicknessMax = thicknessMax

    def shade(self, stroke):
        n = len(stroke)
        for i, svert in enumerate(stroke):
            c = i / n
            if i < (n * 0.5):
                t = (1.0 - c) * self._thicknessMin + c * self._thicknessMax
            else:
                t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin
            svert.attribute.thickness = (t / 2.0, t / 2.0)
                
            

Definition

Stroke Shaders

shaders_list = [
    ConstantThicknessShader(5.0),
    IncreasingColorShader(0.8,0,0,1,0.1,0,0,1),
    ]
Operators.create(TrueUP1D(), shaders_list)

Usage

Iterators

class MyShader(StrokeShader):
    def __init__(self, *args):
        #initialize all the stuff
        Strokeshader.__init__(self)

    def shade(self, stroke):
        # just assigning to attributes: use a normal for loop
        for sv in stroke:
            sv.attribute.color = (1, 0, 0)
        # when using functions, explicitly use an Interface0DIterator
        it = Interface0DIterator(stroke)
        z_indices = tuple(self.func(it) for _ in it)

Usage

  • StrokeVertexIterator: on normal iteration
  • Interface0DIterator: when using Functions

Manipulating Strokes

Stroke Objects

  • Are a collection of StrokeVertex objects
  • can be iterated over
  • are the second argument to a shade function 

StrokeVertex Objects

name type code
location mathutils.Vector (2d) StrokeVertex.point
location along the stroke float StrokeVertex.u
attribute attribute object StrokeVertex.attribute

Attribute Objects

name type example
color rgb float triplet (1.0, 0.5, 0.2)
alpha float 0.5
thickness two floats (right and
left of center)
(1, 2)
visibility bool True or False

Final building blocks

Distribution and interpolation

# from pyDecreasingThicknessShader
def shade(self, stroke):
    l = stroke.length_2d
    n = len(stroke)
    tMax = min(self._thicknessMax, 0.33 * l)
    tMin = min(self._thicknessMin, 0.10 * l)

    for i, svert in enumerate(stroke):
        # c will go from 0.0 to 1.0
        c = i / n
        # the thickness of vertex #1 = tMax, of #n = tMin
        t = (1.0 - c) * tMax + c * tMin
        svert.attribute.thickness = (t / 2.0, t / 2.0)

Vector math

#from pyBackboneStretcherShader
def shade(self, stroke):
    # get start and end points
    v0, vn = stroke[0], stroke[-1]
    p0, pn = v0.point, vn.point
    # get the direction
    d1 = (p0 - stroke[ 1].point).normalized()
    dn = (pn - stroke[-2].point).normalized()
    v0.point += d1 * self._l
    vn.point += dn * self._l
    stroke.update_length()

Freestyle Callbacks

Freestyle Callbacks

# lists of callback functions
# WARNING: highly experimental, not a stable API
callbacks_lineset_pre = []
callbacks_modifiers_post = []
callbacks_lineset_post = []

Definition

yes, it's this simple

Freestyle Callbacks

Usage

Have a look at the Freestyle SVG exporter add-on

Today's projects

  • Make a basic Stroke Shader
  • Make it work from an addon
  • Make a more advanced Stroke Shader
  • optimise/make UI for slices.py
  • Try and make pixelate.py have no external dependencies (so remove ImageMagick)
  • UI/extra features to stroke_anim.py
  • Freestyle to Grease Pencil converter
  • Freestyle to curves converter
  • curvature-dependent Stroke Shaders
  • ...

 

Project Ideas

General add-on concept:

  • make blender auto-reload changed files

Freestyle

By folkert de vries

Freestyle

  • 102