Coverage for lib/lottie/objects/shapes.py: 73%
438 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1import math
2from .base import LottieObject, LottieProp, LottieEnum, NVector
3from .properties import Value, MultiDimensional, GradientColors, ShapeProperty, Bezier, ColorValue, PositionValue
4from ..utils.color import Color
5from .helpers import Transform, VisualObject, BlendMode
8class BoundingBox:
9 """!
10 Shape bounding box
11 """
12 def __init__(self, x1=None, y1=None, x2=None, y2=None):
13 self.x1 = x1
14 self.y1 = y1
15 self.x2 = x2
16 self.y2 = y2
18 def include(self, x, y):
19 """!
20 Expands the box to include the point at x, y
21 """
22 if x is not None:
23 if self.x1 is None or self.x1 > x:
24 self.x1 = x
25 if self.x2 is None or self.x2 < x:
26 self.x2 = x
27 if y is not None:
28 if self.y1 is None or self.y1 > y:
29 self.y1 = y
30 if self.y2 is None or self.y2 < y:
31 self.y2 = y
33 def expand(self, other):
34 """!
35 Expands the bounding box to include another bounding box
36 """
37 self.include(other.x1, other.y1)
38 self.include(other.x2, other.y2)
40 def center(self):
41 """!
42 Center point of the bounding box
43 """
44 return NVector((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
46 def isnull(self):
47 """!
48 Whether the box is default-initialized
49 """
50 return self.x1 is None or self.y2 is None
52 def __repr__(self):
53 return "<BoundingBox [%s, %s] - [%s, %s]>" % (self.x1, self.y1, self.x2, self.y2)
55 @property
56 def width(self):
57 if self.isnull():
58 return 0
59 return self.x2 - self.x1
61 @property
62 def height(self):
63 if self.isnull():
64 return 0
65 return self.y2 - self.y1
67 def size(self):
68 return NVector(self.width, self.height)
71## @ingroup Lottie
72class ShapeElement(VisualObject):
73 """!
74 Base class for all elements of ShapeLayer and Group
75 """
76 _props = [
77 LottieProp("hidden", "hd", bool, False),
78 LottieProp("type", "ty", str, False),
79 LottieProp("blend_mode", "bm", BlendMode, False),
80 LottieProp("property_index", "ix", int, False),
81 LottieProp("css_class", "cl", str, False),
82 LottieProp("layer_xml_id", "ln", str, False),
84 ]
85 ## %Shape type.
86 type = None
87 _shape_classses = None
89 def __init__(self):
90 super().__init__()
91 ## Property index
92 self.property_index = None
93 ## Hide element
94 self.hidden = None
95 ## Blend mode
96 self.blend_mode = None
97 ## CSS class used by the SVG renderer
98 self.css_class = None
99 ## `id` attribute used by the SVG renderer
100 self.layer_xml_id = None
102 def bounding_box(self, time=0):
103 """!
104 Bounding box of the shape element at the given time
105 """
106 return BoundingBox()
108 @classmethod
109 def _load_get_class(cls, lottiedict):
110 if not ShapeElement._shape_classses:
111 ShapeElement._shape_classses = {}
112 ShapeElement._load_sub(ShapeElement._shape_classses)
113 return ShapeElement._shape_classses[lottiedict["ty"]]
115 @classmethod
116 def _load_sub(cls, dict):
117 for sc in cls.__subclasses__():
118 if sc.type and sc.type not in dict:
119 dict[sc.type] = sc
120 sc._load_sub(dict)
122 def __str__(self):
123 return self.name or super().__str__()
126#ingroup Lottie
127class ShapeDirection(LottieEnum):
128 Undefined = 0
129 ## Usually clockwise
130 Normal = 1
131 ## Usually counter clockwise
132 Reversed = 3
135## @ingroup Lottie
136class Shape(ShapeElement):
137 """!
138 Drawable shape
139 """
140 _props = [
141 LottieProp("direction", "d", ShapeDirection, False),
142 ]
144 def __init__(self):
145 ShapeElement.__init__(self)
146 ## After Effect's Direction. Direction how the shape is drawn. Used for trim path for example.
147 self.direction = ShapeDirection.Normal
149 def to_bezier(self):
150 """!
151 Returns a Path corresponding to this Shape
152 """
153 raise NotImplementedError()
156## @ingroup Lottie
157class Rect(Shape):
158 """!
159 A simple rectangle shape
160 """
161 _props = [
162 LottieProp("position", "p", PositionValue, False),
163 LottieProp("size", "s", MultiDimensional, False),
164 LottieProp("rounded", "r", Value, False),
165 ]
166 ## %Shape type.
167 type = "rc"
169 def __init__(self, pos=None, size=None, rounded=0):
170 Shape.__init__(self)
171 ## Rect's position
172 self.position = PositionValue(pos or NVector(0, 0))
173 ## Rect's size
174 self.size = MultiDimensional(size or NVector(0, 0))
175 ## Rect's rounded corners
176 self.rounded = Value(rounded)
178 def bounding_box(self, time=0):
179 pos = self.position.get_value(time)
180 sz = self.size.get_value(time)
182 return BoundingBox(
183 pos[0] - sz[0]/2,
184 pos[1] - sz[1]/2,
185 pos[0] + sz[0]/2,
186 pos[1] + sz[1]/2,
187 )
189 def to_bezier(self):
190 """!
191 Returns a Shape corresponding to this rect
192 """
193 shape = Path()
194 kft = set()
195 if self.position.animated: 195 ↛ 196line 195 didn't jump to line 196, because the condition on line 195 was never true
196 kft |= set(kf.time for kf in self.position.keyframes)
197 if self.size.animated: 197 ↛ 198line 197 didn't jump to line 198, because the condition on line 197 was never true
198 kft |= set(kf.time for kf in self.size.keyframes)
199 if self.rounded.animated: 199 ↛ 200line 199 didn't jump to line 200, because the condition on line 199 was never true
200 kft |= set(kf.time for kf in self.rounded.keyframes)
201 if not kft: 201 ↛ 204line 201 didn't jump to line 204, because the condition on line 201 was never false
202 shape.shape.value = self._bezier_t(0)
203 else:
204 for time in sorted(kft):
205 shape.shape.add_keyframe(time, self._bezier_t(time))
206 return shape
208 def _bezier_t(self, time):
209 bezier = Bezier()
210 bb = self.bounding_box(time)
211 rounded = self.rounded.get_value(time)
212 tl = NVector(bb.x1, bb.y1)
213 tr = NVector(bb.x2, bb.y1)
214 br = NVector(bb.x2, bb.y2)
215 bl = NVector(bb.x1, bb.y2)
217 if not self.rounded.animated and rounded == 0: 217 ↛ 223line 217 didn't jump to line 223, because the condition on line 217 was never false
218 bezier.add_point(tl)
219 bezier.add_point(tr)
220 bezier.add_point(br)
221 bezier.add_point(bl)
222 else:
223 hh = NVector(rounded/2, 0)
224 vh = NVector(0, rounded/2)
225 hd = NVector(rounded, 0)
226 vd = NVector(0, rounded)
227 bezier.add_point(tl+vd, outp=-vh)
228 bezier.add_point(tl+hd, -hh)
229 bezier.add_point(tr-hd, outp=hh)
230 bezier.add_point(tr+vd, -vh)
231 bezier.add_point(br-vd, outp=vh)
232 bezier.add_point(br-hd, hh)
233 bezier.add_point(bl+hd, outp=-hh)
234 bezier.add_point(bl-vd, vh)
236 bezier.close()
237 return bezier
240## @ingroup Lottie
241class StarType(LottieEnum):
242 Star = 1
243 Polygon = 2
246## @ingroup Lottie
247class Star(Shape):
248 """!
249 Star shape
250 """
251 _props = [
252 LottieProp("position", "p", PositionValue, False),
253 LottieProp("inner_radius", "ir", Value, False),
254 LottieProp("inner_roundness", "is", Value, False),
255 LottieProp("outer_radius", "or", Value, False),
256 LottieProp("outer_roundness", "os", Value, False),
257 LottieProp("rotation", "r", Value, False),
258 LottieProp("points", "pt", Value, False),
259 LottieProp("star_type", "sy", StarType, False),
260 ]
261 ## %Shape type.
262 type = "sr"
264 def __init__(self):
265 Shape.__init__(self)
266 ## Star's position
267 self.position = PositionValue(NVector(0, 0))
268 ## Star's inner radius. (Star only)
269 self.inner_radius = Value()
270 ## Star's inner roundness. (Star only)
271 self.inner_roundness = Value()
272 ## Star's outer radius.
273 self.outer_radius = Value()
274 ## Star's outer roundness.
275 self.outer_roundness = Value()
276 ## Star's rotation.
277 self.rotation = Value()
278 ## Star's number of points.
279 self.points = Value(5)
280 ## Star's type. Polygon or Star.
281 self.star_type = StarType.Star
283 def bounding_box(self, time=0):
284 pos = self.position.get_value(time)
285 r = self.outer_radius.get_value(time)
287 return BoundingBox(
288 pos[0] - r,
289 pos[1] - r,
290 pos[0] + r,
291 pos[1] + r,
292 )
294 def to_bezier(self):
295 """!
296 Returns a Shape corresponding to this star
297 """
298 shape = Path()
299 kft = set()
300 if self.position.animated: 300 ↛ 301line 300 didn't jump to line 301, because the condition on line 300 was never true
301 kft |= set(kf.time for kf in self.position.keyframes)
302 if self.inner_radius.animated: 302 ↛ 303line 302 didn't jump to line 303, because the condition on line 302 was never true
303 kft |= set(kf.time for kf in self.inner_radius.keyframes)
304 if self.inner_roundness.animated: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true
305 kft |= set(kf.time for kf in self.inner_roundness.keyframes)
306 if self.points.animated: 306 ↛ 307line 306 didn't jump to line 307, because the condition on line 306 was never true
307 kft |= set(kf.time for kf in self.points.keyframes)
308 if self.rotation.animated: 308 ↛ 309line 308 didn't jump to line 309, because the condition on line 308 was never true
309 kft |= set(kf.time for kf in self.rotation.keyframes)
310 # TODO inner_roundness / outer_roundness
311 if not kft: 311 ↛ 314line 311 didn't jump to line 314, because the condition on line 311 was never false
312 shape.shape.value = self._bezier_t(0)
313 else:
314 for time in sorted(kft):
315 shape.shape.add_keyframe(time, self._bezier_t(time))
316 return shape
318 def _bezier_t(self, time):
319 bezier = Bezier()
320 pos = self.position.get_value(time)
321 r1 = self.inner_radius.get_value(time)
322 r2 = self.outer_radius.get_value(time)
323 rot = -(self.rotation.get_value(time)) * math.pi / 180 + math.pi
324 p = self.points.get_value(time)
325 halfd = -math.pi / p
327 for i in range(int(p)):
328 main_angle = rot + i * halfd * 2
329 dx = r2 * math.sin(main_angle)
330 dy = r2 * math.cos(main_angle)
331 bezier.add_point(NVector(pos.x + dx, pos.y + dy))
333 if self.star_type == StarType.Star: 333 ↛ 327line 333 didn't jump to line 327, because the condition on line 333 was never false
334 dx = r1 * math.sin(main_angle+halfd)
335 dy = r1 * math.cos(main_angle+halfd)
336 bezier.add_point(NVector(pos.x + dx, pos.y + dy))
338 bezier.close()
339 return bezier
342## @ingroup Lottie
343class Ellipse(Shape):
344 """!
345 Ellipse shape
346 """
347 _props = [
348 LottieProp("position", "p", PositionValue, False),
349 LottieProp("size", "s", MultiDimensional, False),
350 ]
351 ## %Shape type.
352 type = "el"
354 def __init__(self, position=None, size=None):
355 Shape.__init__(self)
356 ## Ellipse's position
357 self.position = MultiDimensional(position or NVector(0, 0))
358 ## Ellipse's size
359 self.size = MultiDimensional(size or NVector(0, 0))
361 def bounding_box(self, time=0):
362 pos = self.position.get_value(time)
363 sz = self.size.get_value(time)
365 return BoundingBox(
366 pos[0] - sz[0]/2,
367 pos[1] - sz[1]/2,
368 pos[0] + sz[0]/2,
369 pos[1] + sz[1]/2,
370 )
372 def to_bezier(self):
373 """!
374 Returns a Shape corresponding to this ellipse
375 """
376 shape = Path()
377 kft = set()
378 if self.position.animated: 378 ↛ 379line 378 didn't jump to line 379, because the condition on line 378 was never true
379 kft |= set(kf.time for kf in self.position.keyframes)
380 if self.size.animated: 380 ↛ 381line 380 didn't jump to line 381, because the condition on line 380 was never true
381 kft |= set(kf.time for kf in self.size.keyframes)
382 if not kft: 382 ↛ 385line 382 didn't jump to line 385, because the condition on line 382 was never false
383 shape.shape.value = self._bezier_t(0)
384 else:
385 for time in sorted(kft):
386 shape.shape.add_keyframe(time, self._bezier_t(time))
387 return shape
389 def _bezier_t(self, time):
390 from ..utils.ellipse import Ellipse as EllipseConverter
392 position = self.position.get_value(time)
393 radii = self.size.get_value(time) / 2
394 el = EllipseConverter(position, radii, 0)
395 bezier = el.to_bezier(0, math.pi*2)
396 bezier.close()
397 return bezier
400## @ingroup Lottie
401class Path(Shape):
402 """!
403 Animatable Bezier curve
404 """
405 _props = [
406 LottieProp("shape", "ks", ShapeProperty, False),
407 LottieProp("index", "ind", int, False),
408 ]
409 ## %Shape type.
410 type = "sh"
412 def __init__(self, bezier=None):
413 Shape.__init__(self)
414 ## Shape's vertices
415 self.shape = ShapeProperty(bezier or Bezier())
416 ## @todo Index?
417 self.index = None
419 def bounding_box(self, time=0):
420 pos = self.shape.get_value(time)
422 bb = BoundingBox()
423 for v, i, o in zip(pos.vertices, pos.in_tangents, pos.out_tangents):
424 bb.include(*v)
425 bb.include(*(v+i))
426 bb.include(*(v+o))
428 return bb
430 def to_bezier(self):
431 return self.clone()
434## @ingroup Lottie
435class Group(ShapeElement):
436 """!
437 ShapeElement that can contain other shapes
438 @note Shapes inside the same group will create "holes" in other shapes
439 """
440 _props = [
441 LottieProp("number_of_properties", "np", float, False),
442 LottieProp("shapes", "it", ShapeElement, True),
443 ]
444 ## %Shape type.
445 type = "gr"
447 def __init__(self):
448 ShapeElement.__init__(self)
449 ## Group number of properties. Used for expressions.
450 self.number_of_properties = None
451 ## Group list of items
452 self.shapes = [TransformShape()]
454 @property
455 def transform(self):
456 return self.shapes[-1]
458 def bounding_box(self, time=0):
459 bb = BoundingBox()
460 for v in self.shapes:
461 bb.expand(v.bounding_box(time))
463 if not bb.isnull(): 463 ↛ 476line 463 didn't jump to line 476, because the condition on line 463 was never false
464 mat = self.transform.to_matrix(time)
465 points = [
466 mat.apply(NVector(bb.x1, bb.y1)),
467 mat.apply(NVector(bb.x1, bb.y2)),
468 mat.apply(NVector(bb.x2, bb.y2)),
469 mat.apply(NVector(bb.x2, bb.y1)),
470 ]
471 x1 = min(p.x for p in points)
472 x2 = max(p.x for p in points)
473 y1 = min(p.y for p in points)
474 y2 = max(p.y for p in points)
475 return BoundingBox(x1, y1, x2, y2)
476 return bb
478 def add_shape(self, shape):
479 self.shapes.insert(-1, shape)
480 return shape
482 def insert_shape(self, index, shape):
483 self.shapes.insert(index, shape)
484 return shape
486 @classmethod
487 def load(cls, lottiedict):
488 object = ShapeElement.load(lottiedict)
490 shapes = []
491 transform = None
492 for obj in object.shapes:
493 if isinstance(obj, TransformShape):
494 if not transform:
495 transform = obj
496 else:
497 shapes.append(obj)
499 object.shapes = shapes
500 object.shapes.append(transform)
501 return object
504## @ingroup Lottie
505class FillRule(LottieEnum):
506 NonZero = 1
507 EvenOdd = 2
510## @ingroup Lottie
511class Fill(ShapeElement):
512 """!
513 Solid fill color
514 """
515 _props = [
516 LottieProp("opacity", "o", Value, False),
517 LottieProp("color", "c", ColorValue, False),
518 LottieProp("fill_rule", "r", FillRule, False),
519 ]
520 ## %Shape type.
521 type = "fl"
523 def __init__(self, color=None):
524 ShapeElement.__init__(self)
525 ## Fill Opacity
526 self.opacity = Value(100)
527 ## Fill Color
528 self.color = ColorValue(color or Color(1, 1, 1))
529 ## Fill rule
530 self.fill_rule = None
533## @ingroup Lottie
534class GradientType(LottieEnum):
535 Linear = 1
536 Radial = 2
539## @ingroup Lottie
540class Gradient(LottieObject):
541 _props = [
542 LottieProp("start_point", "s", MultiDimensional, False),
543 LottieProp("end_point", "e", MultiDimensional, False),
544 LottieProp("gradient_type", "t", GradientType, False),
545 LottieProp("highlight_length", "h", Value, False),
546 LottieProp("highlight_angle", "a", Value, False),
547 LottieProp("colors", "g", GradientColors, False),
548 ]
550 def __init__(self, colors=[]):
551 ## Fill Opacity
552 self.opacity = Value(100)
553 ## Gradient Start Point
554 self.start_point = MultiDimensional(NVector(0, 0))
555 ## Gradient End Point
556 self.end_point = MultiDimensional(NVector(0, 0))
557 ## Gradient Type
558 self.gradient_type = GradientType.Linear
559 ## Gradient Highlight Length. Only if type is Radial
560 self.highlight_length = Value()
561 ## Highlight Angle. Only if type is Radial
562 self.highlight_angle = Value()
563 ## Gradient Colors
564 self.colors = GradientColors(colors)
567## @ingroup Lottie
568class GradientFill(ShapeElement, Gradient):
569 """!
570 Gradient fill
571 """
572 _props = [
573 LottieProp("opacity", "o", Value, False),
574 LottieProp("fill_rule", "r", FillRule, False),
575 ]
576 ## %Shape type.
577 type = "gf"
579 def __init__(self, colors=[]):
580 ShapeElement.__init__(self)
581 Gradient.__init__(self, colors)
582 ## Fill Opacity
583 self.opacity = Value(100)
584 ## Fill rule
585 self.fill_rule = None
588## @ingroup Lottie
589class LineJoin(LottieEnum):
590 Miter = 1
591 Round = 2
592 Bevel = 3
595## @ingroup Lottie
596class LineCap(LottieEnum):
597 Butt = 1
598 Round = 2
599 Square = 3
602## @ingroup Lottie
603class StrokeDashType(LottieEnum):
604 Dash = "d"
605 Gap = "g"
606 Offset = "o"
609## @ingroup Lottie
610class StrokeDash(VisualObject):
611 _props = [
612 LottieProp("type", "n", StrokeDashType, False),
613 LottieProp("length", "v", Value, False),
614 ]
616 def __init__(self, length=0, type=StrokeDashType.Dash):
617 super().__init__()
618 self.name = type.name.lower()
619 self.type = type
620 self.length = Value(length)
622 def __str__(self):
623 return self.name or super().__str__()
626## @ingroup Lottie
627class BaseStroke(LottieObject):
628 _props = [
629 LottieProp("line_cap", "lc", LineCap, False),
630 LottieProp("line_join", "lj", LineJoin, False),
631 LottieProp("miter_limit", "ml", float, False),
632 LottieProp("animated_miter_limit", "ml2", Value, False),
633 LottieProp("opacity", "o", Value, False),
634 LottieProp("width", "w", Value, False),
635 LottieProp("dashes", "d", StrokeDash, True),
636 ]
638 def __init__(self, width=1):
639 ## Stroke Line Cap
640 self.line_cap = LineCap.Round
641 ## Stroke Line Join
642 self.line_join = LineJoin.Round
643 ## Stroke Miter Limit. Only if Line Join is set to Miter.
644 self.miter_limit = 0
645 ## Animatable alternative to ml
646 self.animated_miter_limit = None
647 ## Stroke Opacity
648 self.opacity = Value(100)
649 ## Stroke Width
650 self.width = Value(width)
651 ## Dashes
652 self.dashes = None
655## @ingroup Lottie
656class Stroke(ShapeElement, BaseStroke):
657 """!
658 Solid stroke
659 """
660 _props = [
661 LottieProp("color", "c", ColorValue, False),
662 ]
663 ## %Shape type.
664 type = "st"
666 def __init__(self, color=None, width=1):
667 ShapeElement.__init__(self)
668 BaseStroke.__init__(self, width)
669 ## Stroke Color
670 self.color = ColorValue(color or Color(0, 0, 0))
673## @ingroup Lottie
674class GradientStroke(ShapeElement, BaseStroke, Gradient):
675 """!
676 Gradient stroke
677 """
678 ## %Shape type.
679 type = "gs"
681 def __init__(self, stroke_width=1):
682 ShapeElement.__init__(self)
683 BaseStroke.__init__(self, stroke_width)
684 Gradient.__init__(self)
686 def bounding_box(self, time=0):
687 return BoundingBox()
690## @ingroup Lottie
691class TransformShape(ShapeElement, Transform):
692 """!
693 Group transform
694 """
695 ## %Shape type.
696 type = "tr"
698 def __init__(self):
699 ShapeElement.__init__(self)
700 Transform.__init__(self)
701 self.anchor_point = MultiDimensional(NVector(0, 0))
704## @ingroup Lottie
705class Composite(LottieEnum):
706 Above = 1
707 Below = 2
710## @ingroup Lottie
711class RepeaterTransform(Transform):
712 _props = [
713 LottieProp("start_opacity", "so", Value, False),
714 LottieProp("end_opacity", "eo", Value, False),
715 ]
717 def __init__(self):
718 Transform.__init__(self)
719 self.start_opacity = Value(100)
720 self.end_opacity = Value(100)
723## @ingroup Lottie
724class Modifier(ShapeElement):
725 pass
728## @ingroup Lottie
729class TrimMultipleShapes(LottieEnum):
730 Individually = 1
731 Simultaneously = 2
734## @ingroup Lottie
735## @todo Implement SIF Export
736class Trim(Modifier):
737 """
738 Trims shapes into a segment
739 """
740 _props = [
741 LottieProp("start", "s", Value, False),
742 LottieProp("end", "e", Value, False),
743 LottieProp("offset", "o", Value, False),
744 LottieProp("multiple", "m", TrimMultipleShapes, False),
745 ]
746 ## %Shape type.
747 type = "tm"
749 def __init__(self):
750 ShapeElement.__init__(self)
751 ## Start of the segment, as a percentage
752 self.start = Value(0)
753 ## End of the segment, as a percentage
754 self.end = Value(100)
755 ## start/end offset, as an angle (0, 360)
756 self.offset = Value(0)
757 ## @todo?
758 self.multiple = None
761## @ingroup Lottie
762class Repeater(Modifier):
763 """
764 Duplicates previous shapes in a group
765 """
766 _props = [
767 LottieProp("copies", "c", Value, False),
768 LottieProp("offset", "o", Value, False),
769 LottieProp("composite", "m", Composite, False),
770 LottieProp("transform", "tr", RepeaterTransform, False),
771 ]
772 ## %Shape type.
773 type = "rp"
775 def __init__(self, copies=1):
776 Modifier.__init__(self)
777 ## Number of Copies
778 self.copies = Value(copies)
779 ## Offset of Copies
780 self.offset = Value()
781 ## Composite of copies
782 self.composite = Composite.Above
783 ## Transform values for each repeater copy
784 self.transform = RepeaterTransform()
787## @ingroup Lottie
788## @todo Implement SIF Export
789class RoundedCorners(Modifier):
790 """
791 Rounds corners of other shapes
792 """
793 _props = [
794 LottieProp("radius", "r", Value, False),
795 ]
796 ## %Shape type.
797 type = "rd"
799 def __init__(self, radius=0):
800 Modifier.__init__(self)
801 ## Rounded Corner Radius
802 self.radius = Value(radius)
805#ingroup Lottie
806class MergeMode(LottieEnum):
807 """!
808 Boolean operation on shapes
809 """
810 Normal = 1
811 Add = 2
812 Subtract = 3
813 Intersect = 4
814 ExcludeIntersections = 5
817## @ingroup Lottie
818## @note marked as unsupported by lottie
819class Merge(ShapeElement):
820 _props = [
821 LottieProp("merge_mode", "mm", MergeMode, False),
822 ]
823 ## %Shape type.
824 type = "mm"
826 def __init__(self):
827 ShapeElement.__init__(self)
828 ## Merge Mode
829 self.merge_mode = MergeMode.Normal
832## @ingroup Lottie
833## @note marked as unsupported by lottie
834class Twist(ShapeElement):
835 _props = [
836 LottieProp("angle", "a", Value, False),
837 LottieProp("center", "c", MultiDimensional, False),
838 ]
839 ## %Shape type.
840 type = "tw"
842 def __init__(self):
843 ShapeElement.__init__(self)
844 self.angle = Value(0)
845 self.center = MultiDimensional(NVector(0, 0))
848## @ingroup Lottie
849class PuckerBloat(ShapeElement):
850 """
851 Interpolates the shape with its center point and bezier tangents with the opposite direction
852 """
853 _props = [
854 LottieProp("amount", "a", Value, False),
855 ]
856 ## %Shape type.
857 type = "pb"
859 def __init__(self):
860 ShapeElement.__init__(self)
861 self.amount = Value(0)
864#ingroup Lottie
865class ZigZag(ShapeElement):
866 """!
867 Changes the edges of affected shapes into a series of peaks and valleys of uniform size
868 """
869 _props = [
870 LottieProp("shape_type", "ty", str, False),
871 LottieProp("frequency", "r", Value, False),
872 LottieProp("amplitude", "s", Value, False),
873 LottieProp("point_type", "pt", Value, False),
874 ]
876 def __init__(self):
877 super().__init__()
879 self.shape_type = "zz"
880 ## Number of ridges per segment
881 self.frequency = None
882 ## Distance between peaks and troughs
883 self.amplitude = None
884 ## Point type (1 = corner, 2 = smooth)
885 self.point_type = None
888#ingroup Lottie
889class OffsetPath(ShapeElement):
890 """!
891 Interpolates the shape with its center point and bezier tangents with the opposite direction
892 """
893 _props = [
894 LottieProp("shape_type", "ty", str, False),
895 LottieProp("amount", "a", Value, False),
896 LottieProp("line_join", "lj", LineJoin, False),
897 LottieProp("miter_limit", "ml", Value, False),
898 ]
899 ## %Shape type.
900 type = "op"
902 def __init__(self):
903 super().__init__()
905 self.amount = Value(0)
906 self.line_join = LineJoin.Round
907 self.miter_limit = Value(0)