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