File indexing completed on 2025-01-19 03:59:52
0001 import enum 0002 import inspect 0003 import importlib 0004 from ..nvector import NVector 0005 from ..utils.color import Color 0006 0007 0008 class LottieBase: 0009 """! 0010 Base class for Lottie JSON objects bindings 0011 """ 0012 def to_dict(self): 0013 """! 0014 Serializes into a JSON object fit for the Lottie format 0015 """ 0016 raise NotImplementedError 0017 0018 @classmethod 0019 def load(cls, lottiedict): 0020 """! 0021 Loads from a JSON object 0022 @returns An instance of the class 0023 """ 0024 raise NotImplementedError 0025 0026 def clone(self): 0027 """! 0028 Returns a copy of the object 0029 """ 0030 raise NotImplementedError 0031 0032 0033 class EnumMeta(enum.EnumMeta): 0034 """! 0035 Hack to counter-hack the hack in enum meta 0036 """ 0037 def __new__(cls, name, bases, classdict): 0038 classdict["__reduce_ex__"] = lambda *a, **kw: None # pragma: no cover 0039 return super().__new__(cls, name, bases, classdict) 0040 0041 0042 class LottieEnum(LottieBase, enum.Enum, metaclass=EnumMeta): 0043 """! 0044 Base class for enum-like types in the Lottie JSON structure 0045 """ 0046 def to_dict(self): 0047 return self.value 0048 0049 @classmethod 0050 def load(cls, lottieint): 0051 return cls(lottieint) 0052 0053 def clone(self): 0054 return self 0055 0056 0057 class PseudoList: 0058 """! 0059 List tag for some weird values in the Lottie JSON 0060 """ 0061 pass 0062 0063 0064 class LottieValueConverter: 0065 """! 0066 Factory for property types that require special conversions 0067 """ 0068 def __init__(self, py, lottie, name=None): 0069 self.py = py 0070 self.lottie = lottie 0071 self.name = name or "%s but displayed as %s" % (self.py.__name__, self.lottie.__name__) 0072 0073 def py_to_lottie(self, val): 0074 return self.lottie(val) 0075 0076 def lottie_to_py(self, val): 0077 return self.py(val) 0078 0079 @property 0080 def __name__(self): 0081 return self.name 0082 0083 0084 ## For values in Lottie that are bools but ints in the JSON 0085 PseudoBool = LottieValueConverter(bool, int, "0-1 int") 0086 0087 0088 class LottieProp: 0089 """! 0090 Lottie <-> Python property mapper 0091 """ 0092 def __init__(self, name, lottie, type=float, list=False, cond=None): 0093 ## Name of the Python property 0094 self.name = name 0095 ## Name of the Lottie JSON property 0096 self.lottie = lottie 0097 ## Type of the property 0098 ## @see LottieValueConverter, PseudoBool 0099 self.type = type 0100 ## Whether the property is a list of self.type 0101 ## @see PseudoList 0102 self.list = list 0103 ## Condition on when the property is loaded from the Lottie JSON 0104 self.cond = cond 0105 0106 def get(self, obj): 0107 """! 0108 Returns the value of the property from a Python object 0109 """ 0110 return getattr(obj, self.name) 0111 0112 def set(self, obj, value): 0113 """! 0114 Sets the value of the property from a Python object 0115 """ 0116 if isinstance(getattr(obj.__class__, self.name, None), property): 0117 return 0118 return setattr(obj, self.name, value) 0119 0120 def load_from_parent(self, lottiedict): 0121 """! 0122 Returns the value for this property from a JSON dict representing the parent object 0123 @returns The loaded value or @c None if the property is not in @p lottiedict 0124 """ 0125 if self.lottie in lottiedict: 0126 return self.load(lottiedict[self.lottie]) 0127 return None 0128 0129 def load_into(self, lottiedict, obj): 0130 """! 0131 Loads from a Lottie dict into an object 0132 """ 0133 if self.cond and not self.cond(lottiedict): 0134 return 0135 self.set(obj, self.load_from_parent(lottiedict)) 0136 0137 def load(self, lottieval): 0138 """! 0139 Loads the property from a JSON value 0140 @returns the Python equivalent of the JSON value 0141 """ 0142 if self.list is PseudoList and isinstance(lottieval, list): 0143 return self._load_scalar(lottieval[0]) 0144 #return [ 0145 #self._load_scalar(it) 0146 #for it in lottieval 0147 #] 0148 elif self.list is True: 0149 return [ 0150 self._load_scalar(it) 0151 for it in lottieval 0152 ] 0153 return self._load_scalar(lottieval) 0154 0155 def _load_scalar(self, lottieval): 0156 if lottieval is None: 0157 return None 0158 if inspect.isclass(self.type) and issubclass(self.type, LottieBase): 0159 return self.type.load(lottieval) 0160 elif isinstance(self.type, type) and isinstance(lottieval, self.type): 0161 return lottieval 0162 elif isinstance(self.type, LottieValueConverter): 0163 return self.type.lottie_to_py(lottieval) 0164 elif self.type is NVector: 0165 return NVector(*lottieval) 0166 elif self.type is Color: 0167 return Color(*lottieval) 0168 return self.type(lottieval) 0169 0170 def to_dict(self, obj): 0171 """! 0172 Converts the value of the property as from @p obj into a JSON value 0173 @param obj LottieObject with this property 0174 """ 0175 val = self._basic_to_dict(self.get(obj)) 0176 if self.list is PseudoList: 0177 if not isinstance(obj, list): 0178 return [val] 0179 elif isinstance(self.type, LottieValueConverter): 0180 val = self._basic_to_dict(self.type.py_to_lottie(val)) 0181 return val 0182 0183 def _basic_to_dict(self, v): 0184 if isinstance(v, LottieBase): 0185 return v.to_dict() 0186 elif isinstance(v, NVector): 0187 return list(map(self._basic_to_dict, v.components)) 0188 elif isinstance(v, list): 0189 return list(map(self._basic_to_dict, v)) 0190 elif isinstance(v, (int, str, bool)): 0191 return v 0192 elif isinstance(v, float): 0193 if v % 1 == 0: 0194 return int(v) 0195 return v #round(v, 3) 0196 else: 0197 raise Exception("Unknown value %r" % v) 0198 0199 def __repr__(self): 0200 return "<LottieProp %s:%s>" % (self.name, self.lottie) 0201 0202 def clone_value(self, value): 0203 if isinstance(value, list): 0204 return [self.clone_value(v) for v in value] 0205 if isinstance(value, (LottieBase, NVector)): 0206 return value.clone() 0207 if isinstance(value, (int, float, bool, str)) or value is None: 0208 return value 0209 raise Exception("Could not convert %r" % value) 0210 0211 0212 class LottieObjectMeta(type): 0213 def __new__(cls, name, bases, attr): 0214 props = [] 0215 for base in bases: 0216 if type(base) == cls: 0217 props += base._props 0218 attr["_props"] = props + attr.get("_props", []) 0219 return super().__new__(cls, name, bases, attr) 0220 0221 0222 class LottieObject(LottieBase, metaclass=LottieObjectMeta): 0223 """! 0224 @brief Base class for mapping Python classes into Lottie JSON objects 0225 """ 0226 def to_dict(self): 0227 return { 0228 prop.lottie: prop.to_dict(self) 0229 for prop in self._props 0230 if prop.get(self) is not None 0231 } 0232 0233 @classmethod 0234 def load(cls, lottiedict): 0235 if "__pyclass" in lottiedict: 0236 return CustomObject.load(lottiedict) 0237 cls = cls._load_get_class(lottiedict) 0238 obj = cls() 0239 for prop in cls._props: 0240 prop.load_into(lottiedict, obj) 0241 return obj 0242 0243 @classmethod 0244 def _load_get_class(cls, lottiedict): 0245 return cls 0246 0247 def find(self, search, propname="name"): 0248 """! 0249 @param search The value of the property to search 0250 @param propname The name of the property used to search 0251 @brief Recursively searches for child objects with a matching property 0252 """ 0253 if getattr(self, propname, None) == search: 0254 return self 0255 for prop in self._props: 0256 v = prop.get(self) 0257 if isinstance(v, LottieObject): 0258 found = v.find(search, propname) 0259 if found: 0260 return found 0261 elif isinstance(v, list) and v and isinstance(v[0], LottieObject): 0262 for obj in v: 0263 found = obj.find(search, propname) 0264 if found: 0265 return found 0266 return None 0267 0268 def find_all(self, type, predicate=None, include_self=True): 0269 """! 0270 Find all child objects that match a predicate 0271 @param type Type (or tuple of types) of the objects to match 0272 @param predicate Function that returns true on the objects to find 0273 @param include_self Whether should counsider `self` for a potential match 0274 """ 0275 0276 if isinstance(self, type) and include_self: 0277 if not predicate or predicate(self): 0278 yield self 0279 0280 for prop in self._props: 0281 v = prop.get(self) 0282 0283 if isinstance(v, LottieObject): 0284 for found in v.find_all(type, predicate, True): 0285 yield found 0286 elif isinstance(v, list) and v and isinstance(v[0], LottieObject): 0287 for child in v: 0288 for found in child.find_all(type, predicate, True): 0289 yield found 0290 0291 def clone(self): 0292 obj = self.__class__() 0293 for prop in self._props: 0294 v = prop.get(self) 0295 prop.set(obj, prop.clone_value(v)) 0296 return obj 0297 0298 def __str__(self): 0299 return type(self).__name__ 0300 0301 0302 class Index: 0303 """! 0304 @brief Simple iterator to generate increasing integers 0305 """ 0306 def __init__(self): 0307 self._i = -1 0308 0309 def __next__(self): 0310 self._i += 1 0311 return self._i 0312 0313 0314 class CustomObject(LottieObject): 0315 """! 0316 Allows extending the Lottie shapes with custom Python classes 0317 """ 0318 wrapped_lottie = LottieObject 0319 0320 def __init__(self): 0321 self.wrapped = self.wrapped_lottie() 0322 0323 @classmethod 0324 def load(cls, lottiedict): 0325 ld = lottiedict.copy() 0326 classname = ld.pop("__pyclass") 0327 modn, clsn = classname.rsplit(".", 1) 0328 subcls = getattr(importlib.import_module(modn), clsn) 0329 obj = subcls() 0330 for prop in subcls._props: 0331 prop.load_into(lottiedict, obj) 0332 obj.wrapped = subcls.wrapped_lottie.load(ld) 0333 return obj 0334 0335 def clone(self): 0336 obj = self.__class__(**self.to_pyctor()) 0337 obj.wrapped = self.wrapped.clone() 0338 return obj 0339 0340 def to_dict(self): 0341 dict = self.wrapped.to_dict() 0342 dict["__pyclass"] = "{0.__module__}.{0.__name__}".format(self.__class__) 0343 dict.update(LottieObject.to_dict(self)) 0344 return dict 0345 0346 def _build_wrapped(self): 0347 return self.wrapped_lottie() 0348 0349 def refresh(self): 0350 self.wrapped = self._build_wrapped() 0351 0352 0353 class ObjectVisitor: 0354 DONT_RECURSE = object() 0355 0356 def __call__(self, lottie_object): 0357 self._process(lottie_object) 0358 0359 def _process(self, lottie_object): 0360 self.visit(lottie_object) 0361 for p in lottie_object._props: 0362 pval = p.get(lottie_object) 0363 if self.visit_property(lottie_object, p, pval) is not self.DONT_RECURSE: 0364 if isinstance(pval, LottieObject): 0365 self._process(pval) 0366 elif isinstance(pval, list) and pval and isinstance(pval[0], LottieObject): 0367 for c in pval: 0368 self._process(c) 0369 0370 def visit(self, object): 0371 pass 0372 0373 def visit_property(self, object, property, value): 0374 pass