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