Blob Blame Raw
"""
Param.py
Will store the Parameters class for Synfig parameters 
"""

import sys
import copy
import math
import inspect
from lxml import etree
import settings
import common
import synfig.group
from synfig.animation import modify_bool_animation, to_Synfig_axis, is_animated, get_bool_at_frame, get_vector_at_frame, print_animation
from properties.multiDimensionalKeyframed import gen_properties_multi_dimensional_keyframed
from properties.valueKeyframed import gen_value_Keyframed
from properties.value import gen_properties_value
from effects.controller import gen_effects_controller
sys.path.append("..")


class Param:
    """
    Class to keep Synfig format parameters
    """
    def __init__(self, param, parent):
        """
        This parent can be another parameter or a Layer or Canvas
        """
        self.parent = parent
        self.param = param
        self.subparams = {}
        self.SUBPARAMS_EXTRACTED = 0
        self.expression_controllers = [] # Effects will be stored in this
        self.expression = ""
        self.dimension = 1  # 1 represents real, 2 represents vector
        self.get_exported_valuenode()

        self.is_group_child = 0

    def get_exported_valuenode(self):
        """
        If the animation of this param is not directly linked with this param,
        then it must be preset in the canvas :use, :def
        """
        if self.param is None or "use" not in self.param.attrib.keys():
            return
        layer = self.get_layer()
        canvas = layer.getparent()
        key = self.param.attrib["use"]
        anim = canvas.get_def(key)
        assert(anim is not None)
        self.param.append(copy.deepcopy(anim))
        
    def reset(self):
        """
        This function will be called when any of the entities of the parameter
        are changed. This should be internally called, but for now compromising
        """
        self.PATH_GENERATED = 0
        self.TRANSFORM_PATH_GENERATED = 0
        self.path = {}

    def get(self):
        """
        Returns the original param
        """
        return self.param

    def get_text(self):
        """
        Returns the text stored inside
        """
        return self.param.text

    def __getitem__(self, itr):
        """
        Returns the child corresponding to itr
        """
        return self.param[itr]

    def __setitem__(self, itr, val):
        """
        Sets the value of child corresponding to itr
        """
        self.param[itr] = val

    def get_layer_type(self):
        """
        Recursively go till the top until layer is not reached
        """
        if isinstance(self.parent, common.Layer.Layer):
            return self.parent.get_type()
        return self.parent.get_layer_type()

    def get_layer(self):
        """
        Recursively find the layer
        """
        if isinstance(self.parent, common.Layer.Layer):
            return self.parent
        return self.parent.get_layer()

    def get_canvas(self):
        """
        Recursively find the canvas
        """
        if isinstance(self.parent, common.Canvas.Canvas):
            return self.parent
        return self.parent.get_canvas()

    def getparent(self):
        """
        Returns the parent of this parameter
        """
        return self.parent

    def add_subparam(self, key, val):
        """
        Adds sub parameters
        """
        self.subparams[key] = val

    def get_subparam_dict(self):
        """
        Returns the address of the dict
        """
        return self.subparams

    def get_subparam(self, key):
        """
        Given a key, returns it's dictionary
        """
        return self.subparams[key]

    def append_same_key(self, key, child):
        """
        Helps append in the dict if same key is already present
        """
        val = Param(child, self)

        if self.is_group_child:
            val.is_group_child = True

        if isinstance(self.subparams[key], list):
            self.subparams[key].append(val)
        else:
            prev = self.subparams[key]
            self.subparams[key] = [prev, val]

    def extract_subparams(self):
        """
        Extracts the subparameters of this parameter and stores with there tag
        as the key
        """
        if self.SUBPARAMS_EXTRACTED:
            return
        self.SUBPARAMS_EXTRACTED = 1
        self.subparams.clear()
        for child in self.param:
            key = child.tag 
            if key in self.subparams.keys():
                self.append_same_key(key, child)
            else:
                self.subparams[key] = Param(child, self)

                if self.is_group_child:
                    self.subparams[key].is_group_child = True

    def animate_without_path(self, anim_type):
        """
        If this parameter is not animated, it generates dummy waypoints and
        animates this parameter
        """
        if anim_type in {"vector", "group_layer_scale", "stretch_layer_scale"}:   # This will never happen, can remove this latter
            self.dimension = 2

        # Check if we are dealing with convert methods
        if self.param[0].tag in settings.CONVERT_METHODS:
            self.extract_subparams()
            if self.param[0].tag == "add":
                self.subparams["add"].extract_subparams()
                self.subparams["add"].subparams["lhs"].animate(anim_type)
                self.subparams["add"].subparams["rhs"].animate(anim_type)
                self.subparams["add"].subparams["scaler"].animate("scalar_multiply")
        else:
            self.single_animate(anim_type)


    def animate(self, anim_type, transform=False):
        """
        If this parameter is not animated, it generates dummy waypoints and
        animates this parameter
        """
        if not transform and anim_type in {"vector"}:
            # Will add link to convert into lottie axis
            value = common.Vector.Vector()
            value[0] = (settings.lottie_format["w"]/2) / settings.PIX_PER_UNIT
            value[1] = -(settings.lottie_format["h"]/2) / settings.PIX_PER_UNIT
            self.add_convert_link(value)

        self.expression_controllers = []
        self.recur_animate(anim_type)

    def recur_animate(self, anim_type):
        """
        Internal private method for animating
        """
        if anim_type in {"vector", "group_layer_scale", "stretch_layer_scale"}:
            self.dimension = 2

        # Check if we are dealing with convert methods
        if self.param.tag in settings.BONES or self.param[0].tag in settings.CONVERT_METHODS:
            self.extract_subparams()

            if self.param.tag == "bone":  # Carefull about param[0] and param here

                # Get the animation of this origin
                bone_origin, eff_1 = self.subparams["origin"].recur_animate("vector")

                # Get the animation of angle
                prop_name = "angle"
                if self.is_group_child:
                    prop_name = "rotate_layer_angle"
                bone_angle, ang_eff = self.subparams["angle"].recur_animate(prop_name)

                # Get the local length scale of the bone
                lls, lls_eff = self.subparams["scalelx"].recur_animate("scalar_multiply")

                # Get the recursive length scale of the bone
                rls, rls_eff = self.subparams["scalex"].recur_animate("scalar_multiply")

                # Adding to expression controller
                self.expression_controllers.extend(eff_1)
                self.expression_controllers.extend(ang_eff)
                self.expression_controllers.extend(lls_eff)
                self.expression_controllers.extend(rls_eff)

                # Now adding the parent's effects in this bone
                guid = self.subparams["parent"][0].attrib["guid"]
                canvas = self.get_canvas()
                bone = canvas.get_bone(guid)

                # Store the previous state
                prev_state = bone.is_group_child
                if self.is_group_child:
                    bone.is_group_child = True

                par_origin, par_angle, par_lls, par_rls, par_eff = bone.recur_animate("vector")

                # Restore the previous state
                bone.is_group_child = prev_state

                if par_eff is not None: # checking it is not the bone_root
                    # Adding expression to the expression controller
                    self.expression_controllers.extend(par_eff)

                    # Modifying the bone_origin according to the parent's local scale
                    bone_origin = "mul(" + bone_origin + ", " + par_lls + ")"

                    # Modifying the bone_origin according to the parent's recursive scale
                    bone_origin = "mul(" + bone_origin + ", " + par_rls + ")"

                    # Forming the expression for new origin
                    a1 = "degreesToRadians({angle})"
                    a2 = "degreesToRadians(sum({angle}, 90))"
                    a1, a2 = a1.format(angle=par_angle), a2.format(angle=par_angle)

                    ret_x = "sum({par_origin}[0], mul({bone_origin}[0], Math.cos({a1})), mul({bone_origin}[1], Math.cos({a2})))"
                    ret_y = "sum({par_origin}[1], mul({bone_origin}[0], Math.sin({a1})), mul({bone_origin}[1], Math.sin({a2})))"
                    ret_origin = "[" + ret_x + ", " + ret_y + "]"
                    ret_origin = ret_origin.format(par_origin=par_origin, bone_origin=bone_origin, a1=a1, a2=a2)

                    # Forming the expression for new angle
                    ret_angle = "sum({par_angle}, {bone_angle})"
                    ret_angle = ret_angle.format(par_angle=par_angle, bone_angle=bone_angle)
                else:
                    ret_origin = bone_origin
                    ret_angle = bone_angle

                ############# REMOVE AFTER DEBUGGING rls
                rls = "1"
                return ret_origin, ret_angle, lls, rls, self.expression_controllers

            elif self.param.tag == "bone_root": # No animation to be added as this being the root
                return "[0, 0]", "0", "1", "1", None

            elif self.param[0].tag == "add":
                self.subparams["add"].extract_subparams()
                lhs, effects_1 = self.subparams["add"].subparams["lhs"].recur_animate(anim_type)
                rhs, effects_2 = self.subparams["add"].subparams["rhs"].recur_animate(anim_type)   # Only one of the child should be converted to lottie axis
                scalar, effects_3 = self.subparams["add"].subparams["scalar"].recur_animate("scalar_multiply")
                self.expression_controllers.extend(effects_1)
                self.expression_controllers.extend(effects_2)
                self.expression_controllers.extend(effects_3)
                ret = "mul(sum({lhs}, {rhs}), {scalar})"
                ret = ret.format(lhs=lhs, rhs=rhs, scalar=scalar)
                self.expression = ret
                return ret, self.expression_controllers # Might have to return copy.deepcopy because these expressions are destroyed on animating this parameter again, and this param might be child to 2 params

            elif self.param[0].tag == "exp":
                self.subparams["exp"].extract_subparams()
                exp, effects_1 = self.subparams["exp"].subparams["exp"].recur_animate("scalar_multiply")
                scale, effects_2 = self.subparams["exp"].subparams["scale"].recur_animate(anim_type)
                self.expression_controllers.extend(effects_1)
                self.expression_controllers.extend(effects_2)
                ret = "mul(Math.exp({exp}), {scale})"
                ret = ret.format(exp=exp, scale=scale)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "average":
                self.subparams["average"].extract_subparams()   # Entries will be extracted here
                lst = self.subparams["average"].subparams["entry"]
                if not isinstance(lst, list):   # When only one entry is present
                    lst = [lst]

                ret = "sum("    # Returning string
                for it in lst:
                    ent, eff = it.recur_animate(anim_type) 
                    self.expression_controllers.extend(eff)
                    ret += ent
                    ret += ","
                ret = ret[:-1]
                ret += ")"

                # Dividing by the length
                ret = "div(" + ret + "," + str(len(lst)) + ")"
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "composite":  # composite only on vectors
                self.subparams["composite"].extract_subparams()
                x, effect_1 = self.subparams["composite"].subparams["x"].recur_animate("real")
                y, effect_2 = self.subparams["composite"].subparams["y"].recur_animate("real")
                self.expression_controllers.extend(effect_1)
                self.expression_controllers.extend(effect_2)
                ret = "[{x}, -{y}]"
                ret = ret.format(x=x, y=y)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "linear":
                self.subparams["linear"].extract_subparams()
                slope, effect_1 = self.subparams["linear"].subparams["slope"].recur_animate(anim_type)
                offset, effect_2 = self.subparams["linear"].subparams["offset"].recur_animate(anim_type)
                self.expression_controllers.extend(effect_1)
                self.expression_controllers.extend(effect_2)
                ret = "sum({offset}, mul({slope}, time))"
                ret = ret.format(offset=offset, slope=slope)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "radial_composite":   # only for vectors
                dic = {"gen_layer_group", "gen_layer_rotate", "gen_layer_translate", "gen_layer_scale"}
                prop_name = "angle"
                if self.called_by(dic): # Should find some easier way of finding the caller function
                    prop_name = "rotate_layer_angle"
                self.subparams["radial_composite"].extract_subparams()
                rad, eff_1 = self.subparams["radial_composite"].subparams["radius"].recur_animate("real")
                ang, eff_2 = self.subparams["radial_composite"].subparams["theta"].recur_animate(prop_name)
                self.expression_controllers.extend(eff_1)
                self.expression_controllers.extend(eff_2)
                ret = "[mul({radius}, Math.cos(degreesToRadians({angle}))), mul({radius}, Math.sin(degreesToRadians({angle})))]"
                ret = ret.format(radius=rad, angle=ang)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "scale":
                self.subparams["scale"].extract_subparams()
                link, eff_1 = self.subparams["scale"].subparams["link"].recur_animate(anim_type)
                scalar, eff_2 = self.subparams["scale"].subparams["scalar"].recur_animate("scalar_multiply")
                self.expression_controllers.extend(eff_1)
                self.expression_controllers.extend(eff_2)
                ret = "mul({link}, {scalar})"
                ret = ret.format(link=link, scalar=scalar)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "subtract":
                self.subparams["subtract"].extract_subparams()
                scalar, eff_1 = self.subparams["subtract"].subparams["scalar"].recur_animate("scalar_multiply")
                lhs, eff_2 = self.subparams["subtract"].subparams["lhs"].recur_animate(anim_type)
                rhs, eff_3 = self.subparams["subtract"].subparams["rhs"].recur_animate(anim_type)
                self.expression_controllers.extend(eff_1)
                self.expression_controllers.extend(eff_2)
                self.expression_controllers.extend(eff_3)
                ret = "mul(sub({lhs}, {rhs}), {scalar})"
                ret = ret.format(lhs=lhs, rhs=rhs, scalar=scalar)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "switch":
                self.subparams["switch"].extract_subparams()
                link_off, eff_1 = self.subparams["switch"].subparams["link_off"].recur_animate(anim_type)
                link_on, eff_2 = self.subparams["switch"].subparams["link_on"].recur_animate(anim_type)
                switch, eff_3 = self.subparams["switch"].subparams["switch"].recur_animate("bool")
                self.expression_controllers.extend(eff_1)
                self.expression_controllers.extend(eff_2)
                self.expression_controllers.extend(eff_3)
                ret = "sum(mul({eff_2}, {eff_3}), mul({eff_1}, sub(1, {eff_3})))"
                ret = ret.format(eff_1=link_off, eff_2=link_on, eff_3=switch)
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "weighted_average":
                self.subparams["weighted_average"].extract_subparams()
                lst = self.subparams["weighted_average"].subparams["entry"]
                if not isinstance(lst, list):   # When only one entry is present
                    lst = [lst]

                ret = "sum("    # Returning string
                den = "sum("    # Denominator string
                for it in lst:
                    it.extract_subparams()  # weighted_vector
                    it.subparams["weighted_vector"].extract_subparams()
                    weight, eff_1 = it.subparams["weighted_vector"].subparams["weight"].recur_animate("scalar_multiply")
                    value, eff_2 = it.subparams["weighted_vector"].subparams["value"].recur_animate(anim_type)
                    self.expression_controllers.extend(eff_1)
                    self.expression_controllers.extend(eff_2)
                    hell = "mul({weight}, {value}),"
                    hell = hell.format(weight=weight, value=value)
                    ret += hell

                    den += weight
                    den += ","
                ret = ret[:-1]
                den = den[:-1]
                den += ")"
                ret += ")"
                ret = "div(" + ret + "," + den + ")"
                self.expression = ret
                return ret, self.expression_controllers

            elif self.param[0].tag == "bone_link":
                self.subparams["bone_link"].extract_subparams()
                guid = self.subparams["bone_link"].subparams["bone"][0].attrib["guid"]
                bone = self.get_bone_from_canvas(guid)

                base_value, bv_eff = self.subparams["bone_link"].subparams["base_value"].recur_animate("vector")

                # Storing previous state
                prev_state = bone.is_group_child
                if self.is_group_child:
                    bone.is_group_child = True
                origin, angle, lls, rls, eff = bone.recur_animate("vector")

                # Adding lls effect to the bone
                base_value = "mul(" + base_value + ", " + lls + ")"

                # Adding rls effect to the bone
                base_value = "mul(" + base_value + ", " + rls + ")"

                # Adding effect of base_value here
                a1 = "degreesToRadians({angle})"
                a2 = "degreesToRadians(sum({angle}, 90))"
                a1, a2 = a1.format(angle=angle), a2.format(angle=angle)

                ret_x = "sub(sum({origin}[0], mul({base_value}[0], Math.cos({a1}))), mul({base_value}[1], Math.cos({a2})))"
                ret_y = "sub(sum({origin}[1], mul({base_value}[0], Math.sin({a1}))), mul({base_value}[1], Math.sin({a2})))"
                ret_origin = "[" + ret_x + ", -" + ret_y + "]"
                ret_origin = ret_origin.format(origin=origin, base_value=base_value, a1=a1, a2=a2)

                # Restore previous state
                bone.is_group_child = prev_state

                if eff is not None:
                    self.expression_controllers.extend(eff)
                self.expression_controllers.extend(bv_eff)

                self.expression = ret_origin
                return ret_origin, self.expression_controllers

        else:
            self.single_animate(anim_type)
            # Insert the animation into the effect
            self.expression_controllers.append({})

            if anim_type == "vector":
                self.gen_path("vector")
            elif anim_type not in {"bool", "string"}:   # These do not need path generation
                self.gen_path("real")   # "real" can be of various types as defined in common.misc.parse_position()
            elif anim_type in {"bool"}:     # This is the first time bool is needed
                self.gen_path("bool")

            
            gen_effects_controller(self.expression_controllers[-1], self.get_path(), anim_type)

            # Extract name from the effects
            ret = "effect('{effect_1}')('{effect_2}')"
            ret = ret.format(effect_1=self.expression_controllers[-1]["nm"], effect_2=self.expression_controllers[-1]["ef"][0]["nm"])
            self.expression = ret
            return ret, self.expression_controllers

    def called_by(self, dic):
        """
        Checks if this function's ancestor are in this dictionary
        """
        for it in inspect.stack():
            if it[3] in dic:
                return True
        return False

    def single_animate(self, anim_type):
        """
        For animating a single animation, convert methods will use this internal
        function
        """
        is_animate = common.misc.is_animated(self.param[0])
        if is_animate == 2:
            # If already animated, no need to add waypoints
            # Forcibly set it's animation type to the anim_type
            self.param[0].attrib["type"] = anim_type
            if anim_type == "bool":
                modify_bool_animation(self.param[0])
            return
        elif is_animate == 0:
            st = '<animated type="{anim_type}"><waypoint time="0s" before="constant" after="constant"></waypoint></animated>'
            st = st.format(anim_type=anim_type)
            root = etree.fromstring(st)
            root[0].append(copy.deepcopy(self.param[0]))
            self.param[0] = root
        elif is_animate == 1:
            self.param[0].attrib["type"] = anim_type
            self.param[0][0].attrib["before"] = self.param[0][0].attrib["after"] = "constant"

        new_waypoint = copy.deepcopy(self.param[0][0])
        frame = common.misc.get_frame(self.param[0][0])
        frame += 1
        time = frame / settings.lottie_format["fr"]
        time = str(time) + 's'
        new_waypoint.attrib["time"] = time
        self.param[0].insert(1, new_waypoint)

        if anim_type == "bool":
            modify_bool_animation(self.param[0])

    def gen_path(self, anim_type="real", idx=0):
        """
        Generates the path for this parameter over time depending on the
        animation type of this parameter
        """
        self.path = {}
        if anim_type in {"real", "bool"}:
            gen_value_Keyframed(self.path, self.param[0], idx)
        else:
            gen_properties_multi_dimensional_keyframed(self.path, self.param[0], idx)

    def get_path(self):
        """
        Returns the dictionary which stores the path of this parameter
        """
        return self.path

    def fill_path(self, lottie, key):
        """
        Fill's the lottie dictionary with the path of the parameter
        """
        if self.param[0].tag in settings.CONVERT_METHODS:
            expression = "var $bm_rt; $bm_rt = {expr}"
            expression = expression.format(expr=self.expression)
            if self.dimension == 1:
                val = 1
            else:
                val = [1, 1]
            gen_properties_value(lottie[key],
                                 val,
                                 0,
                                 0,
                                 expression)
            if "ef" not in self.get_layer().get_lottie_layer().keys():
                self.get_layer().get_lottie_layer()["ef"] = []
            self.get_layer().get_lottie_layer()["ef"].extend(self.expression_controllers)
                
        else:
            lottie[key] = copy.deepcopy(self.path)

    def get_value(self, frame):
        """
        Public method to get the value of the parameter at a given frame
        """
        ret = self.__get_value(frame)
        # Convert into Lottie format
        if isinstance(ret, list):
            ret = [ret[0], -ret[1]]
        return ret
    
    def __get_value(self, frame):
        """
        Returns the value of the parameter at a given frame
        """
        if self.param.tag in settings.BONES or self.param[0].tag in settings.CONVERT_METHODS:
            if self.param.tag == "bone":
                cur_origin = self.subparams["origin"].__get_value(frame)

                # Now adding the parent's effects in this bone
                guid = self.subparams["parent"][0].attrib["guid"]
                canvas = self.get_canvas()
                bone = canvas.get_bone(guid)
                shifted_origin, shifted_angle, lls, rls = bone.__get_value(frame)
                a1, a2 = math.radians(shifted_angle), math.radians(shifted_angle+90)

                # Calculating this bones angle with respect to parent bone's
                # angle
                if "angle" in self.subparams.keys():
                    angle = to_Synfig_axis(self.subparams["angle"].__get_value(frame), "angle")
                else:
                    angle = 0

                # Calculating the local length scale
                local_length_scale = self.subparams["scalelx"].__get_value(frame)

                # Calculating the recursive length scale
                this_rls = self.subparams["scalex"].__get_value(frame)    # In current angle's direction
                absolute_angle = shifted_angle+angle
                aa1 = math.radians(absolute_angle)
                this_rls = [this_rls * math.cos(aa1), this_rls * math.sin(aa1)]

                # Calculate returning recursive length
                ret_rls = [this_rls[0]*rls[0], this_rls[1]*rls[1]]
                ##### REMOVE AFTER DEBUGGING
                ret_rls = [1, 1]

                # Multiplying the current bone origin with the scale
                cur_origin = [i*lls for i in cur_origin]

                ret = shifted_origin
                # Adding effect of x component
                ret[0] = ret[0] + (cur_origin[0] * math.cos(a1) + cur_origin[1] * math.cos(a2)) * rls[0]
                ret[1] = ret[1] + (cur_origin[0] * math.sin(a1) + cur_origin[1] * math.sin(a2)) * rls[1]

                return ret, absolute_angle, local_length_scale, ret_rls

            elif self.param.tag == "bone_root":
                origin = [0, 0]
                angle = 0
                local_length_scale = 1
                recursive_length_scale = [1, 1] # x and y axis
                return origin, angle, local_length_scale, recursive_length_scale

            elif self.param[0].tag == "add":
                ret = self.subparams["add"].subparams["lhs"].__get_value(frame)
                ret2 = self.subparams["add"].subparams["rhs"].__get_value(frame)
                mul = self.subparams["add"].subparams["scalar"].__get_value(frame)
                if isinstance(ret, list):
                    ret[0] += ret2[0]
                    ret[1] += ret2[1]
                    ret = [it*mul for it in ret]
                else:
                    ret += ret2
                    ret *= mul

            elif self.param[0].tag == "exp":
                exp = self.subparams["exp"].subparams["exp"].__get_value(frame)
                scale = self.subparams["exp"].subparams["scale"].__get_value(frame)
                ret = scale * math.exp(exp)

            elif self.param[0].tag == "average":
                lst = self.subparams["average"].subparams["entry"]
                if not isinstance(lst, list):
                    lst = [lst]

                ret = [0, 0]
                if not isinstance(lst[0].__get_value(frame), list):
                    ret = 0
                for it in lst:
                    val = it.__get_value(frame)
                    if isinstance(val, list):
                        ret[0], ret[1] = ret[0] + val[0], ret[1] + val[1]
                    else:
                        ret += val
                if isinstance(ret, list):
                    ret[0], ret[1] = ret[0] / len(lst), ret[1] / len(lst)
                else:
                    ret /= len(lst)

            elif self.param[0].tag == "weighted_average":
                self.subparams["weighted_average"].extract_subparams()
                lst = self.subparams["weighted_average"].subparams["entry"]
                if not isinstance(lst, list):   # When only one entry is present
                    lst = [lst]

                ret = [0, 0]
                den = 0
                if not isinstance(lst[0].subparams["weighted_vector"].subparams["value"].__get_value(frame), list):
                    ret = 0
                for it in lst:
                    weight = it.subparams["weighted_vector"].subparams["weight"].__get_value(frame)
                    value = it.subparams["weighted_vector"].subparams["value"].__get_value(frame)
                    den += weight
                    if isinstance(value, list):
                        ret[0], ret[1] = ret[0] + value[0]*weight, ret[1] + value[1]*weight
                    else:
                        ret += value*weight
                if isinstance(ret, list):
                    ret[0], ret[1] = ret[0] / den, ret[1] / den
                else:
                    ret /= den

            elif self.param[0].tag == "composite":  # Only available for vectors
                x = self.subparams["composite"].subparams["x"].__get_value(frame)
                y = self.subparams["composite"].subparams["y"].__get_value(frame)
                ret = [x, y]

            elif self.param[0].tag == "linear":
                slope = self.subparams["linear"].subparams["slope"].__get_value(frame)
                offset = self.subparams["linear"].subparams["offset"].__get_value(frame)
                if isinstance(slope, list):
                    ret = [0, 0]
                    ret[0] = offset[0] + slope[0]*(frame/settings.lottie_format["fr"])
                    ret[1] = offset[1] + slope[1]*(frame/settings.lottie_format["fr"])
                else:
                    ret = offset + slope*(frame/settings.lottie_format["fr"])

            elif self.param[0].tag == "radial_composite":   # Only for vectors
                rad = self.subparams["radial_composite"].subparams["radius"].__get_value(frame)
                angle = to_Synfig_axis(self.subparams["radial_composite"].subparams["theta"].__get_value(frame), "angle")
                angle = math.radians(angle)
                x = rad * math.cos(angle)
                y = rad * math.sin(angle)
                ret = [x, y]

            elif self.param[0].tag == "scale":
                link = self.subparams["scale"].subparams["link"].__get_value(frame)
                scalar = self.subparams["scale"].subparams["scalar"].__get_value(frame)
                if isinstance(link, list):
                    link[0] *= scalar
                    link[1] *= scalar
                else:
                    link *= scalar
                ret = link

            elif self.param[0].tag == "subtract":
                lhs = self.subparams["subtract"].subparams["lhs"].__get_value(frame)
                rhs = self.subparams["subtract"].subparams["rhs"].__get_value(frame)
                scalar = self.subparams["subtract"].subparams["scalar"].__get_value(frame)
                if isinstance(lhs, list):
                    ret = [0, 0]
                    ret[0] = (lhs[0] - rhs[0]) * scalar
                    ret[1] = (lhs[1] - rhs[1]) * scalar
                else:
                    ret = (lhs - rhs) * scalar

            elif self.param[0].tag == "switch":
                link_off = self.subparams["switch"].subparams["link_off"].__get_value(frame)
                link_on = self.subparams["switch"].subparams["link_on"].__get_value(frame)
                switch = self.subparams["switch"].subparams["switch"].__get_value(frame)
                if isinstance(link_on, list):
                    ret = [0, 0]
                    ret[0] = link_on[0] * switch + link_off[0] * (1 - switch)
                    ret[1] = link_on[1] * switch + link_off[1] * (1 - switch)
                else:
                    ret = link_on * switch + link_off * (1 - switch)

            elif self.param[0].tag == "bone_link":
                guid = self.subparams["bone_link"].subparams["bone"][0].attrib["guid"]
                bone = self.get_bone_from_canvas(guid)
                ret_origin, ret_angle, lls, rls = bone.__get_value(frame)

                # Adding the base value effect here
                base_value = self.subparams["bone_link"].subparams["base_value"].__get_value(frame)
                a1, a2 = math.radians(ret_angle), math.radians(ret_angle+90)
                ret = ret_origin

                # base_value to be arranged according to the local scale
                base_value = [lls*i for i in base_value]

                ret[0] = ret[0] + (base_value[0] * math.cos(a1) - base_value[1] * math.cos(a2)) * rls[0]
                ret[1] = ret[1] + (base_value[0] * math.sin(a1) - base_value[1] * math.sin(a2)) * rls[1]

                ret = [ret[0], ret[1]]

        else:
            ret = self.get_single_value(frame)
            if isinstance(ret, list):
                # Need to change the calculation inside get_single_value, this
                # is just a hack
                ret = [ret[0], -ret[1]]
        return ret

    def get_single_value(self, frame):
        """
        Returns the value of some parameter which is not a convert method
        """
        if self.param[0].attrib["type"] == "bool":  # No need of lottie format path here
            return get_bool_at_frame(self.param[0], frame)

        if not self.path:   # Empty dictionary
            raise KeyError("Please calculate the path of this parameter before getting value at a frame")
        return get_vector_at_frame(self.path, frame)


    def add_offset(self):
        """
        Will modify the animation by inserting <add></add> xml elements
        """
        offset = synfig.group.get_offset()
        self.add_convert_link(offset)


    def add_convert_link(self, val):
        """
        Private method for inserting <add></add>, given a offset
        """
        st = "<add type='vector'><lhs></lhs><rhs></rhs><scalar><real value='1.00'/></scalar></add>"
        root = etree.fromstring(st)
        first = copy.deepcopy(self.param[0])
        root[0].append(first)
        
        st = "<vector><x>{x_val}</x><y>{y_val}</y></vector>"
        st = st.format(x_val=val[0], y_val=val[1])
        second = etree.fromstring(st)
        root[1].append(second)

        self.param[0].getparent().remove(self.param[0])
        self.param.append(root)
        self.SUBPARAMS_EXTRACTED = 0

    def update_frame_window(self, window):
        """
        Given an animation, find the minimum and maximum frame at which the
        waypoints are located
        """
        self.extract_subparams()
        if self.param.tag in {"bone", "bone_root"}:
            node = self.param
        else:
            node = self.param[0]

        if node.tag in settings.BONES:
            if node.tag == "bone":
                self.subparams["origin"].update_frame_window(window)
                self.subparams["angle"].update_frame_window(window)
                self.subparams["scalelx"].update_frame_window(window)
                parent_guid = self.subparams["parent"][0].attrib["guid"]
                canvas = self.get_canvas()
                bone = canvas.get_bone(parent_guid)
                bone.update_frame_window(window)

        # Time updation for converted nodes:
        if node.tag in settings.CONVERT_METHODS:
            if node.tag in {"add", "subtract"}:
                key = node.tag
                self.subparams[key].extract_subparams()
                self.subparams[key].subparams["lhs"].update_frame_window(window)
                self.subparams[key].subparams["rhs"].update_frame_window(window)
                self.subparams[key].subparams["scalar"].update_frame_window(window)

            elif node.tag == "switch":
                key = node.tag
                self.subparams[key].extract_subparams()
                self.subparams[key].subparams["link_off"].update_frame_window(window)
                self.subparams[key].subparams["link_on"].update_frame_window(window)
                self.subparams[key].subparams["switch"].update_frame_window(window)

            elif node.tag == "average":
                if not isinstance(self.subparams["average"], list):
                    self.subparams["average"] = [self.subparams["average"]]
                for it in self.subparams["average"]:
                    it.update_frame_window(window)

            elif node.tag == "exp":
                self.subparams["exp"].extract_subparams()
                self.subparams["exp"].subparams["exp"].update_frame_window(window)
                self.subparams["exp"].subparams["scale"].update_frame_window(window)

            elif node.tag == "weighted_average":
                self.subparams["weighted_average"].extract_subparams()
                for it in self.subparams["weighted_average"].subparams["entry"]:
                    it.extract_subparams()
                    it.subparams["weighted_vector"].extract_subparams()
                    ti = it.subparams["weighted_vector"].subparams
                    ti["weight"].update_frame_window(window)
                    ti["value"].update_frame_window(window)

            elif node.tag == "composite":
                self.subparams["composite"].extract_subparams()
                self.subparams["composite"].subparams["x"].update_frame_window(window)
                self.subparams["composite"].subparams["y"].update_frame_window(window)

            elif node.tag == "linear":
                window["first"] = settings.lottie_format["ip"]
                window["last"] = settings.lottie_format["op"]

            elif node.tag == "radial_composite":
                self.subparams["radial_composite"].extract_subparams()
                self.subparams["radial_composite"].subparams["radius"].update_frame_window(window)
                self.subparams["radial_composite"].subparams["theta"].update_frame_window(window)

            elif node.tag == "scale":
                self.subparams["scale"].extract_subparams()
                self.subparams["scale"].subparams["link"].update_frame_window(window)
                self.subparams["scale"].subparams["scalar"].update_frame_window(window)

            elif node.tag == "bone_link":
                self.subparams["bone_link"].extract_subparams()
                guid = self.subparams["bone_link"].subparams["bone"][0].attrib["guid"]
                bone = self.get_bone_from_canvas(guid)
                bone.update_frame_window(window)
                self.subparams["bone_link"].subparams["base_value"].update_frame_window(window)

        if is_animated(node) == 2:
            for waypoint in node:
                fr = common.misc.get_frame(waypoint)
                if fr > window["last"]:
                    window["last"] = fr
                if fr < window["first"]:
                    window["first"] = fr

    def get_bone_from_canvas(self, guid):
        """
        Given a canvas, recursively travel to the ancestor canvas's and find the
        bone associated with the guid
        """
        now = self
        while True:
            layer = now.get_layer()
            canvas = layer.getparent()
            bone = canvas.get_bone(guid)
            if bone is not None:
                break
            else:
                now = canvas.getparent_param()
        return bone