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

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 

6 

7 

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 

17 

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 

32 

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) 

39 

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) 

45 

46 def isnull(self): 

47 """! 

48 Whether the box is default-initialized 

49 """ 

50 return self.x1 is None or self.y2 is None 

51 

52 def __repr__(self): 

53 return "<BoundingBox [%s, %s] - [%s, %s]>" % (self.x1, self.y1, self.x2, self.y2) 

54 

55 @property 

56 def width(self): 

57 if self.isnull(): 

58 return 0 

59 return self.x2 - self.x1 

60 

61 @property 

62 def height(self): 

63 if self.isnull(): 

64 return 0 

65 return self.y2 - self.y1 

66 

67 def size(self): 

68 return NVector(self.width, self.height) 

69 

70 

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), 

83 

84 ] 

85 ## %Shape type. 

86 type = None 

87 _shape_classses = None 

88 

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 

101 

102 def bounding_box(self, time=0): 

103 """! 

104 Bounding box of the shape element at the given time 

105 """ 

106 return BoundingBox() 

107 

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"]] 

114 

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) 

121 

122 def __str__(self): 

123 return self.name or super().__str__() 

124 

125 

126#ingroup Lottie 

127class ShapeDirection(LottieEnum): 

128 Undefined = 0 

129 ## Usually clockwise 

130 Normal = 1 

131 ## Usually counter clockwise 

132 Reversed = 3 

133 

134 

135## @ingroup Lottie 

136class Shape(ShapeElement): 

137 """! 

138 Drawable shape 

139 """ 

140 _props = [ 

141 LottieProp("direction", "d", ShapeDirection, False), 

142 ] 

143 

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 

148 

149 def to_bezier(self): 

150 """! 

151 Returns a Path corresponding to this Shape 

152 """ 

153 raise NotImplementedError() 

154 

155 

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" 

168 

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) 

177 

178 def bounding_box(self, time=0): 

179 pos = self.position.get_value(time) 

180 sz = self.size.get_value(time) 

181 

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 ) 

188 

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 

207 

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) 

216 

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) 

235 

236 bezier.close() 

237 return bezier 

238 

239 

240## @ingroup Lottie 

241class StarType(LottieEnum): 

242 Star = 1 

243 Polygon = 2 

244 

245 

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" 

263 

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 

282 

283 def bounding_box(self, time=0): 

284 pos = self.position.get_value(time) 

285 r = self.outer_radius.get_value(time) 

286 

287 return BoundingBox( 

288 pos[0] - r, 

289 pos[1] - r, 

290 pos[0] + r, 

291 pos[1] + r, 

292 ) 

293 

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 

317 

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 

326 

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)) 

332 

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)) 

337 

338 bezier.close() 

339 return bezier 

340 

341 

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" 

353 

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)) 

360 

361 def bounding_box(self, time=0): 

362 pos = self.position.get_value(time) 

363 sz = self.size.get_value(time) 

364 

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 ) 

371 

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 

388 

389 def _bezier_t(self, time): 

390 from ..utils.ellipse import Ellipse as EllipseConverter 

391 

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 

398 

399 

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" 

411 

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 

418 

419 def bounding_box(self, time=0): 

420 pos = self.shape.get_value(time) 

421 

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)) 

427 

428 return bb 

429 

430 def to_bezier(self): 

431 return self.clone() 

432 

433 

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" 

446 

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()] 

453 

454 @property 

455 def transform(self): 

456 return self.shapes[-1] 

457 

458 def bounding_box(self, time=0): 

459 bb = BoundingBox() 

460 for v in self.shapes: 

461 bb.expand(v.bounding_box(time)) 

462 

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 

477 

478 def add_shape(self, shape): 

479 self.shapes.insert(-1, shape) 

480 return shape 

481 

482 def insert_shape(self, index, shape): 

483 self.shapes.insert(index, shape) 

484 return shape 

485 

486 @classmethod 

487 def load(cls, lottiedict): 

488 object = ShapeElement.load(lottiedict) 

489 

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) 

498 

499 object.shapes = shapes 

500 object.shapes.append(transform) 

501 return object 

502 

503 

504## @ingroup Lottie 

505class FillRule(LottieEnum): 

506 NonZero = 1 

507 EvenOdd = 2 

508 

509 

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" 

522 

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 

531 

532 

533## @ingroup Lottie 

534class GradientType(LottieEnum): 

535 Linear = 1 

536 Radial = 2 

537 

538 

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 ] 

549 

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) 

565 

566 

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" 

578 

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 

586 

587 

588## @ingroup Lottie 

589class LineJoin(LottieEnum): 

590 Miter = 1 

591 Round = 2 

592 Bevel = 3 

593 

594 

595## @ingroup Lottie 

596class LineCap(LottieEnum): 

597 Butt = 1 

598 Round = 2 

599 Square = 3 

600 

601 

602## @ingroup Lottie 

603class StrokeDashType(LottieEnum): 

604 Dash = "d" 

605 Gap = "g" 

606 Offset = "o" 

607 

608 

609## @ingroup Lottie 

610class StrokeDash(VisualObject): 

611 _props = [ 

612 LottieProp("type", "n", StrokeDashType, False), 

613 LottieProp("length", "v", Value, False), 

614 ] 

615 

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) 

621 

622 def __str__(self): 

623 return self.name or super().__str__() 

624 

625 

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 ] 

637 

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 

653 

654 

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" 

665 

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)) 

671 

672 

673## @ingroup Lottie 

674class GradientStroke(ShapeElement, BaseStroke, Gradient): 

675 """! 

676 Gradient stroke 

677 """ 

678 ## %Shape type. 

679 type = "gs" 

680 

681 def __init__(self, stroke_width=1): 

682 ShapeElement.__init__(self) 

683 BaseStroke.__init__(self, stroke_width) 

684 Gradient.__init__(self) 

685 

686 def bounding_box(self, time=0): 

687 return BoundingBox() 

688 

689 

690## @ingroup Lottie 

691class TransformShape(ShapeElement, Transform): 

692 """! 

693 Group transform 

694 """ 

695 ## %Shape type. 

696 type = "tr" 

697 

698 def __init__(self): 

699 ShapeElement.__init__(self) 

700 Transform.__init__(self) 

701 self.anchor_point = MultiDimensional(NVector(0, 0)) 

702 

703 

704## @ingroup Lottie 

705class Composite(LottieEnum): 

706 Above = 1 

707 Below = 2 

708 

709 

710## @ingroup Lottie 

711class RepeaterTransform(Transform): 

712 _props = [ 

713 LottieProp("start_opacity", "so", Value, False), 

714 LottieProp("end_opacity", "eo", Value, False), 

715 ] 

716 

717 def __init__(self): 

718 Transform.__init__(self) 

719 self.start_opacity = Value(100) 

720 self.end_opacity = Value(100) 

721 

722 

723## @ingroup Lottie 

724class Modifier(ShapeElement): 

725 pass 

726 

727 

728## @ingroup Lottie 

729class TrimMultipleShapes(LottieEnum): 

730 Individually = 1 

731 Simultaneously = 2 

732 

733 

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" 

748 

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 

759 

760 

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" 

774 

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() 

785 

786 

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" 

798 

799 def __init__(self, radius=0): 

800 Modifier.__init__(self) 

801 ## Rounded Corner Radius 

802 self.radius = Value(radius) 

803 

804 

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 

815 

816 

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" 

825 

826 def __init__(self): 

827 ShapeElement.__init__(self) 

828 ## Merge Mode 

829 self.merge_mode = MergeMode.Normal 

830 

831 

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" 

841 

842 def __init__(self): 

843 ShapeElement.__init__(self) 

844 self.angle = Value(0) 

845 self.center = MultiDimensional(NVector(0, 0)) 

846 

847 

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" 

858 

859 def __init__(self): 

860 ShapeElement.__init__(self) 

861 self.amount = Value(0) 

862 

863 

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 ] 

875 

876 def __init__(self): 

877 super().__init__() 

878 

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 

886 

887 

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" 

901 

902 def __init__(self): 

903 super().__init__() 

904 

905 self.amount = Value(0) 

906 self.line_join = LineJoin.Round 

907 self.miter_limit = Value(0)