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 ( QuantitativeInvisibilityUP1D, FalseUP1D, TrueUP1D ) from freestyle.shaders import ( BezierCurveShader, pyDecreasingThicknessShader, IncreasingColorShader ) from freestyle.types import Operators from freestyle.types import ( AdjacencyIterator, ChainingIterator ) 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): pass 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.select(QuantitativeInvisibilityUP1D(0)) Operators.chain(singleLineIterator(), FalseUP1D()) # Operators.bidirectional_chain(singleLineIterator(), FalseUP1D()) shaders_list = [ BezierCurveShader(3), pyDecreasingThicknessShader(1,40), 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:
So, how do I fix this? I think this means either:
- actually convincing
chainto give me previously-visited edges, or
bidirectional_chainto be less bidirectional, which seems less elegant but maybe more doable.
restrict_to_selection is also not working as I expected when using
restricted_to_unvisited=true, then both
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.