Blenderyard

October 17, 2014

Contour lines as in maps

Filed under: Blender, Freestyle — blenderyard @ 4:40 PM

slices_suzanne_tn

This is a quick proof-of-concept render of contour lines (as in geographical maps) drawn by Freestyle in Blender.  The contour lines are genuine edges in mesh data annotated by Freestyle edge marks.  These edges were auto-generated by a script based on the Intersect mesh-editing command (new in 2.72) to identify contour lines, i.e., cross-section lines with a plane at different heights at a regular interval.

# slices.py

import bpy
import math
from mathutils import Vector

def exec_slices(context, interval):
    print('interval', interval)

    ob = context.active_object
    ob.update_from_editmode()
    print('ob.name', ob.name)

    rad = max(Vector(point).length for point in ob.bound_box)
    print('rad', rad)

    zmin = min(point[2] for point in ob.bound_box)
    zmax = max(point[2] for point in ob.bound_box)
    print('zmin', zmin, 'zmax', zmax)

    imin = int(math.ceil(zmin / interval))
    imax = int(math.floor(zmax / interval))
    zvals = [i * interval for i in range(imin, imax+1)]
    print('zvals', zvals)

    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.object.vertex_group_add()

    rot = Vector((0.0, 0.0, 0.0))
    for z in zvals:
        loc = Vector((0.0, 0.0, z)) + ob.location
        do_slice(rad, loc, rot)

    bpy.ops.object.vertex_group_remove(all=False)
    bpy.ops.object.mode_set(mode='OBJECT')

def do_slice(rad, loc, rot):
    bpy.ops.mesh.primitive_plane_add(radius=rad, location=loc, rotation=rot)

    bpy.ops.object.vertex_group_assign()

    bpy.ops.mesh.intersect()
    bpy.ops.mesh.remove_doubles()
    bpy.ops.mesh.mark_freestyle_edge()
    bpy.ops.mesh.select_all(action='DESELECT')

    bpy.ops.object.vertex_group_select()

    bpy.ops.mesh.delete(type='VERT')

class Slices(bpy.types.Operator):
    """Slices"""
    bl_idname = "object.slices"
    bl_label = "Slices"
    bl_options = {'REGISTER', 'UNDO'}

    interval = bpy.props.FloatProperty(name="Slice Interval", default=1.0, min=0.01, max=100)

    @classmethod
    def poll(cls, context):
        ob = context.active_object
        return ob is not None and ob.type == 'MESH'

    def execute(self, context):
        exec_slices(context, self.interval)
        return {'FINISHED'}

def register():
    bpy.utils.register_class(Slices)

def unregister():
    bpy.utils.unregister_class(Slices)

if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.slices(interval=0.1)

October 9, 2014

Under the hood: Stroke animation with Freestyle for Blender

Filed under: Blender, Freestyle — blenderyard @ 3:40 PM

During the Christmas and new year holidays at the end of 2010, I was experimenting an idea to realize animated strokes using Freestyle for Blender.  The idea was to create an animation movie clip in which a set of stylized lines is gradually drawn as time goes.  After a few attempts of coding in Python using the Freestyle Python API, I got a style module that achieved the desired visual effects.  I made several video clips to demonstrate the new stroke drawing module by the end of new year holidays.  I was quite satisfied with the results.

After a while, I came to know that I had deleted the stroke drawing module file by accident.  Relevant .blend files and output movie clips were salvaged, but the core of the coding work was permanently lost.  Since then it was a long-standing to-do item for me to re-implement the stroke drawing module from scratch.

Recently I got a bit of spare time (for the first time since a long time, after the completion of my intensive freelance work on Freestyle and Blender NPR development supported by the Blender Development Fund).  So I gave another try to stroke animation in Freestyle.  Here is the result (click the image below to jump to the finished product posted on Vimeo):

vimeo107822555

Technically speaking, the stroke animation is implemented as follows.  First a set of stylized strokes is created as usual using Freestyle, namely either through the interactive Parameter Editor mode or in the coder-oriented Python Scripting mode.  The basic strategy for animated strokes then is to make only a fraction of the entire stylized strokes visible, where the fraction is set to the ratio of the number of rendered frames (i.e., the current frame number) to the total number of frames.  To make the visual effects as general as possible, I have implemented the stroke drawing effect as a post-processing procedure as follows:

# stroke_anim.py

from freestyle.types import Operators
from freestyle.utils import getCurrentScene

def postprocess(frame_start=None, frame_end=None, interval=0):
    """
    frame_start: the start frame (default Scene.frame_start)
    frame_end: the end frame (default: Scene.frame_end)
    interval: the number of frames inserted as interval between two strokes
    """

    totlen = 0.0
    nstrokes = Operators.get_strokes_size()
    #print('#strokes', nstrokes)
    for i in range(nstrokes):
        stroke = Operators.get_stroke_from_index(i)
        totlen += stroke.length_2d
    #print('totlen', totlen)

    scene = getCurrentScene()
    sta = scene.frame_start if frame_start is None else frame_start
    end = scene.frame_end if frame_end is None else frame_end
    cur = scene.frame_current
    fac = (cur - sta) / (end - sta)
    #print('fac', fac)

    totDrawingFrames = (end - sta + 1) - interval * (nstrokes - 1)
    if totDrawingFrames < 0:
        raise RuntimeError('The number of frames is too small')

    lengthPerFrame = totlen / totDrawingFrames
    #print('lengthPerFrame', lengthPerFrame)

    thresh = (cur - sta + 1) * lengthPerFrame + 1e-6
    #print('thresh', thresh)

    curlen = 0.0
    for i in range(nstrokes):
        stroke = Operators.get_stroke_from_index(i)
        for svert in stroke:
            svert.attribute.visible = curlen + svert.curvilinear_abscissa < thresh
        curlen += stroke.length_2d + lengthPerFrame * interval
    #print('done')

In the case of Suzanne the monkey drawn by thick brush strokes, the following style module was used (that is, a slightly modified version of japanese_bigbrush.py).

# brush_anim.py

from freestyle.types import *
from freestyle.predicates import *
from freestyle.functions import *
from freestyle.chainingiterators import *
from freestyle.shaders import *

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))

Operators.select(QuantitativeInvisibilityUP1D(0))
Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(QuantitativeInvisibilityUP1D(0)))
## Splits strokes at points of highest 2D curavture
## when there are too many abrupt turns in it
func = pyInverseCurvature2DAngleF0D()
Operators.recursive_split(func, pyParameterUP0D(0.2, 0.8), NotUP1D(pyHigherNumberOfTurnsUP1D(3, 0.5)), 2)
## Keeps only long enough strokes
Operators.select(pyHigherLengthUP1D(100))
## Sorts strokes by the maximum projected Y coordinate value
Operators.sort(pyProjectedYBP1D(IntegrationType.MAX))
shaders_list = [
    pySamplingShader(10),
    BezierCurveShader(30),
    pyNonLinearVaryingThicknessShader(2, 16, 0.6),
    ConstantColorShader(0, 0, 0),
    TipRemoverShader(10),
    ]
Operators.create(TrueUP1D(), shaders_list)

import stroke_anim; stroke_anim.postprocess(interval=10)

Notice the last line in the stylization script where the post-processing procedure is invoked.  By animation rendering from 1 to some frame count (400 in the case of brush-stroked Suzanne), a set of stylized lines created by brush_anim.py will be transformed into a sequence of animation frames where the strokes are “drawn” over time.  The interval parameter specifies a pause between two strokes in units of frames.  Stroke animation can also be done in the Parameter Editor mode of Freestyle by manually modifying script/freestyle/module/parameter_editor.py in your Blender installation as indicated above in the code example.

I believe the idea of having post-processing effects after the creation of Freestyle strokes can be generalized in the future in such a way that artists can specify either user-defined Python procedures or predefined Parameter Editor options to further retouch auto-generated stylized lines.  Some coding effort is required for now, however, to reproduce the documented stroke animation effects.

Blog at WordPress.com.