Freestyle: remove a bunch of debug prints in the python code, these are confusing
[blender.git] / release / scripts / freestyle / style_modules / parameter_editor.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 #  Filename : parameter_editor.py
20 #  Authors  : Tamito Kajiyama
21 #  Date     : 26/07/2010
22 #  Purpose  : Interactive manipulation of stylization parameters
23
24 import freestyle
25 import math
26 import mathutils
27 import time
28
29 from ChainingIterators import pySketchyChainSilhouetteIterator, pySketchyChainingIterator
30 from freestyle import BackboneStretcherShader, BezierCurveShader, BinaryPredicate1D, ChainPredicateIterator, \
31     ChainSilhouetteIterator, ConstantColorShader, ContourUP1D, Curvature2DAngleF0D, ExternalContourUP1D, \
32     FalseBP1D, FalseUP1D, GuidingLinesShader, Interface0DIterator, Nature, Noise, Normal2DF0D, Operators, \
33     PolygonalizationShader, QuantitativeInvisibilityF1D, QuantitativeInvisibilityUP1D, SamplingShader, \
34     SpatialNoiseShader, StrokeAttribute, StrokeShader, TipRemoverShader, TrueBP1D, TrueUP1D, UnaryPredicate0D, \
35     UnaryPredicate1D, VertexOrientation2DF0D, WithinImageBoundaryUP1D, ContextFunctions
36 from Functions0D import CurveMaterialF0D
37 from PredicatesU1D import pyNatureUP1D
38 from logical_operators import AndUP1D, NotUP1D, OrUP1D
39 from shaders import pyBluePrintCirclesShader, pyBluePrintEllipsesShader, pyBluePrintSquaresShader
40
41
42 class ColorRampModifier(StrokeShader):
43     def __init__(self, blend, influence, ramp):
44         StrokeShader.__init__(self)
45         self.__blend = blend
46         self.__influence = influence
47         self.__ramp = ramp
48
49     def evaluate(self, t):
50         col = freestyle.evaluateColorRamp(self.__ramp, t)
51         col = col.xyz  # omit alpha
52         return col
53
54     def blend_ramp(self, a, b):
55         return freestyle.blendRamp(self.__blend, a, self.__influence, b)
56
57
58 class ScalarBlendModifier(StrokeShader):
59     def __init__(self, blend, influence):
60         StrokeShader.__init__(self)
61         self.__blend = blend
62         self.__influence = influence
63
64     def blend(self, v1, v2):
65         fac = self.__influence
66         facm = 1.0 - fac
67         if self.__blend == 'MIX':
68             v1 = facm * v1 + fac * v2
69         elif self.__blend == 'ADD':
70             v1 += fac * v2
71         elif self.__blend == 'MULTIPLY':
72             v1 *= facm + fac * v2
73         elif self.__blend == 'SUBTRACT':
74             v1 -= fac * v2
75         elif self.__blend == 'DIVIDE':
76             if v2 != 0.0:
77                 v1 = facm * v1 + fac * v1 / v2
78         elif self.__blend == 'DIFFERENCE':
79             v1 = facm * v1 + fac * abs(v1 - v2)
80         elif self.__blend == 'MININUM':
81             tmp = fac * v2
82             if v1 > tmp:
83                 v1 = tmp
84         elif self.__blend == 'MAXIMUM':
85             tmp = fac * v2
86             if v1 < tmp:
87                 v1 = tmp
88         else:
89             raise ValueError("unknown curve blend type: " + self.__blend)
90         return v1
91
92
93 class CurveMappingModifier(ScalarBlendModifier):
94     def __init__(self, blend, influence, mapping, invert, curve):
95         ScalarBlendModifier.__init__(self, blend, influence)
96         assert mapping in {'LINEAR', 'CURVE'}
97         self.__mapping = getattr(self, mapping)
98         self.__invert = invert
99         self.__curve = curve
100
101     def LINEAR(self, t):
102         if self.__invert:
103             return 1.0 - t
104         return t
105
106     def CURVE(self, t):
107         return freestyle.evaluateCurveMappingF(self.__curve, 0, t)
108
109     def evaluate(self, t):
110         return self.__mapping(t)
111
112
113 class ThicknessModifierMixIn:
114     def __init__(self):
115         scene = freestyle.getCurrentScene()
116         self.__persp_camera = (scene.camera.data.type == 'PERSP')
117
118     def set_thickness(self, sv, outer, inner):
119         fe = sv.first_svertex.get_fedge(sv.second_svertex)
120         nature = fe.nature
121         if (nature & Nature.BORDER):
122             if self.__persp_camera:
123                 point = -sv.point_3d.copy()
124                 point.normalize()
125                 dir = point.dot(fe.normal_left)
126             else:
127                 dir = fe.normal_left.z
128             if dir < 0.0:  # the back side is visible
129                 outer, inner = inner, outer
130         elif (nature & Nature.SILHOUETTE):
131             if fe.is_smooth:  # TODO more tests needed
132                 outer, inner = inner, outer
133         else:
134             outer = inner = (outer + inner) / 2
135         sv.attribute.thickness = (outer, inner)
136
137
138 class ThicknessBlenderMixIn(ThicknessModifierMixIn):
139     def __init__(self, position, ratio):
140         ThicknessModifierMixIn.__init__(self)
141         self.__position = position
142         self.__ratio = ratio
143
144     def blend_thickness(self, outer, inner, v):
145         v = self.blend(outer + inner, v)
146         if self.__position == 'CENTER':
147             outer = v * 0.5
148             inner = v - outer
149         elif self.__position == 'INSIDE':
150             outer = 0
151             inner = v
152         elif self.__position == 'OUTSIDE':
153             outer = v
154             inner = 0
155         elif self.__position == 'RELATIVE':
156             outer = v * self.__ratio
157             inner = v - outer
158         else:
159             raise ValueError("unknown thickness position: " + self.__position)
160         return outer, inner
161
162
163 class BaseColorShader(ConstantColorShader):
164     pass
165
166
167 class BaseThicknessShader(StrokeShader, ThicknessModifierMixIn):
168     def __init__(self, thickness, position, ratio):
169         StrokeShader.__init__(self)
170         ThicknessModifierMixIn.__init__(self)
171         if position == 'CENTER':
172             self.__outer = thickness * 0.5
173             self.__inner = thickness - self.__outer
174         elif position == 'INSIDE':
175             self.__outer = 0
176             self.__inner = thickness
177         elif position == 'OUTSIDE':
178             self.__outer = thickness
179             self.__inner = 0
180         elif position == 'RELATIVE':
181             self.__outer = thickness * ratio
182             self.__inner = thickness - self.__outer
183         else:
184             raise ValueError("unknown thickness position: " + self.position)
185
186     def shade(self, stroke):
187         it = stroke.stroke_vertices_begin()
188         while not it.is_end:
189             sv = it.object
190             self.set_thickness(sv, self.__outer, self.__inner)
191             it.increment()
192
193
194 # Along Stroke modifiers
195
196 def iter_t2d_along_stroke(stroke):
197     total = stroke.length_2d
198     distance = 0.0
199     it = stroke.stroke_vertices_begin()
200     prev = it.object.point
201     while not it.is_end:
202         p = it.object.point
203         distance += (prev - p).length
204         prev = p.copy()  # need a copy because the point can be altered
205         t = min(distance / total, 1.0) if total > 0.0 else 0.0
206         yield it, t
207         it.increment()
208
209
210 class ColorAlongStrokeShader(ColorRampModifier):
211     def shade(self, stroke):
212         for it, t in iter_t2d_along_stroke(stroke):
213             sv = it.object
214             a = sv.attribute.color
215             b = self.evaluate(t)
216             sv.attribute.color = self.blend_ramp(a, b)
217
218
219 class AlphaAlongStrokeShader(CurveMappingModifier):
220     def shade(self, stroke):
221         for it, t in iter_t2d_along_stroke(stroke):
222             sv = it.object
223             a = sv.attribute.alpha
224             b = self.evaluate(t)
225             sv.attribute.alpha = self.blend(a, b)
226
227
228 class ThicknessAlongStrokeShader(ThicknessBlenderMixIn, CurveMappingModifier):
229     def __init__(self, thickness_position, thickness_ratio,
230                  blend, influence, mapping, invert, curve, value_min, value_max):
231         ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio)
232         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
233         self.__value_min = value_min
234         self.__value_max = value_max
235
236     def shade(self, stroke):
237         for it, t in iter_t2d_along_stroke(stroke):
238             sv = it.object
239             a = sv.attribute.thickness
240             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
241             c = self.blend_thickness(a[0], a[1], b)
242             self.set_thickness(sv, c[0], c[1])
243
244
245 # Distance from Camera modifiers
246
247 def iter_distance_from_camera(stroke, range_min, range_max):
248     normfac = range_max - range_min  # normalization factor
249     it = stroke.stroke_vertices_begin()
250     while not it.is_end:
251         p = it.object.point_3d  # in the camera coordinate
252         distance = p.length
253         if distance < range_min:
254             t = 0.0
255         elif distance > range_max:
256             t = 1.0
257         else:
258             t = (distance - range_min) / normfac
259         yield it, t
260         it.increment()
261
262
263 class ColorDistanceFromCameraShader(ColorRampModifier):
264     def __init__(self, blend, influence, ramp, range_min, range_max):
265         ColorRampModifier.__init__(self, blend, influence, ramp)
266         self.__range_min = range_min
267         self.__range_max = range_max
268
269     def shade(self, stroke):
270         for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max):
271             sv = it.object
272             a = sv.attribute.color
273             b = self.evaluate(t)
274             sv.attribute.color = self.blend_ramp(a, b)
275
276
277 class AlphaDistanceFromCameraShader(CurveMappingModifier):
278     def __init__(self, blend, influence, mapping, invert, curve, range_min, range_max):
279         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
280         self.__range_min = range_min
281         self.__range_max = range_max
282
283     def shade(self, stroke):
284         for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max):
285             sv = it.object
286             a = sv.attribute.alpha
287             b = self.evaluate(t)
288             sv.attribute.alpha = self.blend(a, b)
289
290
291 class ThicknessDistanceFromCameraShader(ThicknessBlenderMixIn, CurveMappingModifier):
292     def __init__(self, thickness_position, thickness_ratio,
293                  blend, influence, mapping, invert, curve, range_min, range_max, value_min, value_max):
294         ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio)
295         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
296         self.__range_min = range_min
297         self.__range_max = range_max
298         self.__value_min = value_min
299         self.__value_max = value_max
300
301     def shade(self, stroke):
302         for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max):
303             sv = it.object
304             a = sv.attribute.thickness
305             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
306             c = self.blend_thickness(a[0], a[1], b)
307             self.set_thickness(sv, c[0], c[1])
308
309
310 # Distance from Object modifiers
311
312 def iter_distance_from_object(stroke, object, range_min, range_max):
313     scene = freestyle.getCurrentScene()
314     mv = scene.camera.matrix_world.copy()  # model-view matrix
315     mv.invert()
316     loc = mv * object.location  # loc in the camera coordinate
317     normfac = range_max - range_min  # normalization factor
318     it = stroke.stroke_vertices_begin()
319     while not it.is_end:
320         p = it.object.point_3d  # in the camera coordinate
321         distance = (p - loc).length
322         if distance < range_min:
323             t = 0.0
324         elif distance > range_max:
325             t = 1.0
326         else:
327             t = (distance - range_min) / normfac
328         yield it, t
329         it.increment()
330
331
332 class ColorDistanceFromObjectShader(ColorRampModifier):
333     def __init__(self, blend, influence, ramp, target, range_min, range_max):
334         ColorRampModifier.__init__(self, blend, influence, ramp)
335         self.__target = target
336         self.__range_min = range_min
337         self.__range_max = range_max
338
339     def shade(self, stroke):
340         if self.__target is None:
341             return
342         for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max):
343             sv = it.object
344             a = sv.attribute.color
345             b = self.evaluate(t)
346             sv.attribute.color = self.blend_ramp(a, b)
347
348
349 class AlphaDistanceFromObjectShader(CurveMappingModifier):
350     def __init__(self, blend, influence, mapping, invert, curve, target, range_min, range_max):
351         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
352         self.__target = target
353         self.__range_min = range_min
354         self.__range_max = range_max
355
356     def shade(self, stroke):
357         if self.__target is None:
358             return
359         for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max):
360             sv = it.object
361             a = sv.attribute.alpha
362             b = self.evaluate(t)
363             sv.attribute.alpha = self.blend(a, b)
364
365
366 class ThicknessDistanceFromObjectShader(ThicknessBlenderMixIn, CurveMappingModifier):
367     def __init__(self, thickness_position, thickness_ratio,
368                  blend, influence, mapping, invert, curve, target, range_min, range_max, value_min, value_max):
369         ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio)
370         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
371         self.__target = target
372         self.__range_min = range_min
373         self.__range_max = range_max
374         self.__value_min = value_min
375         self.__value_max = value_max
376
377     def shade(self, stroke):
378         if self.__target is None:
379             return
380         for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max):
381             sv = it.object
382             a = sv.attribute.thickness
383             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
384             c = self.blend_thickness(a[0], a[1], b)
385             self.set_thickness(sv, c[0], c[1])
386
387
388 # Material modifiers
389
390 def iter_material_color(stroke, material_attribute):
391     func = CurveMaterialF0D()
392     it = stroke.stroke_vertices_begin()
393     while not it.is_end:
394         material = func(Interface0DIterator(it))
395         if material_attribute == 'DIFF':
396             color = material.diffuse[0:3]
397         elif material_attribute == 'SPEC':
398             color = material.specular[0:3]
399         else:
400             raise ValueError("unexpected material attribute: " + material_attribute)
401         yield it, color
402         it.increment()
403
404
405 def iter_material_value(stroke, material_attribute):
406     func = CurveMaterialF0D()
407     it = stroke.stroke_vertices_begin()
408     while not it.is_end:
409         material = func(Interface0DIterator(it))
410         if material_attribute == 'DIFF':
411             r, g, b = material.diffuse[0:3]
412             t = 0.35 * r + 0.45 * r + 0.2 * b
413         elif material_attribute == 'DIFF_R':
414             t = material.diffuse[0]
415         elif material_attribute == 'DIFF_G':
416             t = material.diffuse[1]
417         elif material_attribute == 'DIFF_B':
418             t = material.diffuse[2]
419         elif material_attribute == 'SPEC':
420             r, g, b = material.specular[0:3]
421             t = 0.35 * r + 0.45 * r + 0.2 * b
422         elif material_attribute == 'SPEC_R':
423             t = material.specular[0]
424         elif material_attribute == 'SPEC_G':
425             t = material.specular[1]
426         elif material_attribute == 'SPEC_B':
427             t = material.specular[2]
428         elif material_attribute == 'SPEC_HARDNESS':
429             t = material.shininess
430         elif material_attribute == 'ALPHA':
431             t = material.diffuse[3]
432         else:
433             raise ValueError("unexpected material attribute: " + material_attribute)
434         yield it, t
435         it.increment()
436
437
438 class ColorMaterialShader(ColorRampModifier):
439     def __init__(self, blend, influence, ramp, material_attribute, use_ramp):
440         ColorRampModifier.__init__(self, blend, influence, ramp)
441         self.__material_attribute = material_attribute
442         self.__use_ramp = use_ramp
443
444     def shade(self, stroke):
445         if self.__material_attribute in {'DIFF', 'SPEC'} and not self.__use_ramp:
446             for it, b in iter_material_color(stroke, self.__material_attribute):
447                 sv = it.object
448                 a = sv.attribute.color
449                 sv.attribute.color = self.blend_ramp(a, b)
450         else:
451             for it, t in iter_material_value(stroke, self.__material_attribute):
452                 sv = it.object
453                 a = sv.attribute.color
454                 b = self.evaluate(t)
455                 sv.attribute.color = self.blend_ramp(a, b)
456
457
458 class AlphaMaterialShader(CurveMappingModifier):
459     def __init__(self, blend, influence, mapping, invert, curve, material_attribute):
460         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
461         self.__material_attribute = material_attribute
462
463     def shade(self, stroke):
464         for it, t in iter_material_value(stroke, self.__material_attribute):
465             sv = it.object
466             a = sv.attribute.alpha
467             b = self.evaluate(t)
468             sv.attribute.alpha = self.blend(a, b)
469
470
471 class ThicknessMaterialShader(ThicknessBlenderMixIn, CurveMappingModifier):
472     def __init__(self, thickness_position, thickness_ratio,
473                  blend, influence, mapping, invert, curve, material_attribute, value_min, value_max):
474         ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio)
475         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
476         self.__material_attribute = material_attribute
477         self.__value_min = value_min
478         self.__value_max = value_max
479
480     def shade(self, stroke):
481         for it, t in iter_material_value(stroke, self.__material_attribute):
482             sv = it.object
483             a = sv.attribute.thickness
484             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
485             c = self.blend_thickness(a[0], a[1], b)
486             self.set_thickness(sv, c[0], c[1])
487
488
489 # Calligraphic thickness modifier
490
491 class CalligraphicThicknessShader(ThicknessBlenderMixIn, ScalarBlendModifier):
492     def __init__(self, thickness_position, thickness_ratio,
493                  blend, influence, orientation, thickness_min, thickness_max):
494         ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio)
495         ScalarBlendModifier.__init__(self, blend, influence)
496         self.__orientation = mathutils.Vector((math.cos(orientation), math.sin(orientation)))
497         self.__thickness_min = thickness_min
498         self.__thickness_max = thickness_max
499
500     def shade(self, stroke):
501         func = VertexOrientation2DF0D()
502         it = stroke.stroke_vertices_begin()
503         while not it.is_end:
504             dir = func(Interface0DIterator(it))
505             orthDir = mathutils.Vector((-dir.y, dir.x))
506             orthDir.normalize()
507             fac = abs(orthDir * self.__orientation)
508             sv = it.object
509             a = sv.attribute.thickness
510             b = self.__thickness_min + fac * (self.__thickness_max - self.__thickness_min)
511             b = max(b, 0.0)
512             c = self.blend_thickness(a[0], a[1], b)
513             self.set_thickness(sv, c[0], c[1])
514             it.increment()
515
516
517 # Geometry modifiers
518
519 def iter_distance_along_stroke(stroke):
520     distance = 0.0
521     it = stroke.stroke_vertices_begin()
522     prev = it.object.point
523     while not it.is_end:
524         p = it.object.point
525         distance += (prev - p).length
526         prev = p.copy()  # need a copy because the point can be altered
527         yield it, distance
528         it.increment()
529
530
531 class SinusDisplacementShader(StrokeShader):
532     def __init__(self, wavelength, amplitude, phase):
533         StrokeShader.__init__(self)
534         self._wavelength = wavelength
535         self._amplitude = amplitude
536         self._phase = phase / wavelength * 2 * math.pi
537         self._getNormal = Normal2DF0D()
538
539     def shade(self, stroke):
540         for it, distance in iter_distance_along_stroke(stroke):
541             v = it.object
542             n = self._getNormal(Interface0DIterator(it))
543             n = n * self._amplitude * math.cos(distance / self._wavelength * 2 * math.pi + self._phase)
544             v.point = v.point + n
545         stroke.update_length()
546
547
548 class PerlinNoise1DShader(StrokeShader):
549     def __init__(self, freq=10, amp=10, oct=4, angle=math.radians(45), seed=-1):
550         StrokeShader.__init__(self)
551         self.__noise = Noise(seed)
552         self.__freq = freq
553         self.__amp = amp
554         self.__oct = oct
555         self.__dir = mathutils.Vector((math.cos(angle), math.sin(angle)))
556
557     def shade(self, stroke):
558         length = stroke.length_2d
559         it = stroke.stroke_vertices_begin()
560         while not it.is_end:
561             v = it.object
562             nres = self.__noise.turbulence1(length * v.u, self.__freq, self.__amp, self.__oct)
563             v.point = v.point + nres * self.__dir
564             it.increment()
565         stroke.update_length()
566
567
568 class PerlinNoise2DShader(StrokeShader):
569     def __init__(self, freq=10, amp=10, oct=4, angle=math.radians(45), seed=-1):
570         StrokeShader.__init__(self)
571         self.__noise = Noise(seed)
572         self.__freq = freq
573         self.__amp = amp
574         self.__oct = oct
575         self.__dir = mathutils.Vector((math.cos(angle), math.sin(angle)))
576
577     def shade(self, stroke):
578         it = stroke.stroke_vertices_begin()
579         while not it.is_end:
580             v = it.object
581             vec = mathutils.Vector((v.projected_x, v.projected_y))
582             nres = self.__noise.turbulence2(vec, self.__freq, self.__amp, self.__oct)
583             v.point = v.point + nres * self.__dir
584             it.increment()
585         stroke.update_length()
586
587
588 class Offset2DShader(StrokeShader):
589     def __init__(self, start, end, x, y):
590         StrokeShader.__init__(self)
591         self.__start = start
592         self.__end = end
593         self.__xy = mathutils.Vector((x, y))
594         self.__getNormal = Normal2DF0D()
595
596     def shade(self, stroke):
597         it = stroke.stroke_vertices_begin()
598         while not it.is_end:
599             v = it.object
600             u = v.u
601             a = self.__start + u * (self.__end - self.__start)
602             n = self.__getNormal(Interface0DIterator(it))
603             n = n * a
604             v.point = v.point + n + self.__xy
605             it.increment()
606         stroke.update_length()
607
608
609 class Transform2DShader(StrokeShader):
610     def __init__(self, pivot, scale_x, scale_y, angle, pivot_u, pivot_x, pivot_y):
611         StrokeShader.__init__(self)
612         self.__pivot = pivot
613         self.__scale_x = scale_x
614         self.__scale_y = scale_y
615         self.__angle = angle
616         self.__pivot_u = pivot_u
617         self.__pivot_x = pivot_x
618         self.__pivot_y = pivot_y
619
620     def shade(self, stroke):
621         # determine the pivot of scaling and rotation operations
622         if self.__pivot == 'START':
623             it = stroke.stroke_vertices_begin()
624             pivot = it.object.point
625         elif self.__pivot == 'END':
626             it = stroke.stroke_vertices_end()
627             it.decrement()
628             pivot = it.object.point
629         elif self.__pivot == 'PARAM':
630             p = None
631             it = stroke.stroke_vertices_begin()
632             while not it.is_end:
633                 prev = p
634                 v = it.object
635                 p = v.point
636                 u = v.u
637                 if self.__pivot_u < u:
638                     break
639                 it.increment()
640             if prev is None:
641                 pivot = p
642             else:
643                 delta = u - self.__pivot_u
644                 pivot = p + delta * (prev - p)
645         elif self.__pivot == 'CENTER':
646             pivot = mathutils.Vector((0.0, 0.0))
647             n = 0
648             it = stroke.stroke_vertices_begin()
649             while not it.is_end:
650                 p = it.object.point
651                 pivot = pivot + p
652                 n += 1
653                 it.increment()
654             pivot.x = pivot.x / n
655             pivot.y = pivot.y / n
656         elif self.__pivot == 'ABSOLUTE':
657             pivot = mathutils.Vector((self.__pivot_x, self.__pivot_y))
658         # apply scaling and rotation operations
659         cos_theta = math.cos(self.__angle)
660         sin_theta = math.sin(self.__angle)
661         it = stroke.stroke_vertices_begin()
662         while not it.is_end:
663             v = it.object
664             p = v.point
665             p = p - pivot
666             x = p.x * self.__scale_x
667             y = p.y * self.__scale_y
668             p.x = x * cos_theta - y * sin_theta
669             p.y = x * sin_theta + y * cos_theta
670             v.point = p + pivot
671             it.increment()
672         stroke.update_length()
673
674
675 # Predicates and helper functions
676
677 class QuantitativeInvisibilityRangeUP1D(UnaryPredicate1D):
678     def __init__(self, qi_start, qi_end):
679         UnaryPredicate1D.__init__(self)
680         self.__getQI = QuantitativeInvisibilityF1D()
681         self.__qi_start = qi_start
682         self.__qi_end = qi_end
683
684     def __call__(self, inter):
685         qi = self.__getQI(inter)
686         return self.__qi_start <= qi <= self.__qi_end
687
688
689 def join_unary_predicates(upred_list, bpred):
690     if not upred_list:
691         return None
692     upred = upred_list[0]
693     for p in upred_list[1:]:
694         upred = bpred(upred, p)
695     return upred
696
697
698 class ObjectNamesUP1D(UnaryPredicate1D):
699     def __init__(self, names, negative):
700         UnaryPredicate1D.__init__(self)
701         self._names = names
702         self._negative = negative
703
704     def __call__(self, viewEdge):
705         found = viewEdge.viewshape.name in self._names
706         if self._negative:
707             return not found
708         return found
709
710
711 # Stroke caps
712
713 def iter_stroke_vertices(stroke):
714     it = stroke.stroke_vertices_begin()
715     prev_p = None
716     while not it.is_end:
717         sv = it.object
718         p = sv.point
719         if prev_p is None or (prev_p - p).length > 1e-6:
720             yield sv
721             prev_p = p.copy()
722         it.increment()
723
724
725 class RoundCapShader(StrokeShader):
726     def round_cap_thickness(self, x):
727         x = max(0.0, min(x, 1.0))
728         return math.sqrt(1.0 - (x ** 2.0))
729
730     def shade(self, stroke):
731         # save the location and attribute of stroke vertices
732         buffer = []
733         for sv in iter_stroke_vertices(stroke):
734             buffer.append((mathutils.Vector(sv.point), StrokeAttribute(sv.attribute)))
735         nverts = len(buffer)
736         if nverts < 2:
737             return
738         # calculate the number of additional vertices to form caps
739         R, L = stroke[0].attribute.thickness
740         caplen_beg = (R + L) / 2.0
741         nverts_beg = max(5, int(R + L))
742         R, L = stroke[-1].attribute.thickness
743         caplen_end = (R + L) / 2.0
744         nverts_end = max(5, int(R + L))
745         # adjust the total number of stroke vertices
746         stroke.resample(nverts + nverts_beg + nverts_end)
747         # restore the location and attribute of the original vertices
748         for i in range(nverts):
749             p, attr = buffer[i]
750             stroke[nverts_beg + i].point = p
751             stroke[nverts_beg + i].attribute = attr
752         # reshape the cap at the beginning of the stroke
753         q, attr = buffer[1]
754         p, attr = buffer[0]
755         d = p - q
756         d = d / d.length * caplen_beg
757         n = 1.0 / nverts_beg
758         R, L = attr.thickness
759         for i in range(nverts_beg):
760             t = (nverts_beg - i) * n
761             stroke[i].point = p + d * t
762             r = self.round_cap_thickness((nverts_beg - i + 1) * n)
763             stroke[i].attribute = attr
764             stroke[i].attribute.thickness = (R * r, L * r)
765         # reshape the cap at the end of the stroke
766         q, attr = buffer[-2]
767         p, attr = buffer[-1]
768         d = p - q
769         d = d / d.length * caplen_end
770         n = 1.0 / nverts_end
771         R, L = attr.thickness
772         for i in range(nverts_end):
773             t = (nverts_end - i) * n
774             stroke[-i - 1].point = p + d * t
775             r = self.round_cap_thickness((nverts_end - i + 1) * n)
776             stroke[-i - 1].attribute = attr
777             stroke[-i - 1].attribute.thickness = (R * r, L * r)
778         # update the curvilinear 2D length of each vertex
779         stroke.update_length()
780
781
782 class SquareCapShader(StrokeShader):
783     def shade(self, stroke):
784         # save the location and attribute of stroke vertices
785         buffer = []
786         for sv in iter_stroke_vertices(stroke):
787             buffer.append((mathutils.Vector(sv.point), StrokeAttribute(sv.attribute)))
788         nverts = len(buffer)
789         if nverts < 2:
790             return
791         # calculate the number of additional vertices to form caps
792         R, L = stroke[0].attribute.thickness
793         caplen_beg = (R + L) / 2.0
794         nverts_beg = 1
795         R, L = stroke[-1].attribute.thickness
796         caplen_end = (R + L) / 2.0
797         nverts_end = 1
798         # adjust the total number of stroke vertices
799         stroke.resample(nverts + nverts_beg + nverts_end)
800         # restore the location and attribute of the original vertices
801         for i in range(nverts):
802             p, attr = buffer[i]
803             stroke[nverts_beg + i].point = p
804             stroke[nverts_beg + i].attribute = attr
805         # reshape the cap at the beginning of the stroke
806         q, attr = buffer[1]
807         p, attr = buffer[0]
808         d = p - q
809         stroke[0].point = p + d / d.length * caplen_beg
810         stroke[0].attribute = attr
811         # reshape the cap at the end of the stroke
812         q, attr = buffer[-2]
813         p, attr = buffer[-1]
814         d = p - q
815         stroke[-1].point = p + d / d.length * caplen_beg
816         stroke[-1].attribute = attr
817         # update the curvilinear 2D length of each vertex
818         stroke.update_length()
819
820
821 # Split by dashed line pattern
822
823 class SplitPatternStartingUP0D(UnaryPredicate0D):
824     def __init__(self, controller):
825         UnaryPredicate0D.__init__(self)
826         self._controller = controller
827
828     def __call__(self, inter):
829         return self._controller.start()
830
831
832 class SplitPatternStoppingUP0D(UnaryPredicate0D):
833     def __init__(self, controller):
834         UnaryPredicate0D.__init__(self)
835         self._controller = controller
836
837     def __call__(self, inter):
838         return self._controller.stop()
839
840
841 class SplitPatternController:
842     def __init__(self, pattern, sampling):
843         self.sampling = float(sampling)
844         k = len(pattern) // 2
845         n = k * 2
846         self.start_pos = [pattern[i] + pattern[i + 1] for i in range(0, n, 2)]
847         self.stop_pos = [pattern[i] for i in range(0, n, 2)]
848         self.init()
849
850     def init(self):
851         self.start_len = 0.0
852         self.start_idx = 0
853         self.stop_len = self.sampling
854         self.stop_idx = 0
855
856     def start(self):
857         self.start_len += self.sampling
858         if abs(self.start_len - self.start_pos[self.start_idx]) < self.sampling / 2.0:
859             self.start_len = 0.0
860             self.start_idx = (self.start_idx + 1) % len(self.start_pos)
861             return True
862         return False
863
864     def stop(self):
865         if self.start_len > 0.0:
866             self.init()
867         self.stop_len += self.sampling
868         if abs(self.stop_len - self.stop_pos[self.stop_idx]) < self.sampling / 2.0:
869             self.stop_len = self.sampling
870             self.stop_idx = (self.stop_idx + 1) % len(self.stop_pos)
871             return True
872         return False
873
874
875 # Dashed line
876
877 class DashedLineShader(StrokeShader):
878     def __init__(self, pattern):
879         StrokeShader.__init__(self)
880         self._pattern = pattern
881
882     def shade(self, stroke):
883         index = 0  # pattern index
884         start = 0.0  # 2D curvilinear length
885         visible = True
886         sampling = 1.0
887         it = stroke.stroke_vertices_begin(sampling)
888         while not it.is_end:
889             pos = it.t  # curvilinear abscissa
890             # The extra 'sampling' term is added below, because the
891             # visibility attribute of the i-th vertex refers to the
892             # visibility of the stroke segment between the i-th and
893             # (i+1)-th vertices.
894             if pos - start + sampling > self._pattern[index]:
895                 start = pos
896                 index += 1
897                 if index == len(self._pattern):
898                     index = 0
899                 visible = not visible
900             it.object.attribute.visible = visible
901             it.increment()
902
903
904 # predicates for chaining
905
906 class AngleLargerThanBP1D(BinaryPredicate1D):
907     def __init__(self, angle):
908         BinaryPredicate1D.__init__(self)
909         self._angle = angle
910
911     def __call__(self, i1, i2):
912         sv1a = i1.first_fedge.first_svertex.point_2d
913         sv1b = i1.last_fedge.second_svertex.point_2d
914         sv2a = i2.first_fedge.first_svertex.point_2d
915         sv2b = i2.last_fedge.second_svertex.point_2d
916         if (sv1a - sv2a).length < 1e-6:
917             dir1 = sv1a - sv1b
918             dir2 = sv2b - sv2a
919         elif (sv1b - sv2b).length < 1e-6:
920             dir1 = sv1b - sv1a
921             dir2 = sv2a - sv2b
922         elif (sv1a - sv2b).length < 1e-6:
923             dir1 = sv1a - sv1b
924             dir2 = sv2a - sv2b
925         elif (sv1b - sv2a).length < 1e-6:
926             dir1 = sv1b - sv1a
927             dir2 = sv2b - sv2a
928         else:
929             return False
930         denom = dir1.length * dir2.length
931         if denom < 1e-6:
932             return False
933         x = (dir1 * dir2) / denom
934         return math.acos(min(max(x, -1.0), 1.0)) > self._angle
935
936
937 class AndBP1D(BinaryPredicate1D):
938     def __init__(self, pred1, pred2):
939         BinaryPredicate1D.__init__(self)
940         self.__pred1 = pred1
941         self.__pred2 = pred2
942
943     def __call__(self, i1, i2):
944         return self.__pred1(i1, i2) and self.__pred2(i1, i2)
945
946
947 # predicates for selection
948
949 class LengthThresholdUP1D(UnaryPredicate1D):
950     def __init__(self, length_min=None, length_max=None):
951         UnaryPredicate1D.__init__(self)
952         self._length_min = length_min
953         self._length_max = length_max
954
955     def __call__(self, inter):
956         length = inter.length_2d
957         if self._length_min is not None and length < self._length_min:
958             return False
959         if self._length_max is not None and length > self._length_max:
960             return False
961         return True
962
963
964 class FaceMarkBothUP1D(UnaryPredicate1D):
965     def __call__(self, inter):  # ViewEdge
966         fe = inter.first_fedge
967         while fe is not None:
968             if fe.is_smooth:
969                 if fe.face_mark:
970                     return True
971             elif (fe.nature & Nature.BORDER):
972                 if fe.face_mark_left:
973                     return True
974             else:
975                 if fe.face_mark_right and fe.face_mark_left:
976                     return True
977             fe = fe.next_fedge
978         return False
979
980
981 class FaceMarkOneUP1D(UnaryPredicate1D):
982     def __call__(self, inter):  # ViewEdge
983         fe = inter.first_fedge
984         while fe is not None:
985             if fe.is_smooth:
986                 if fe.face_mark:
987                     return True
988             elif (fe.nature & Nature.BORDER):
989                 if fe.face_mark_left:
990                     return True
991             else:
992                 if fe.face_mark_right or fe.face_mark_left:
993                     return True
994             fe = fe.next_fedge
995         return False
996
997
998 # predicates for splitting
999
1000 class MaterialBoundaryUP0D(UnaryPredicate0D):
1001     def __call__(self, it):
1002         if it.is_begin:
1003             return False
1004         it_prev = Interface0DIterator(it)
1005         it_prev.decrement()
1006         v = it.object
1007         it.increment()
1008         if it.is_end:
1009             return False
1010         fe = v.get_fedge(it_prev.object)
1011         idx1 = fe.material_index if fe.is_smooth else fe.material_index_left
1012         fe = v.get_fedge(it.object)
1013         idx2 = fe.material_index if fe.is_smooth else fe.material_index_left
1014         return idx1 != idx2
1015
1016
1017 class Curvature2DAngleThresholdUP0D(UnaryPredicate0D):
1018     def __init__(self, angle_min=None, angle_max=None):
1019         UnaryPredicate0D.__init__(self)
1020         self._angle_min = angle_min
1021         self._angle_max = angle_max
1022         self._func = Curvature2DAngleF0D()
1023
1024     def __call__(self, inter):
1025         angle = math.pi - self._func(inter)
1026         if self._angle_min is not None and angle < self._angle_min:
1027             return True
1028         if self._angle_max is not None and angle > self._angle_max:
1029             return True
1030         return False
1031
1032
1033 class Length2DThresholdUP0D(UnaryPredicate0D):
1034     def __init__(self, length_limit):
1035         UnaryPredicate0D.__init__(self)
1036         self._length_limit = length_limit
1037         self._t = 0.0
1038
1039     def __call__(self, inter):
1040         t = inter.t  # curvilinear abscissa
1041         if t < self._t:
1042             self._t = 0.0
1043             return False
1044         if t - self._t < self._length_limit:
1045             return False
1046         self._t = t
1047         return True
1048
1049
1050 # Seed for random number generation
1051
1052 class Seed:
1053     def __init__(self):
1054         self.t_max = 2 ** 15
1055         self.t = int(time.time()) % self.t_max
1056
1057     def get(self, seed):
1058         if seed < 0:
1059             self.t = (self.t + 1) % self.t_max
1060             return self.t
1061         return seed
1062
1063 _seed = Seed()
1064
1065 ### T.K. 07-Aug-2013 Temporary fix for unexpected line gaps
1066
1067
1068 def iter_three_segments(stroke):
1069     n = stroke.stroke_vertices_size()
1070     if n >= 4:
1071         it1 = stroke.stroke_vertices_begin()
1072         it2 = stroke.stroke_vertices_begin()
1073         it2.increment()
1074         it3 = stroke.stroke_vertices_begin()
1075         it3.increment()
1076         it3.increment()
1077         it4 = stroke.stroke_vertices_begin()
1078         it4.increment()
1079         it4.increment()
1080         it4.increment()
1081         while not it4.is_end:
1082             yield (it1.object, it2.object, it3.object, it4.object)
1083             it1.increment()
1084             it2.increment()
1085             it3.increment()
1086             it4.increment()
1087
1088
1089 class StrokeCleaner(StrokeShader):
1090     def shade(self, stroke):
1091         for sv1, sv2, sv3, sv4 in iter_three_segments(stroke):
1092             seg1 = sv2.point - sv1.point
1093             seg2 = sv3.point - sv2.point
1094             seg3 = sv4.point - sv3.point
1095             if seg1.dot(seg2) < 0.0 and seg2.dot(seg3) < 0.0:
1096                 #print(sv2.first_svertex.viewvertex)
1097                 #print(sv2.second_svertex.viewvertex)
1098                 #print(sv3.first_svertex.viewvertex)
1099                 #print(sv3.second_svertex.viewvertex)
1100                 p2 = mathutils.Vector(sv2.point)
1101                 p3 = mathutils.Vector(sv3.point)
1102                 sv2.point = p3
1103                 sv3.point = p2
1104
1105
1106 # main function for parameter processing
1107
1108 def process(layer_name, lineset_name):
1109     scene = freestyle.getCurrentScene()
1110     layer = scene.render.layers[layer_name]
1111     lineset = layer.freestyle_settings.linesets[lineset_name]
1112     linestyle = lineset.linestyle
1113
1114     selection_criteria = []
1115     # prepare selection criteria by visibility
1116     if lineset.select_by_visibility:
1117         if lineset.visibility == 'VISIBLE':
1118             selection_criteria.append(
1119                 QuantitativeInvisibilityUP1D(0))
1120         elif lineset.visibility == 'HIDDEN':
1121             selection_criteria.append(
1122                 NotUP1D(QuantitativeInvisibilityUP1D(0)))
1123         elif lineset.visibility == 'RANGE':
1124             selection_criteria.append(
1125                 QuantitativeInvisibilityRangeUP1D(lineset.qi_start, lineset.qi_end))
1126     # prepare selection criteria by edge types
1127     if lineset.select_by_edge_types:
1128         edge_type_criteria = []
1129         if lineset.select_silhouette:
1130             upred = pyNatureUP1D(Nature.SILHOUETTE)
1131             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_silhouette else upred)
1132         if lineset.select_border:
1133             upred = pyNatureUP1D(Nature.BORDER)
1134             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_border else upred)
1135         if lineset.select_crease:
1136             upred = pyNatureUP1D(Nature.CREASE)
1137             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_crease else upred)
1138         if lineset.select_ridge_valley:
1139             upred = pyNatureUP1D(Nature.RIDGE)
1140             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_ridge_valley else upred)
1141         if lineset.select_suggestive_contour:
1142             upred = pyNatureUP1D(Nature.SUGGESTIVE_CONTOUR)
1143             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_suggestive_contour else upred)
1144         if lineset.select_material_boundary:
1145             upred = pyNatureUP1D(Nature.MATERIAL_BOUNDARY)
1146             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_material_boundary else upred)
1147         if lineset.select_edge_mark:
1148             upred = pyNatureUP1D(Nature.EDGE_MARK)
1149             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_edge_mark else upred)
1150         if lineset.select_contour:
1151             upred = ContourUP1D()
1152             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_contour else upred)
1153         if lineset.select_external_contour:
1154             upred = ExternalContourUP1D()
1155             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_external_contour else upred)
1156         if lineset.edge_type_combination == 'OR':
1157             upred = join_unary_predicates(edge_type_criteria, OrUP1D)
1158         else:
1159             upred = join_unary_predicates(edge_type_criteria, AndUP1D)
1160         if upred is not None:
1161             if lineset.edge_type_negation == 'EXCLUSIVE':
1162                 upred = NotUP1D(upred)
1163             selection_criteria.append(upred)
1164     # prepare selection criteria by face marks
1165     if lineset.select_by_face_marks:
1166         if lineset.face_mark_condition == 'BOTH':
1167             upred = FaceMarkBothUP1D()
1168         else:
1169             upred = FaceMarkOneUP1D()
1170         if lineset.face_mark_negation == 'EXCLUSIVE':
1171             upred = NotUP1D(upred)
1172         selection_criteria.append(upred)
1173     # prepare selection criteria by group of objects
1174     if lineset.select_by_group:
1175         if lineset.group is not None:
1176             names = dict((ob.name, True) for ob in lineset.group.objects)
1177             upred = ObjectNamesUP1D(names, lineset.group_negation == 'EXCLUSIVE')
1178             selection_criteria.append(upred)
1179     # prepare selection criteria by image border
1180     if lineset.select_by_image_border:
1181         xmin, ymin, xmax, ymax = ContextFunctions.get_border()
1182         upred = WithinImageBoundaryUP1D(xmin, ymin, xmax, ymax)
1183         selection_criteria.append(upred)
1184     # select feature edges
1185     upred = join_unary_predicates(selection_criteria, AndUP1D)
1186     if upred is None:
1187         upred = TrueUP1D()
1188     Operators.select(upred)
1189     # join feature edges to form chains
1190     if linestyle.use_chaining:
1191         if linestyle.chaining == 'PLAIN':
1192             if linestyle.use_same_object:
1193                 Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred))
1194             else:
1195                 Operators.bidirectional_chain(ChainPredicateIterator(upred, TrueBP1D()), NotUP1D(upred))
1196         elif linestyle.chaining == 'SKETCHY':
1197             if linestyle.use_same_object:
1198                 Operators.bidirectional_chain(pySketchyChainSilhouetteIterator(linestyle.rounds))
1199             else:
1200                 Operators.bidirectional_chain(pySketchyChainingIterator(linestyle.rounds))
1201     else:
1202         Operators.chain(ChainPredicateIterator(FalseUP1D(), FalseBP1D()), NotUP1D(upred))
1203     # split chains
1204     if linestyle.material_boundary:
1205         Operators.sequential_split(MaterialBoundaryUP0D())
1206     if linestyle.use_angle_min or linestyle.use_angle_max:
1207         angle_min = linestyle.angle_min if linestyle.use_angle_min else None
1208         angle_max = linestyle.angle_max if linestyle.use_angle_max else None
1209         Operators.sequential_split(Curvature2DAngleThresholdUP0D(angle_min, angle_max))
1210     if linestyle.use_split_length:
1211         Operators.sequential_split(Length2DThresholdUP0D(linestyle.split_length), 1.0)
1212     if linestyle.use_split_pattern:
1213         pattern = []
1214         if linestyle.split_dash1 > 0 and linestyle.split_gap1 > 0:
1215             pattern.append(linestyle.split_dash1)
1216             pattern.append(linestyle.split_gap1)
1217         if linestyle.split_dash2 > 0 and linestyle.split_gap2 > 0:
1218             pattern.append(linestyle.split_dash2)
1219             pattern.append(linestyle.split_gap2)
1220         if linestyle.split_dash3 > 0 and linestyle.split_gap3 > 0:
1221             pattern.append(linestyle.split_dash3)
1222             pattern.append(linestyle.split_gap3)
1223         if len(pattern) > 0:
1224             sampling = 1.0
1225             controller = SplitPatternController(pattern, sampling)
1226             Operators.sequential_split(SplitPatternStartingUP0D(controller),
1227                                        SplitPatternStoppingUP0D(controller),
1228                                        sampling)
1229     # select chains
1230     if linestyle.use_length_min or linestyle.use_length_max:
1231         length_min = linestyle.length_min if linestyle.use_length_min else None
1232         length_max = linestyle.length_max if linestyle.use_length_max else None
1233         Operators.select(LengthThresholdUP1D(length_min, length_max))
1234     # prepare a list of stroke shaders
1235     shaders_list = []
1236     for m in linestyle.geometry_modifiers:
1237         if not m.use:
1238             continue
1239         if m.type == 'SAMPLING':
1240             shaders_list.append(SamplingShader(
1241                 m.sampling))
1242         elif m.type == 'BEZIER_CURVE':
1243             shaders_list.append(BezierCurveShader(
1244                 m.error))
1245         elif m.type == 'SINUS_DISPLACEMENT':
1246             shaders_list.append(SinusDisplacementShader(
1247                 m.wavelength, m.amplitude, m.phase))
1248         elif m.type == 'SPATIAL_NOISE':
1249             shaders_list.append(SpatialNoiseShader(
1250                 m.amplitude, m.scale, m.octaves, m.smooth, m.use_pure_random))
1251         elif m.type == 'PERLIN_NOISE_1D':
1252             shaders_list.append(PerlinNoise1DShader(
1253                 m.frequency, m.amplitude, m.octaves, m.angle, _seed.get(m.seed)))
1254         elif m.type == 'PERLIN_NOISE_2D':
1255             shaders_list.append(PerlinNoise2DShader(
1256                 m.frequency, m.amplitude, m.octaves, m.angle, _seed.get(m.seed)))
1257         elif m.type == 'BACKBONE_STRETCHER':
1258             shaders_list.append(BackboneStretcherShader(
1259                 m.backbone_length))
1260         elif m.type == 'TIP_REMOVER':
1261             shaders_list.append(TipRemoverShader(
1262                 m.tip_length))
1263         elif m.type == 'POLYGONIZATION':
1264             shaders_list.append(PolygonalizationShader(
1265                 m.error))
1266         elif m.type == 'GUIDING_LINES':
1267             shaders_list.append(GuidingLinesShader(
1268                 m.offset))
1269         elif m.type == 'BLUEPRINT':
1270             if m.shape == 'CIRCLES':
1271                 shaders_list.append(pyBluePrintCirclesShader(
1272                     m.rounds, m.random_radius, m.random_center))
1273             elif m.shape == 'ELLIPSES':
1274                 shaders_list.append(pyBluePrintEllipsesShader(
1275                     m.rounds, m.random_radius, m.random_center))
1276             elif m.shape == 'SQUARES':
1277                 shaders_list.append(pyBluePrintSquaresShader(
1278                     m.rounds, m.backbone_length, m.random_backbone))
1279         elif m.type == '2D_OFFSET':
1280             shaders_list.append(Offset2DShader(
1281                 m.start, m.end, m.x, m.y))
1282         elif m.type == '2D_TRANSFORM':
1283             shaders_list.append(Transform2DShader(
1284                 m.pivot, m.scale_x, m.scale_y, m.angle, m.pivot_u, m.pivot_x, m.pivot_y))
1285     ###
1286     shaders_list.append(StrokeCleaner())
1287     ###
1288     color = linestyle.color
1289     if (not linestyle.use_chaining) or (linestyle.chaining == 'PLAIN' and linestyle.use_same_object):
1290         thickness_position = linestyle.thickness_position
1291     else:
1292         thickness_position = 'CENTER'
1293         import bpy
1294         if bpy.app.debug_freestyle:
1295             print("Warning: Thickness position options are applied when chaining is disabled\n"
1296                   "         or the Plain chaining is used with the Same Object option enabled.")
1297     shaders_list.append(BaseColorShader(color.r, color.g, color.b, linestyle.alpha))
1298     shaders_list.append(BaseThicknessShader(linestyle.thickness, thickness_position,
1299                                             linestyle.thickness_ratio))
1300     for m in linestyle.color_modifiers:
1301         if not m.use:
1302             continue
1303         if m.type == 'ALONG_STROKE':
1304             shaders_list.append(ColorAlongStrokeShader(
1305                 m.blend, m.influence, m.color_ramp))
1306         elif m.type == 'DISTANCE_FROM_CAMERA':
1307             shaders_list.append(ColorDistanceFromCameraShader(
1308                 m.blend, m.influence, m.color_ramp,
1309                 m.range_min, m.range_max))
1310         elif m.type == 'DISTANCE_FROM_OBJECT':
1311             shaders_list.append(ColorDistanceFromObjectShader(
1312                 m.blend, m.influence, m.color_ramp, m.target,
1313                 m.range_min, m.range_max))
1314         elif m.type == 'MATERIAL':
1315             shaders_list.append(ColorMaterialShader(
1316                 m.blend, m.influence, m.color_ramp, m.material_attribute,
1317                 m.use_ramp))
1318     for m in linestyle.alpha_modifiers:
1319         if not m.use:
1320             continue
1321         if m.type == 'ALONG_STROKE':
1322             shaders_list.append(AlphaAlongStrokeShader(
1323                 m.blend, m.influence, m.mapping, m.invert, m.curve))
1324         elif m.type == 'DISTANCE_FROM_CAMERA':
1325             shaders_list.append(AlphaDistanceFromCameraShader(
1326                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1327                 m.range_min, m.range_max))
1328         elif m.type == 'DISTANCE_FROM_OBJECT':
1329             shaders_list.append(AlphaDistanceFromObjectShader(
1330                 m.blend, m.influence, m.mapping, m.invert, m.curve, m.target,
1331                 m.range_min, m.range_max))
1332         elif m.type == 'MATERIAL':
1333             shaders_list.append(AlphaMaterialShader(
1334                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1335                 m.material_attribute))
1336     for m in linestyle.thickness_modifiers:
1337         if not m.use:
1338             continue
1339         if m.type == 'ALONG_STROKE':
1340             shaders_list.append(ThicknessAlongStrokeShader(
1341                 thickness_position, linestyle.thickness_ratio,
1342                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1343                 m.value_min, m.value_max))
1344         elif m.type == 'DISTANCE_FROM_CAMERA':
1345             shaders_list.append(ThicknessDistanceFromCameraShader(
1346                 thickness_position, linestyle.thickness_ratio,
1347                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1348                 m.range_min, m.range_max, m.value_min, m.value_max))
1349         elif m.type == 'DISTANCE_FROM_OBJECT':
1350             shaders_list.append(ThicknessDistanceFromObjectShader(
1351                 thickness_position, linestyle.thickness_ratio,
1352                 m.blend, m.influence, m.mapping, m.invert, m.curve, m.target,
1353                 m.range_min, m.range_max, m.value_min, m.value_max))
1354         elif m.type == 'MATERIAL':
1355             shaders_list.append(ThicknessMaterialShader(
1356                 thickness_position, linestyle.thickness_ratio,
1357                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1358                 m.material_attribute, m.value_min, m.value_max))
1359         elif m.type == 'CALLIGRAPHY':
1360             shaders_list.append(CalligraphicThicknessShader(
1361                 thickness_position, linestyle.thickness_ratio,
1362                 m.blend, m.influence,
1363                 m.orientation, m.thickness_min, m.thickness_max))
1364     if linestyle.caps == 'ROUND':
1365         shaders_list.append(RoundCapShader())
1366     elif linestyle.caps == 'SQUARE':
1367         shaders_list.append(SquareCapShader())
1368     if linestyle.use_dashed_line:
1369         pattern = []
1370         if linestyle.dash1 > 0 and linestyle.gap1 > 0:
1371             pattern.append(linestyle.dash1)
1372             pattern.append(linestyle.gap1)
1373         if linestyle.dash2 > 0 and linestyle.gap2 > 0:
1374             pattern.append(linestyle.dash2)
1375             pattern.append(linestyle.gap2)
1376         if linestyle.dash3 > 0 and linestyle.gap3 > 0:
1377             pattern.append(linestyle.dash3)
1378             pattern.append(linestyle.gap3)
1379         if len(pattern) > 0:
1380             shaders_list.append(DashedLineShader(pattern))
1381     # create strokes using the shaders list
1382     Operators.create(TrueUP1D(), shaders_list)