Files
2026-03-01 21:36:54 +08:00

121 lines
4.1 KiB
Python

from xml.dom import minidom
import enum
from lottie.parsers.sif.sif.core import TypeDescriptor, ObjectRegistry, SifNodeMeta, FrameTime
from lottie.parsers.sif.xml.utils import xml_child_elements, xml_first_element_child
class SifAstNode:
_subclasses = None
_tag = None
@staticmethod
def from_dom(xml: minidom.Element, param: TypeDescriptor, registry: ObjectRegistry):
if xml.tagName == param.typename:
return SifValue.from_dom(xml, param, registry)
xmltype = xml.getAttribute("type")
if xmltype != param.typename and xmltype != "weighted_" + param.typename:
raise ValueError("Invalid type %s (should be %s)" % (xmltype, param.typename))
return SifAstNode.ast_node_types()[xml.tagName].from_dom(xml, param, registry)
@staticmethod
def ast_node_types():
if SifAstNode._subclasses is None:
from . import nodes
SifAstNode._subclasses = {}
SifAstNode._gather_ast_types(SifAstNode)
return SifAstNode._subclasses
@staticmethod
def _gather_ast_types(cls):
for subcls in cls.__subclasses__():
if subcls._tag:
SifAstNode._subclasses[subcls._tag] = subcls
SifAstNode._gather_ast_types(subcls)
def _prepare_to_dom(self, dom: minidom.Document, param: TypeDescriptor):
element = dom.createElement(self._tag)
element.setAttribute("type", param.typename)
return element
class SifValue(SifAstNode):
def __init__(self, value=None):
self.value = value
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.value)
@classmethod
def from_dom(cls, xml: minidom.Element, param: TypeDescriptor, registry: ObjectRegistry):
return SifValue(param.value_from_xml_element(xml, registry))
def to_dom(self, dom: minidom.Document, param: TypeDescriptor):
return param.value_to_xml_element(self.value, dom)
class Interpolation(enum.Enum):
Auto = "auto"
Linear = "linear"
Clamped = "clamped"
Ease = "halt"
Constant = "constant"
class SifKeyframe:
def __init__(self, value, time: FrameTime, before=Interpolation.Clamped, after=Interpolation.Clamped):
self.value = value
self.time = time
self.before = before
self.after = after
@classmethod
def from_dom(cls, xml: minidom.Element, param: TypeDescriptor, registry: ObjectRegistry):
return cls(
param.value_from_xml_element(xml_first_element_child(xml), registry),
FrameTime.parse_string(xml.getAttribute("time"), registry),
Interpolation(xml.getAttribute("before")),
Interpolation(xml.getAttribute("after"))
)
def to_dom(self, dom: minidom.Document, param: TypeDescriptor):
element = dom.createElement("waypoint")
element.setAttribute("time", str(self.time))
element.setAttribute("before", self.before.value)
element.setAttribute("after", self.after.value)
element.appendChild(param.value_to_xml_element(self.value, dom))
return element
def __repr__(self):
return "<SifKeyframe %s %s>" % (self.time, self.value)
class SifAnimated(SifAstNode):
_tag = "animated"
def __init__(self):
self.keyframes = []
@classmethod
def from_dom(cls, xml: minidom.Element, param: TypeDescriptor, registry: ObjectRegistry):
obj = SifAnimated()
for waypoint in xml_child_elements(xml, "waypoint"):
obj.keyframes.append(SifKeyframe.from_dom(waypoint, param, registry))
return obj
def to_dom(self, dom: minidom.Document, param: TypeDescriptor):
element = self._prepare_to_dom(dom, param)
for kf in self.keyframes:
element.appendChild(kf.to_dom(dom, param))
return element
def add_keyframe(self, *args, **kwargs):
if not kwargs and len(args) == 1 and isinstance(args[0], SifKeyframe):
keyframe = args[0]
else:
keyframe = SifKeyframe(*args, **kwargs)
self.keyframes.append(keyframe)
return keyframe