I'm trying to write a chaining iterator for Freestyle to produce a single, fully-joined chain; mathematically, I believe this requires me to trace each line twice.

I have written a simple chaining iterator, inheriting from ChainingIterator, with a traverse function which keeps track of visited edges and allows each to be visited exactly twice. The problem is that when I use it with the built-in chain operator, I'm not seeing edges that have been visited at all. According to the docs, I should be able to ask for all edges, including the visited ones, by setting restrict_to_unvisited=False in my iterator; indeed, this works fine when I use the bidirectional_chain operator instead of chain but uh, this also has the effect of resulting in a bidirectional chain (with the result that I'll end up with some segments visited thrice and some not at all, which is not what I'm looking for aesthetically for this project).

I have the following code:

from freestyle.predicates import (
from freestyle.shaders import (
from freestyle.types import Operators

from freestyle.types import (

class singleLineIterator(ChainingIterator):
    def __init__(self):
        ChainingIterator.__init__(self, restrict_to_selection=True, restrict_to_unvisited=False, begin=None, orientation=True)
        self.edgeDict = dict()

    def init(self):

    def traverse(self, iter):
        it = AdjacencyIterator(iter)
        bestScore = 100
        bestEdge = None
        # "ve" means "view edge" apparently!
        for ve in it:
            score = self.edgeDict.get((ve.id.first, ve.id.second), 0)
            if (score < bestScore):
                bestEdge = ve
                bestScore = score
        print(bestScore) # this should be printing numbers higher than 0!
        if bestEdge is None or bestScore >= 2:
            return None
        self.edgeDict[(bestEdge.id.first, bestEdge.id.second)] = bestScore+1
        return bestEdge


Operators.chain(singleLineIterator(), FalseUP1D())
# Operators.bidirectional_chain(singleLineIterator(), FalseUP1D())

shaders_list = [
    IncreasingColorShader(0,0,0,1, 255,0,255,1),
Operators.create(TrueUP1D(), shaders_list)

(Setup: basic cube, rotated slightly so the occluded edge isn't lining up with the front edge, for readability; Cycles renderer; Freestyle enabled, set to Python Scripting Mode, and using the above code as the only style module. Here's a .blend file with all this.)

Running the above code, the printout from line 45 never prints a number higher than 0. If I comment out line 54 and comment in line 55 (using bidirectional_chain instead of chain), I correctly see printed numbers up to and including 2.

In the linked .blend, I also have an svg_export.py with the same iterator, since it's easier to see the path continuity in an svg path. I marked the beginnings and endings of lines with a circle and a triangle, respectively: Bidirectional_chain shows a single continuous path, but not all edges visited exactly twice. chain shows no edge visited more than once

So, how do I fix this? I think this means either:

  1. actually convincing chain to give me previously-visited edges, or
  2. hacking bidirectional_chain to be less bidirectional, which seems less elegant but maybe more doable.

P.S. Interestingly, restrict_to_selection is also not working as I expected when using chain: if restricted_to_unvisited=true, then both restrict_to_selection=true and restrict_to_selection=false have results that are restricted to the selection (i.e. not showing the occluded edge). restrict_to_selection=false only works as expected if restricted_to_unvisited is also false. This doesn't bother me as much for this project, but perhaps it's a clue as to what is going wrong?

P.P.S. Is there a way to use the built-in Freestyle SVG Exporter Add-on with my own Python Style Module here? It didn't seem to work (no svg export iff Freestyle was in Python Scripting Mode); it seemed easier to grab the code from github and integrate it than to actually debug it, but I'm idly curious.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.