Fix for the Selection by Image Border not working properly.
[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 import Freestyle
20 import math
21 import mathutils
22 import time
23
24 from freestyle_init import *
25 from logical_operators import *
26 from ChainingIterators import *
27 from shaders import *
28
29 class ColorRampModifier(StrokeShader):
30     def __init__(self, blend, influence, ramp):
31         StrokeShader.__init__(self)
32         self.__blend = blend
33         self.__influence = influence
34         self.__ramp = ramp
35     def evaluate(self, t):
36         col = Freestyle.evaluateColorRamp(self.__ramp, t)
37         col = col.xyz # omit alpha
38         return col
39     def blend_ramp(self, a, b):
40         return Freestyle.blendRamp(self.__blend, a, self.__influence, b)
41
42 class ScalarBlendModifier(StrokeShader):
43     def __init__(self, blend, influence):
44         StrokeShader.__init__(self)
45         self.__blend = blend
46         self.__influence = influence
47     def blend(self, v1, v2):
48         fac = self.__influence
49         facm = 1.0 - fac
50         if self.__blend == "MIX":
51             v1 = facm * v1 + fac * v2
52         elif self.__blend == "ADD":
53             v1 += fac * v2
54         elif self.__blend == "MULTIPLY":
55             v1 *= facm + fac * v2;
56         elif self.__blend == "SUBTRACT":
57             v1 -= fac * v2
58         elif self.__blend == "DIVIDE":
59             if v2 != 0.0:
60                 v1 = facm * v1 + fac * v1 / v2
61         elif self.__blend == "DIFFERENCE":
62             v1 = facm * v1 + fac * abs(v1 - v2)
63         elif self.__blend == "MININUM":
64             tmp = fac * v1
65             if v1 > tmp:
66                 v1 = tmp
67         elif self.__blend == "MAXIMUM":
68             tmp = fac * v1
69             if v1 < tmp:
70                 v1 = tmp
71         else:
72             raise ValueError("unknown curve blend type: " + self.__blend)
73         return v1
74
75 class CurveMappingModifier(ScalarBlendModifier):
76     def __init__(self, blend, influence, mapping, invert, curve):
77         ScalarBlendModifier.__init__(self, blend, influence)
78         assert mapping in ("LINEAR", "CURVE")
79         self.__mapping = getattr(self, mapping)
80         self.__invert = invert
81         self.__curve = curve
82     def LINEAR(self, t):
83         if self.__invert:
84             return 1.0 - t
85         return t
86     def CURVE(self, t):
87         return Freestyle.evaluateCurveMappingF(self.__curve, 0, t)
88     def evaluate(self, t):
89         return self.__mapping(t)
90
91 # Along Stroke modifiers
92
93 def iter_t2d_along_stroke(stroke):
94     total = stroke.getLength2D()
95     distance = 0.0
96     it = stroke.strokeVerticesBegin()
97     while not it.isEnd():
98         p = it.getObject().getPoint()
99         if not it.isBegin():
100             distance += (prev - p).length
101         prev = p
102         t = min(distance / total, 1.0)
103         yield it, t
104         it.increment()
105
106 class ColorAlongStrokeShader(ColorRampModifier):
107     def getName(self):
108         return "ColorAlongStrokeShader"
109     def shade(self, stroke):
110         for it, t in iter_t2d_along_stroke(stroke):
111             attr = it.getObject().attribute()
112             a = attr.getColorRGB()
113             b = self.evaluate(t)
114             c = self.blend_ramp(a, b)
115             attr.setColor(c)
116
117 class AlphaAlongStrokeShader(CurveMappingModifier):
118     def getName(self):
119         return "AlphaAlongStrokeShader"
120     def shade(self, stroke):
121         for it, t in iter_t2d_along_stroke(stroke):
122             attr = it.getObject().attribute()
123             a = attr.getAlpha()
124             b = self.evaluate(t)
125             c = self.blend(a, b)
126             attr.setAlpha(c)
127
128 class ThicknessAlongStrokeShader(CurveMappingModifier):
129     def __init__(self, blend, influence, mapping, invert, curve, value_min, value_max):
130         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
131         self.__value_min = value_min
132         self.__value_max = value_max
133     def getName(self):
134         return "ThicknessAlongStrokeShader"
135     def shade(self, stroke):
136         for it, t in iter_t2d_along_stroke(stroke):
137             attr = it.getObject().attribute()
138             a = attr.getThicknessRL()
139             a = a[0] + a[1]
140             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
141             c = self.blend(a, b)
142             attr.setThickness(c/2, c/2)
143
144 # Distance from Camera modifiers
145
146 def iter_distance_from_camera(stroke, range_min, range_max):
147     normfac = range_max - range_min # normalization factor
148     it = stroke.strokeVerticesBegin()
149     while not it.isEnd():
150         p = it.getObject().getPoint3D() # in the camera coordinate
151         distance = p.length
152         if distance < range_min:
153             t = 0.0
154         elif distance > range_max:
155             t = 1.0
156         else:
157             t = (distance - range_min) / normfac
158         yield it, t
159         it.increment()
160
161 class ColorDistanceFromCameraShader(ColorRampModifier):
162     def __init__(self, blend, influence, ramp, range_min, range_max):
163         ColorRampModifier.__init__(self, blend, influence, ramp)
164         self.__range_min = range_min
165         self.__range_max = range_max
166     def getName(self):
167         return "ColorDistanceFromCameraShader"
168     def shade(self, stroke):
169         for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max):
170             attr = it.getObject().attribute()
171             a = attr.getColorRGB()
172             b = self.evaluate(t)
173             c = self.blend_ramp(a, b)
174             attr.setColor(c)
175
176 class AlphaDistanceFromCameraShader(CurveMappingModifier):
177     def __init__(self, blend, influence, mapping, invert, curve, range_min, range_max):
178         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
179         self.__range_min = range_min
180         self.__range_max = range_max
181     def getName(self):
182         return "AlphaDistanceFromCameraShader"
183     def shade(self, stroke):
184         for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max):
185             attr = it.getObject().attribute()
186             a = attr.getAlpha()
187             b = self.evaluate(t)
188             c = self.blend(a, b)
189             attr.setAlpha(c)
190
191 class ThicknessDistanceFromCameraShader(CurveMappingModifier):
192     def __init__(self, blend, influence, mapping, invert, curve, range_min, range_max, value_min, value_max):
193         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
194         self.__range_min = range_min
195         self.__range_max = range_max
196         self.__value_min = value_min
197         self.__value_max = value_max
198     def getName(self):
199         return "ThicknessDistanceFromCameraShader"
200     def shade(self, stroke):
201         for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max):
202             attr = it.getObject().attribute()
203             a = attr.getThicknessRL()
204             a = a[0] + a[1]
205             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
206             c = self.blend(a, b)
207             attr.setThickness(c/2, c/2)
208
209 # Distance from Object modifiers
210
211 def iter_distance_from_object(stroke, object, range_min, range_max):
212     scene = Freestyle.getCurrentScene()
213     mv = scene.camera.matrix_world.copy() # model-view matrix
214     mv.invert()
215     loc = mv * object.location # loc in the camera coordinate
216     normfac = range_max - range_min # normalization factor
217     it = stroke.strokeVerticesBegin()
218     while not it.isEnd():
219         p = it.getObject().getPoint3D() # in the camera coordinate
220         distance = (p - loc).length
221         if distance < range_min:
222             t = 0.0
223         elif distance > range_max:
224             t = 1.0
225         else:
226             t = (distance - range_min) / normfac
227         yield it, t
228         it.increment()
229
230 class ColorDistanceFromObjectShader(ColorRampModifier):
231     def __init__(self, blend, influence, ramp, target, range_min, range_max):
232         ColorRampModifier.__init__(self, blend, influence, ramp)
233         self.__target = target
234         self.__range_min = range_min
235         self.__range_max = range_max
236     def getName(self):
237         return "ColorDistanceFromObjectShader"
238     def shade(self, stroke):
239         if self.__target is None:
240             return
241         for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max):
242             attr = it.getObject().attribute()
243             a = attr.getColorRGB()
244             b = self.evaluate(t)
245             c = self.blend_ramp(a, b)
246             attr.setColor(c)
247
248 class AlphaDistanceFromObjectShader(CurveMappingModifier):
249     def __init__(self, blend, influence, mapping, invert, curve, target, range_min, range_max):
250         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
251         self.__target = target
252         self.__range_min = range_min
253         self.__range_max = range_max
254     def getName(self):
255         return "AlphaDistanceFromObjectShader"
256     def shade(self, stroke):
257         if self.__target is None:
258             return
259         for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max):
260             attr = it.getObject().attribute()
261             a = attr.getAlpha()
262             b = self.evaluate(t)
263             c = self.blend(a, b)
264             attr.setAlpha(c)
265
266 class ThicknessDistanceFromObjectShader(CurveMappingModifier):
267     def __init__(self, blend, influence, mapping, invert, curve, target, range_min, range_max, value_min, value_max):
268         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
269         self.__target = target
270         self.__range_min = range_min
271         self.__range_max = range_max
272         self.__value_min = value_min
273         self.__value_max = value_max
274     def getName(self):
275         return "ThicknessDistanceFromObjectShader"
276     def shade(self, stroke):
277         if self.__target is None:
278             return
279         for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max):
280             attr = it.getObject().attribute()
281             a = attr.getThicknessRL()
282             a = a[0] + a[1]
283             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
284             c = self.blend(a, b)
285             attr.setThickness(c/2, c/2)
286
287 # Material modifiers
288
289 def iter_material_color(stroke, material_attr):
290     func = CurveMaterialF0D()
291     it = stroke.strokeVerticesBegin()
292     while not it.isEnd():
293         material = func(it.castToInterface0DIterator())
294         if material_attr == "DIFF":
295             color = (material.diffuseR(),
296                      material.diffuseG(),
297                      material.diffuseB())
298         elif material_attr == "SPEC":
299             color = (material.specularR(),
300                      material.specularG(),
301                      material.specularB())
302         else:
303             raise ValueError("unexpected material attribute: " + material_attr)
304         yield it, color
305         it.increment()
306
307 def iter_material_value(stroke, material_attr):
308     func = CurveMaterialF0D()
309     it = stroke.strokeVerticesBegin()
310     while not it.isEnd():
311         material = func(it.castToInterface0DIterator())
312         if material_attr == "DIFF":
313             r = material.diffuseR()
314             g = material.diffuseG()
315             b = material.diffuseB()
316             t = 0.35 * r + 0.45 * r + 0.2 * b
317         elif material_attr == "DIFF_R":
318             t = material.diffuseR()
319         elif material_attr == "DIFF_G":
320             t = material.diffuseG()
321         elif material_attr == "DIFF_B":
322             t = material.diffuseB()
323         elif material_attr == "SPEC":
324             r = material.specularR()
325             g = material.specularG()
326             b = material.specularB()
327             t = 0.35 * r + 0.45 * r + 0.2 * b
328         elif material_attr == "SPEC_R":
329             t = material.specularR()
330         elif material_attr == "SPEC_G":
331             t = material.specularG()
332         elif material_attr == "SPEC_B":
333             t = material.specularB()
334         elif material_attr == "SPEC_HARDNESS":
335             t = material.shininess()
336         elif material_attr == "ALPHA":
337             t = material.diffuseA()
338         else:
339             raise ValueError("unexpected material attribute: " + material_attr)
340         yield it, t
341         it.increment()
342
343 class ColorMaterialShader(ColorRampModifier):
344     def __init__(self, blend, influence, ramp, material_attr, use_ramp):
345         ColorRampModifier.__init__(self, blend, influence, ramp)
346         self.__material_attr = material_attr
347         self.__use_ramp = use_ramp
348     def getName(self):
349         return "ColorMaterialShader"
350     def shade(self, stroke):
351         if self.__material_attr in ["DIFF", "SPEC"] and not self.__use_ramp:
352             for it, b in iter_material_color(stroke, self.__material_attr):
353                 attr = it.getObject().attribute()
354                 a = attr.getColorRGB()
355                 c = self.blend_ramp(a, b)
356                 attr.setColor(c)
357         else:
358             for it, t in iter_material_value(stroke, self.__material_attr):
359                 attr = it.getObject().attribute()
360                 a = attr.getColorRGB()
361                 b = self.evaluate(t)
362                 c = self.blend_ramp(a, b)
363                 attr.setColor(c)
364
365 class AlphaMaterialShader(CurveMappingModifier):
366     def __init__(self, blend, influence, mapping, invert, curve, material_attr):
367         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
368         self.__material_attr = material_attr
369     def getName(self):
370         return "AlphaMaterialShader"
371     def shade(self, stroke):
372         for it, t in iter_material_value(stroke, self.__material_attr):
373             attr = it.getObject().attribute()
374             a = attr.getAlpha()
375             b = self.evaluate(t)
376             c = self.blend(a, b)
377             attr.setAlpha(c)
378
379 class ThicknessMaterialShader(CurveMappingModifier):
380     def __init__(self, blend, influence, mapping, invert, curve, material_attr, value_min, value_max):
381         CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve)
382         self.__material_attr = material_attr
383         self.__value_min = value_min
384         self.__value_max = value_max
385     def getName(self):
386         return "ThicknessMaterialShader"
387     def shade(self, stroke):
388         for it, t in iter_material_value(stroke, self.__material_attr):
389             attr = it.getObject().attribute()
390             a = attr.getThicknessRL()
391             a = a[0] + a[1]
392             b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min)
393             c = self.blend(a, b)
394             attr.setThickness(c/2, c/2)
395
396 # Calligraphic thickness modifier
397
398 class CalligraphicThicknessShader(ScalarBlendModifier):
399     def __init__(self, blend, influence, orientation, min_thickness, max_thickness):
400         ScalarBlendModifier.__init__(self, blend, influence)
401         rad = orientation / 180.0 * math.pi
402         self.__orientation = mathutils.Vector((math.cos(rad), math.sin(rad)))
403         self.__min_thickness = min_thickness
404         self.__max_thickness = max_thickness
405     def shade(self, stroke):
406         func = VertexOrientation2DF0D()
407         it = stroke.strokeVerticesBegin()
408         while not it.isEnd():
409             dir = func(it.castToInterface0DIterator())
410             orthDir = mathutils.Vector((-dir.y, dir.x))
411             orthDir.normalize()
412             fac = abs(orthDir * self.__orientation)
413             attr = it.getObject().attribute()
414             a = attr.getThicknessRL()
415             a = a[0] + a[1]
416             b = self.__min_thickness + fac * (self.__max_thickness - self.__min_thickness)
417             b = max(b, 0.0)
418             c = self.blend(a, b)
419             attr.setThickness(c/2, c/2)
420             it.increment()
421
422 # Geometry modifiers
423
424 def iter_distance_along_stroke(stroke):
425     distance = 0.0
426     it = stroke.strokeVerticesBegin()
427     while not it.isEnd():
428         p = it.getObject().getPoint()
429         if not it.isBegin():
430             distance += (prev - p).length
431         prev = p
432         yield it, distance
433         it.increment()
434
435 class SinusDisplacementShader(StrokeShader):
436     def __init__(self, wavelength, amplitude, phase):
437         StrokeShader.__init__(self)
438         self._wavelength = wavelength
439         self._amplitude = amplitude
440         self._phase = phase / wavelength * 2 * math.pi
441         self._getNormal = Normal2DF0D()
442     def getName(self):
443         return "SinusDisplacementShader"
444     def shade(self, stroke):
445         for it, distance in iter_distance_along_stroke(stroke):
446             v = it.getObject()
447             n = self._getNormal(it.castToInterface0DIterator())
448             p = v.getPoint()
449             u = v.u()
450             n = n * self._amplitude * math.cos(distance / self._wavelength * 2 * math.pi + self._phase)
451             v.setPoint(p + n)
452         stroke.UpdateLength()
453
454 class PerlinNoise1DShader(StrokeShader):
455     def __init__(self, freq = 10, amp = 10, oct = 4, angle = 45, seed = -1):
456         StrokeShader.__init__(self)
457         self.__noise = Noise(seed)
458         self.__freq = freq
459         self.__amp = amp
460         self.__oct = oct
461         theta = pi * angle / 180.0
462         self.__dir = Vector([cos(theta), sin(theta)])
463     def getName(self):
464         return "PerlinNoise1DShader"
465     def shade(self, stroke):
466         length = stroke.getLength2D()
467         it = stroke.strokeVerticesBegin()
468         while not it.isEnd():
469             v = it.getObject()
470             nres = self.__noise.turbulence1(length * v.u(), self.__freq, self.__amp, self.__oct)
471             v.setPoint(v.getPoint() + nres * self.__dir)
472             it.increment()
473         stroke.UpdateLength()
474
475 class PerlinNoise2DShader(StrokeShader):
476     def __init__(self, freq = 10, amp = 10, oct = 4, angle = 45, seed = -1):
477         StrokeShader.__init__(self)
478         self.__noise = Noise(seed)
479         self.__freq = freq
480         self.__amp = amp
481         self.__oct = oct
482         theta = pi * angle / 180.0
483         self.__dir = Vector([cos(theta), sin(theta)])
484     def getName(self):
485         return "PerlinNoise2DShader"
486     def shade(self, stroke):
487         it = stroke.strokeVerticesBegin()
488         while not it.isEnd():
489             v = it.getObject()
490             vec = Vector([v.getProjectedX(), v.getProjectedY()])
491             nres = self.__noise.turbulence2(vec, self.__freq, self.__amp, self.__oct)
492             v.setPoint(v.getPoint() + nres * self.__dir)
493             it.increment()
494         stroke.UpdateLength()
495
496 class Offset2DShader(StrokeShader):
497     def __init__(self, start, end, x, y):
498         StrokeShader.__init__(self)
499         self.__start = start
500         self.__end = end
501         self.__xy = Vector([x, y])
502         self.__getNormal = Normal2DF0D()
503     def getName(self):
504         return "Offset2DShader"
505     def shade(self, stroke):
506         it = stroke.strokeVerticesBegin()
507         while not it.isEnd():
508             v = it.getObject()
509             u = v.u()
510             a = self.__start + u * (self.__end - self.__start)
511             n = self.__getNormal(it.castToInterface0DIterator())
512             n = n * a
513             p = v.getPoint()
514             v.setPoint(p + n + self.__xy)
515             it.increment()
516         stroke.UpdateLength()
517
518 class Transform2DShader(StrokeShader):
519     def __init__(self, pivot, scale_x, scale_y, angle, pivot_u, pivot_x, pivot_y):
520         StrokeShader.__init__(self)
521         self.__pivot = pivot
522         self.__scale_x = scale_x
523         self.__scale_y = scale_y
524         self.__angle = angle
525         self.__pivot_u = pivot_u
526         self.__pivot_x = pivot_x
527         self.__pivot_y = pivot_y
528     def getName(self):
529         return "Transform2DShader"
530     def shade(self, stroke):
531         # determine the pivot of scaling and rotation operations
532         if self.__pivot == "START":
533             it = stroke.strokeVerticesBegin()
534             pivot = it.getObject().getPoint()
535         elif self.__pivot == "END":
536             it = stroke.strokeVerticesEnd()
537             it.decrement()
538             pivot = it.getObject().getPoint()
539         elif self.__pivot == "PARAM":
540             p = None
541             it = stroke.strokeVerticesBegin()
542             while not it.isEnd():
543                 prev = p
544                 v = it.getObject()
545                 p = v.getPoint()
546                 u = v.u()
547                 if self.__pivot_u < u:
548                     break
549                 it.increment()
550             if prev is None:
551                 pivot = p
552             else:
553                 delta = u - self.__pivot_u
554                 pivot = p + delta * (prev - p)
555         elif self.__pivot == "CENTER":
556             pivot = Vector([0.0, 0.0])
557             n = 0
558             it = stroke.strokeVerticesBegin()
559             while not it.isEnd():
560                 p = it.getObject().getPoint()
561                 pivot = pivot + p
562                 n = n + 1
563                 it.increment()
564             pivot.x = pivot.x / n
565             pivot.y = pivot.y / n
566         elif self.__pivot == "ABSOLUTE":
567             pivot = Vector([self.__pivot_x, self.__pivot_y])
568         # apply scaling and rotation operations
569         cos_theta = math.cos(math.pi * self.__angle / 180.0)
570         sin_theta = math.sin(math.pi * self.__angle / 180.0)
571         it = stroke.strokeVerticesBegin()
572         while not it.isEnd():
573             v = it.getObject()
574             p = v.getPoint()
575             p = p - pivot
576             x = p.x * self.__scale_x
577             y = p.y * self.__scale_y
578             p.x = x * cos_theta - y * sin_theta
579             p.y = x * sin_theta + y * cos_theta
580             v.setPoint(p + pivot)
581             it.increment()
582         stroke.UpdateLength()
583
584 # Predicates and helper functions
585
586 class QuantitativeInvisibilityRangeUP1D(UnaryPredicate1D):
587     def __init__(self, qi_start, qi_end):
588         UnaryPredicate1D.__init__(self)
589         self.__getQI = QuantitativeInvisibilityF1D()
590         self.__qi_start = qi_start
591         self.__qi_end = qi_end
592     def getName(self):
593         return "QuantitativeInvisibilityRangeUP1D"
594     def __call__(self, inter):
595         qi = self.__getQI(inter)
596         return self.__qi_start <= qi <= self.__qi_end
597
598 def join_unary_predicates(upred_list, bpred):
599     if not upred_list:
600         return None
601     upred = upred_list[0]
602     for p in upred_list[1:]:
603         upred = bpred(upred, p)
604     return upred
605
606 class ObjectNamesUP1D(UnaryPredicate1D):
607     def __init__(self, names, negative):
608         UnaryPredicate1D.__init__(self)
609         self._names = names
610         self._negative = negative
611     def getName(self):
612         return "ObjectNamesUP1D"
613     def __call__(self, viewEdge):
614         found = viewEdge.viewShape().getName() in self._names
615         if self._negative:
616             return not found
617         return found
618
619 class WithinImageBorderUP1D(UnaryPredicate1D):
620     def __init__(self, xmin, xmax, ymin, ymax):
621         UnaryPredicate1D.__init__(self)
622         self._xmin = xmin
623         self._xmax = xmax
624         self._ymin = ymin
625         self._ymax = ymax
626     def getName(self):
627         return "WithinImageBorderUP1D"
628     def __call__(self, inter):
629         it = inter.verticesBegin()
630         while not it.isEnd():
631             if self.withinBorder(it.getObject()):
632                 return True
633             it.increment()
634         return False
635     def withinBorder(self, vert):
636         x = vert.getProjectedX()
637         y = vert.getProjectedY()
638         return self._xmin <= x <= self._xmax and self._ymin <= y <= self._ymax
639
640 # Stroke caps
641
642 def iter_stroke_vertices(stroke):
643     it = stroke.strokeVerticesBegin()
644     prev_p = None
645     while not it.isEnd():
646         sv = it.getObject()
647         p = sv.getPoint()
648         if prev_p is None or (prev_p - p).length > 1e-6:
649             yield sv
650             prev_p = p
651         it.increment()
652
653 class RoundCapShader(StrokeShader):
654     def round_cap_thickness(self, x):
655         x = max(0.0, min(x, 1.0))
656         return math.sqrt(1.0 - (x ** 2))
657     def shade(self, stroke):
658         # save the location and attribute of stroke vertices
659         buffer = []
660         for sv in iter_stroke_vertices(stroke):
661             buffer.append((sv.getPoint(), sv.attribute()))
662         nverts = len(buffer)
663         if nverts < 2:
664             return
665         # calculate the number of additional vertices to form caps
666         R, L = stroke[0].attribute().getThicknessRL()
667         caplen_beg = (R + L) / 2.0
668         nverts_beg = max(5, int(R + L))
669         R, L = stroke[-1].attribute().getThicknessRL()
670         caplen_end = (R + L) / 2.0
671         nverts_end = max(5, int(R + L))
672         # adjust the total number of stroke vertices
673         stroke.Resample(nverts + nverts_beg + nverts_end)
674         # restore the location and attribute of the original vertices
675         for i in range(nverts):
676             p, attr = buffer[i]
677             stroke[nverts_beg + i].setPoint(p)
678             stroke[nverts_beg + i].setAttribute(attr)
679         # reshape the cap at the beginning of the stroke
680         q, attr = buffer[1]
681         p, attr = buffer[0]
682         d = p - q
683         d = d / d.length * caplen_beg
684         n = 1.0 / nverts_beg
685         R, L = attr.getThicknessRL()
686         for i in range(nverts_beg):
687             t = (nverts_beg - i) * n
688             stroke[i].setPoint(p + d * t)
689             r = self.round_cap_thickness((nverts_beg - i + 1) * n)
690             stroke[i].setAttribute(attr)
691             stroke[i].attribute().setThickness(R * r, L * r)
692         # reshape the cap at the end of the stroke
693         q, attr = buffer[-2]
694         p, attr = buffer[-1]
695         d = p - q
696         d = d / d.length * caplen_end
697         n = 1.0 / nverts_end
698         R, L = attr.getThicknessRL()
699         for i in range(nverts_end):
700             t = (nverts_end - i) * n
701             stroke[-i-1].setPoint(p + d * t)
702             r = self.round_cap_thickness((nverts_end - i + 1) * n)
703             stroke[-i-1].setAttribute(attr)
704             stroke[-i-1].attribute().setThickness(R * r, L * r)
705
706 class SquareCapShader(StrokeShader):
707     def shade(self, stroke):
708         # save the location and attribute of stroke vertices
709         buffer = []
710         for sv in iter_stroke_vertices(stroke):
711             buffer.append((sv.getPoint(), sv.attribute()))
712         nverts = len(buffer)
713         if nverts < 2:
714             return
715         # calculate the number of additional vertices to form caps
716         R, L = stroke[0].attribute().getThicknessRL()
717         caplen_beg = (R + L) / 2.0
718         nverts_beg = 1
719         R, L = stroke[-1].attribute().getThicknessRL()
720         caplen_end = (R + L) / 2.0
721         nverts_end = 1
722         # adjust the total number of stroke vertices
723         stroke.Resample(nverts + nverts_beg + nverts_end)
724         # restore the location and attribute of the original vertices
725         for i in range(nverts):
726             p, attr = buffer[i]
727             stroke[nverts_beg + i].setPoint(p)
728             stroke[nverts_beg + i].setAttribute(attr)
729         # reshape the cap at the beginning of the stroke
730         q, attr = buffer[1]
731         p, attr = buffer[0]
732         d = p - q
733         stroke[0].setPoint(p + d / d.length * caplen_beg)
734         stroke[0].setAttribute(attr)
735         # reshape the cap at the end of the stroke
736         q, attr = buffer[-2]
737         p, attr = buffer[-1]
738         d = p - q
739         stroke[-1].setPoint(p + d / d.length * caplen_beg)
740         stroke[-1].setAttribute(attr)
741
742 # dashed line
743
744 class DashedLineStartingUP0D(UnaryPredicate0D):
745     def __init__(self, controller):
746         UnaryPredicate0D.__init__(self)
747         self._controller = controller
748     def __call__(self, inter):
749         return self._controller.start()
750
751 class DashedLineStoppingUP0D(UnaryPredicate0D):
752     def __init__(self, controller):
753         UnaryPredicate0D.__init__(self)
754         self._controller = controller
755     def __call__(self, inter):
756         return self._controller.stop()
757
758 class DashedLineController:
759     def __init__(self, pattern, sampling):
760         self.sampling = float(sampling)
761         k = len(pattern) // 2
762         n = k * 2
763         self.start_pos = [pattern[i] + pattern[i+1] for i in range(0, n, 2)]
764         self.stop_pos = [pattern[i] for i in range(0, n, 2)]
765         self.init()
766     def init(self):
767         self.start_len = 0.0
768         self.start_idx = 0
769         self.stop_len = self.sampling
770         self.stop_idx = 0
771     def start(self):
772         self.start_len += self.sampling
773         if abs(self.start_len - self.start_pos[self.start_idx]) < self.sampling / 2.0:
774             self.start_len = 0.0
775             self.start_idx = (self.start_idx + 1) % len(self.start_pos)
776             return True
777         return False
778     def stop(self):
779         if self.start_len > 0.0:
780             self.init()
781         self.stop_len += self.sampling
782         if abs(self.stop_len - self.stop_pos[self.stop_idx]) < self.sampling / 2.0:
783             self.stop_len = self.sampling
784             self.stop_idx = (self.stop_idx + 1) % len(self.stop_pos)
785             return True
786         return False
787
788 # predicates for chaining
789
790 class AngleLargerThanBP1D(BinaryPredicate1D):
791     def __init__(self, angle):
792         BinaryPredicate1D.__init__(self)
793         self._angle = math.pi * angle / 180.0
794     def getName(self):
795         return "AngleLargerThanBP1D"
796     def __call__(self, i1, i2):
797         fe1a = i1.fedgeA()
798         fe1b = i1.fedgeB()
799         fe2a = i2.fedgeA()
800         fe2b = i2.fedgeB()
801         sv1a = fe1a.vertexA().getPoint2D()
802         sv1b = fe1b.vertexB().getPoint2D()
803         sv2a = fe2a.vertexA().getPoint2D()
804         sv2b = fe2b.vertexB().getPoint2D()
805         if (sv1a - sv2a).length < 1e-6:
806             dir1 = sv1a - sv1b
807             dir2 = sv2b - sv2a
808         elif (sv1b - sv2b).length < 1e-6:
809             dir1 = sv1b - sv1a
810             dir2 = sv2a - sv2b
811         elif (sv1a - sv2b).length < 1e-6:
812             dir1 = sv1a - sv1b
813             dir2 = sv2a - sv2b
814         elif (sv1b - sv2a).length < 1e-6:
815             dir1 = sv1b - sv1a
816             dir2 = sv2b - sv2a
817         else:
818             return False
819         denom = dir1.length * dir2.length
820         if denom < 1e-6:
821             return False
822         x = (dir1 * dir2) / denom
823         return math.acos(min(max(x, -1.0), 1.0)) > self._angle
824
825 class AndBP1D(BinaryPredicate1D):
826     def __init__(self, pred1, pred2):
827         BinaryPredicate1D.__init__(self)
828         self.__pred1 = pred1
829         self.__pred2 = pred2
830     def getName(self):
831         return "AndBP1D"
832     def __call__(self, i1, i2):
833         return self.__pred1(i1, i2) and self.__pred2(i1, i2)
834
835 # predicates for selection
836
837 class LengthThresholdUP1D(UnaryPredicate1D):
838     def __init__(self, min_length=None, max_length=None):
839         UnaryPredicate1D.__init__(self)
840         self._min_length = min_length
841         self._max_length = max_length
842     def getName(self):
843         return "LengthThresholdUP1D"
844     def __call__(self, inter):
845         length = inter.getLength2D()
846         if self._min_length is not None and length < self._min_length:
847             return False
848         if self._max_length is not None and length > self._max_length:
849             return False
850         return True
851
852 class FaceMarkBothUP1D(UnaryPredicate1D):
853     def __call__(self, inter): # ViewEdge
854         fe = inter.fedgeA()
855         while fe is not None:
856             if fe.isSmooth():
857                 if fe.faceMark():
858                     return True
859             else:
860                 if fe.aFaceMark() and fe.bFaceMark():
861                     return True
862             fe = fe.nextEdge()
863         return False
864
865 class FaceMarkOneUP1D(UnaryPredicate1D):
866     def __call__(self, inter): # ViewEdge
867         fe = inter.fedgeA()
868         while fe is not None:
869             if fe.isSmooth():
870                 if fe.faceMark():
871                     return True
872             else:
873                 if fe.aFaceMark() or fe.bFaceMark():
874                     return True
875             fe = fe.nextEdge()
876         return False
877
878 # predicates for splitting
879
880 class MaterialBoundaryUP0D(UnaryPredicate0D):
881     def getName(self):
882         return "MaterialBoundaryUP0D"
883     def __call__(self, it):
884         if it.isBegin():
885             return False
886         it_prev = Interface0DIterator(it) 
887         it_prev.decrement()
888         v = it.getObject()
889         it.increment()
890         if it.isEnd():
891             return False
892         fe = v.getFEdge(it_prev.getObject())
893         idx1 = fe.materialIndex() if fe.isSmooth() else fe.bMaterialIndex()
894         fe = v.getFEdge(it.getObject())
895         idx2 = fe.materialIndex() if fe.isSmooth() else fe.bMaterialIndex()
896         return idx1 != idx2
897
898 class Curvature2DAngleThresholdUP0D(UnaryPredicate0D):
899     def __init__(self, min_angle=None, max_angle=None):
900         UnaryPredicate0D.__init__(self)
901         self._min_angle = min_angle
902         self._max_angle = max_angle
903         self._func = Curvature2DAngleF0D()
904     def getName(self):
905         return "Curvature2DAngleThresholdUP0D"
906     def __call__(self, inter):
907         angle = math.pi - self._func(inter)
908         if self._min_angle is not None and angle < self._min_angle:
909             return True
910         if self._max_angle is not None and angle > self._max_angle:
911             return True
912         return False
913
914 class Length2DThresholdUP0D(UnaryPredicate0D):
915     def __init__(self, length_limit):
916         UnaryPredicate0D.__init__(self)
917         self._length_limit = length_limit
918         self._t = 0.0
919     def getName(self):
920         return "Length2DThresholdUP0D"
921     def __call__(self, inter):
922         t = inter.t() # curvilinear abscissa
923         if t < self._t:
924             self._t = 0.0
925             return False
926         if t - self._t < self._length_limit:
927             return False
928         self._t = t
929         return True
930
931 # Seed for random number generation
932
933 class Seed:
934     def __init__(self):
935         self.t_max = 2 ** 15
936         self.t = int(time.time()) % self.t_max
937     def get(self, seed):
938         if seed < 0:
939             self.t = (self.t + 1) % self.t_max
940             return self.t
941         return seed
942
943 _seed = Seed()
944
945 # main function for parameter processing
946
947 def process(layer_name, lineset_name):
948     scene = Freestyle.getCurrentScene()
949     layer = scene.render.layers[layer_name]
950     lineset = layer.freestyle_settings.linesets[lineset_name]
951     linestyle = lineset.linestyle
952
953     selection_criteria = []
954     # prepare selection criteria by visibility
955     if lineset.select_by_visibility:
956         if lineset.visibility == "VISIBLE":
957             selection_criteria.append(
958                 QuantitativeInvisibilityUP1D(0))
959         elif lineset.visibility == "HIDDEN":
960             selection_criteria.append(
961                 NotUP1D(QuantitativeInvisibilityUP1D(0)))
962         elif lineset.visibility == "RANGE":
963             selection_criteria.append(
964                 QuantitativeInvisibilityRangeUP1D(lineset.qi_start, lineset.qi_end))
965     # prepare selection criteria by edge types
966     if lineset.select_by_edge_types:
967         edge_type_criteria = []
968         if lineset.select_silhouette:
969             upred = pyNatureUP1D(Nature.SILHOUETTE)
970             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_silhouette else upred)
971         if lineset.select_border:
972             upred = pyNatureUP1D(Nature.BORDER)
973             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_border else upred)
974         if lineset.select_crease:
975             upred = pyNatureUP1D(Nature.CREASE)
976             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_crease else upred)
977         if lineset.select_ridge_valley:
978             upred = pyNatureUP1D(Nature.RIDGE)
979             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_ridge_valley else upred)
980         if lineset.select_suggestive_contour:
981             upred = pyNatureUP1D(Nature.SUGGESTIVE_CONTOUR)
982             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_suggestive_contour else upred)
983         if lineset.select_material_boundary:
984             upred = pyNatureUP1D(Nature.MATERIAL_BOUNDARY)
985             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_material_boundary else upred)
986         if lineset.select_edge_mark:
987             upred = pyNatureUP1D(Nature.EDGE_MARK)
988             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_edge_mark else upred)
989         if lineset.select_contour:
990             upred = ContourUP1D()
991             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_contour else upred)
992         if lineset.select_external_contour:
993             upred = ExternalContourUP1D()
994             edge_type_criteria.append(NotUP1D(upred) if lineset.exclude_external_contour else upred)
995         if lineset.edge_type_combination == "OR":
996             upred = join_unary_predicates(edge_type_criteria, OrUP1D)
997         else:
998             upred = join_unary_predicates(edge_type_criteria, AndUP1D)
999         if upred is not None:
1000             if lineset.edge_type_negation == "EXCLUSIVE":
1001                 upred = NotUP1D(upred)
1002             selection_criteria.append(upred)
1003     # prepare selection criteria by face marks
1004     if lineset.select_by_face_marks:
1005         if lineset.face_mark_condition == "BOTH":
1006             upred = FaceMarkBothUP1D()
1007         else:
1008             upred = FaceMarkOneUP1D()
1009         if lineset.face_mark_negation == "EXCLUSIVE":
1010             upred = NotUP1D(upred)
1011         selection_criteria.append(upred)
1012     # prepare selection criteria by group of objects
1013     if lineset.select_by_group:
1014         if lineset.group is not None and len(lineset.group.objects) > 0:
1015             names = dict((ob.name, True) for ob in lineset.group.objects)
1016             upred = ObjectNamesUP1D(names, lineset.group_negation == 'EXCLUSIVE')
1017             selection_criteria.append(upred)
1018     # prepare selection criteria by image border
1019     if lineset.select_by_image_border:
1020         w = scene.render.resolution_x
1021         h = scene.render.resolution_y
1022         if scene.render.use_border:
1023             xmin = scene.render.border_min_x * w
1024             xmax = scene.render.border_max_x * w
1025             ymin = scene.render.border_min_y * h
1026             ymax = scene.render.border_max_y * h
1027         else:
1028             xmin, xmax = 0.0, float(w)
1029             ymin, ymax = 0.0, float(h)
1030         upred = WithinImageBorderUP1D(xmin, xmax, ymin, ymax)
1031         selection_criteria.append(upred)
1032     # select feature edges
1033     upred = join_unary_predicates(selection_criteria, AndUP1D)
1034     if upred is None:
1035         upred = TrueUP1D()
1036     Operators.select(upred)
1037     # join feature edges to form chains
1038     if linestyle.use_chaining:
1039         if linestyle.chaining == "PLAIN":
1040             if linestyle.same_object:
1041                 Operators.bidirectionalChain(ChainSilhouetteIterator(), NotUP1D(upred))
1042             else:
1043                 Operators.bidirectionalChain(ChainPredicateIterator(upred, TrueBP1D()), NotUP1D(upred))
1044         elif linestyle.chaining == "SKETCHY":
1045             if linestyle.same_object:
1046                 Operators.bidirectionalChain(pySketchyChainSilhouetteIterator(linestyle.rounds))
1047             else:
1048                 Operators.bidirectionalChain(pySketchyChainingIterator(linestyle.rounds))
1049     else:
1050         Operators.chain(ChainPredicateIterator(FalseUP1D(), FalseBP1D()), NotUP1D(upred))
1051     # split chains
1052     if linestyle.material_boundary:
1053         Operators.sequentialSplit(MaterialBoundaryUP0D())
1054     if linestyle.use_min_angle or linestyle.use_max_angle:
1055         min_angle = linestyle.min_angle if linestyle.use_min_angle else None
1056         max_angle = linestyle.max_angle if linestyle.use_max_angle else None
1057         Operators.sequentialSplit(Curvature2DAngleThresholdUP0D(min_angle, max_angle))
1058     if linestyle.use_split_length:
1059         Operators.sequentialSplit(Length2DThresholdUP0D(linestyle.split_length), 1.0)
1060     # select chains
1061     if linestyle.use_min_length or linestyle.use_max_length:
1062         min_length = linestyle.min_length if linestyle.use_min_length else None
1063         max_length = linestyle.max_length if linestyle.use_max_length else None
1064         Operators.select(LengthThresholdUP1D(min_length, max_length))
1065     # dashed line
1066     if linestyle.use_dashed_line:
1067         pattern = []
1068         if linestyle.dash1 > 0 and linestyle.gap1 > 0:
1069             pattern.append(linestyle.dash1)
1070             pattern.append(linestyle.gap1)
1071         if linestyle.dash2 > 0 and linestyle.gap2 > 0:
1072             pattern.append(linestyle.dash2)
1073             pattern.append(linestyle.gap2)
1074         if linestyle.dash3 > 0 and linestyle.gap3 > 0:
1075             pattern.append(linestyle.dash3)
1076             pattern.append(linestyle.gap3)
1077         if len(pattern) > 0:
1078             sampling = 1.0
1079             controller = DashedLineController(pattern, sampling)
1080             Operators.sequentialSplit(DashedLineStartingUP0D(controller),
1081                                       DashedLineStoppingUP0D(controller),
1082                                       sampling)
1083     # prepare a list of stroke shaders
1084     shaders_list = []
1085     for m in linestyle.geometry_modifiers:
1086         if not m.use:
1087             continue
1088         if m.type == "SAMPLING":
1089             shaders_list.append(SamplingShader(
1090                 m.sampling))
1091         elif m.type == "BEZIER_CURVE":
1092             shaders_list.append(BezierCurveShader(
1093                 m.error))
1094         elif m.type == "SINUS_DISPLACEMENT":
1095             shaders_list.append(SinusDisplacementShader(
1096                 m.wavelength, m.amplitude, m.phase))
1097         elif m.type == "SPATIAL_NOISE":
1098             shaders_list.append(SpatialNoiseShader(
1099                 m.amplitude, m.scale, m.octaves, m.smooth, m.pure_random))
1100         elif m.type == "PERLIN_NOISE_1D":
1101             shaders_list.append(PerlinNoise1DShader(
1102                 m.frequency, m.amplitude, m.octaves, m.angle, _seed.get(m.seed)))
1103         elif m.type == "PERLIN_NOISE_2D":
1104             shaders_list.append(PerlinNoise2DShader(
1105                 m.frequency, m.amplitude, m.octaves, m.angle, _seed.get(m.seed)))
1106         elif m.type == "BACKBONE_STRETCHER":
1107             shaders_list.append(BackboneStretcherShader(
1108                 m.backbone_length))
1109         elif m.type == "TIP_REMOVER":
1110             shaders_list.append(TipRemoverShader(
1111                 m.tip_length))
1112         elif m.type == "POLYGONIZATION":
1113             shaders_list.append(PolygonalizationShader(
1114                 m.error))
1115         elif m.type == "GUIDING_LINES":
1116             shaders_list.append(GuidingLinesShader(
1117                 m.offset))
1118         elif m.type == "BLUEPRINT":
1119             if m.shape == "CIRCLES":
1120                 shaders_list.append(pyBluePrintCirclesShader(
1121                     m.rounds, m.random_radius, m.random_center))
1122             elif m.shape == "ELLIPSES":
1123                 shaders_list.append(pyBluePrintEllipsesShader(
1124                     m.rounds, m.random_radius, m.random_center))
1125             elif m.shape == "SQUARES":
1126                 shaders_list.append(pyBluePrintSquaresShader(
1127                     m.rounds, m.backbone_length, m.random_backbone))
1128         elif m.type == "2D_OFFSET":
1129             shaders_list.append(Offset2DShader(
1130                 m.start, m.end, m.x, m.y))
1131         elif m.type == "2D_TRANSFORM":
1132             shaders_list.append(Transform2DShader(
1133                 m.pivot, m.scale_x, m.scale_y, m.angle, m.pivot_u, m.pivot_x, m.pivot_y))
1134     color = linestyle.color
1135     shaders_list.append(ConstantColorShader(color.r, color.g, color.b, linestyle.alpha))
1136     shaders_list.append(ConstantThicknessShader(linestyle.thickness))
1137     for m in linestyle.color_modifiers:
1138         if not m.use:
1139             continue
1140         if m.type == "ALONG_STROKE":
1141             shaders_list.append(ColorAlongStrokeShader(
1142                 m.blend, m.influence, m.color_ramp))
1143         elif m.type == "DISTANCE_FROM_CAMERA":
1144             shaders_list.append(ColorDistanceFromCameraShader(
1145                 m.blend, m.influence, m.color_ramp,
1146                 m.range_min, m.range_max))
1147         elif m.type == "DISTANCE_FROM_OBJECT":
1148             shaders_list.append(ColorDistanceFromObjectShader(
1149                 m.blend, m.influence, m.color_ramp, m.target,
1150                 m.range_min, m.range_max))
1151         elif m.type == "MATERIAL":
1152             shaders_list.append(ColorMaterialShader(
1153                 m.blend, m.influence, m.color_ramp, m.material_attr,
1154                 m.use_ramp))
1155     for m in linestyle.alpha_modifiers:
1156         if not m.use:
1157             continue
1158         if m.type == "ALONG_STROKE":
1159             shaders_list.append(AlphaAlongStrokeShader(
1160                 m.blend, m.influence, m.mapping, m.invert, m.curve))
1161         elif m.type == "DISTANCE_FROM_CAMERA":
1162             shaders_list.append(AlphaDistanceFromCameraShader(
1163                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1164                 m.range_min, m.range_max))
1165         elif m.type == "DISTANCE_FROM_OBJECT":
1166             shaders_list.append(AlphaDistanceFromObjectShader(
1167                 m.blend, m.influence, m.mapping, m.invert, m.curve, m.target,
1168                 m.range_min, m.range_max))
1169         elif m.type == "MATERIAL":
1170             shaders_list.append(AlphaMaterialShader(
1171                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1172                 m.material_attr))
1173     for m in linestyle.thickness_modifiers:
1174         if not m.use:
1175             continue
1176         if m.type == "ALONG_STROKE":
1177             shaders_list.append(ThicknessAlongStrokeShader(
1178                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1179                 m.value_min, m.value_max))
1180         elif m.type == "DISTANCE_FROM_CAMERA":
1181             shaders_list.append(ThicknessDistanceFromCameraShader(
1182                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1183                 m.range_min, m.range_max, m.value_min, m.value_max))
1184         elif m.type == "DISTANCE_FROM_OBJECT":
1185             shaders_list.append(ThicknessDistanceFromObjectShader(
1186                 m.blend, m.influence, m.mapping, m.invert, m.curve, m.target,
1187                 m.range_min, m.range_max, m.value_min, m.value_max))
1188         elif m.type == "MATERIAL":
1189             shaders_list.append(ThicknessMaterialShader(
1190                 m.blend, m.influence, m.mapping, m.invert, m.curve,
1191                 m.material_attr, m.value_min, m.value_max))
1192         elif m.type == "CALLIGRAPHY":
1193             shaders_list.append(CalligraphicThicknessShader(
1194                 m.blend, m.influence,
1195                 m.orientation, m.min_thickness, m.max_thickness))
1196     if linestyle.caps == "ROUND":
1197         shaders_list.append(RoundCapShader())
1198     elif linestyle.caps == "SQUARE":
1199         shaders_list.append(SquareCapShader())
1200     # create strokes using the shaders list
1201     Operators.create(TrueUP1D(), shaders_list)