Blame synfig-studio/plugins/lottie-exporter/common/misc.py

AnishGulati 1f6b9a
# pylint: disable=line-too-long
AnishGulati 1f6b9a
"""
AnishGulati 1f6b9a
misc.py
AnishGulati 1f6b9a
Some miscellaneous functions and classes will be provided here
AnishGulati 1f6b9a
"""
AnishGulati 1f6b9a
AnishGulati 1f6b9a
import sys
AnishGulati 1f6b9a
import math
AnishGulati 1f6b9a
import settings
AnishGulati 1f6b9a
from common.Vector import Vector
AnishGulati 1f6b9a
from common.Color import Color
AnishGulati 1f6b9a
sys.path.append("..")
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def approximate_equal(a, b):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Need to define this function somewhere else, a and b are of type "float"
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        a (float) : First number to be compared
AnishGulati 1f6b9a
        b (float) : Second number to be compared
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (bool) : True if the numbers are approximately equal under precision
AnishGulati 1f6b9a
               : False otherwise
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    precision = 1e-8
AnishGulati 1f6b9a
    if a < b:
AnishGulati 1f6b9a
        return b - a < precision
AnishGulati 1f6b9a
    return a - b < precision
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def calculate_pixels_per_unit():
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Gives the value of 1 unit in terms of pixels according to the canvas defined
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        (None)
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (float) : Pixels per unit
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    image_width = float(settings.lottie_format["w"])
AnishGulati 1f6b9a
    image_area_width = settings.view_box_canvas["val"][2] - settings.view_box_canvas["val"][0]
AnishGulati 1f6b9a
    settings.PIX_PER_UNIT = image_width / image_area_width
AnishGulati 1f6b9a
    return settings.PIX_PER_UNIT
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati d4c500
def change_axis(x_val, y_val, is_transform=True):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Convert synfig axis coordinates into lottie format
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        x_val (float | str) : x axis value in pixels
AnishGulati 1f6b9a
        y_val (float | str) : y axis value in pixels
AnishGulati 1f6b9a
        is_transform (`obj`: bool, optional) : Is this value used in transform module?
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (list)  : x and y axis value in Lottie format
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    x_val, y_val = float(x_val), float(y_val)
AnishGulati 1f6b9a
    if is_transform:
AnishGulati 1f6b9a
        x_val, y_val = x_val, -y_val
AnishGulati 1f6b9a
    else:
AnishGulati 1f6b9a
        x_val, y_val = x_val + settings.lottie_format["w"]/2, -y_val + settings.lottie_format["h"]/2
AnishGulati 1f6b9a
    return [x_val, y_val]
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def parse_position(animated, i):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    To convert the synfig coordinates from units(initially a string) to pixels
AnishGulati 1f6b9a
    Depends on whether a vector is provided to it or a real value
AnishGulati 1f6b9a
    If real value is provided, then time is also taken into consideration
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        animated (lxml.etree._Element) : Stores animation which contains waypoints
AnishGulati 1f6b9a
        i        (int)                 : Iterator over animation
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati a3579e
        (common.Vector.Vector) If the animated type is not color
AnishGulati a3579e
        (common.Color.Color)  Else if the animated type is color
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    if animated.attrib["type"] == "vector":
AnishGulati 1f6b9a
        pos = [float(animated[i][0][0].text),
AnishGulati 1f6b9a
               float(animated[i][0][1].text)]
AnishGulati 1f6b9a
        pos = [settings.PIX_PER_UNIT*x for x in pos]
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "real":
AnishGulati 1f6b9a
        pos = parse_value(animated, i)
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "circle_radius":
AnishGulati 1f6b9a
        pos = parse_value(animated, i)
AnishGulati 1f6b9a
        pos[0] *= 2 # Diameter
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "angle":
AnishGulati 1f6b9a
        pos = [get_angle(float(animated[i][0].attrib["value"])),
AnishGulati 1f6b9a
               get_frame(animated[i])]
AnishGulati 1f6b9a
AnishGulati 4ccde7
    elif animated.attrib["type"] in {"composite_convert", "region_angle", "star_angle_new", "scalar_multiply"}:
AnishGulati 1f6b9a
        pos = [float(animated[i][0].attrib["value"]),
AnishGulati 1f6b9a
               get_frame(animated[i])]
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "rotate_layer_angle":
AnishGulati 1f6b9a
        # Angle needs to made neg of what they are
AnishGulati 1f6b9a
        pos = [-float(animated[i][0].attrib["value"]),
AnishGulati 1f6b9a
               get_frame(animated[i])]
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "opacity":
AnishGulati 1f6b9a
        pos = [float(animated[i][0].attrib["value"]) * settings.OPACITY_CONSTANT,
AnishGulati 1f6b9a
               get_frame(animated[i])]
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "effects_opacity":
AnishGulati 1f6b9a
        pos = [float(animated[i][0].attrib["value"]),
AnishGulati 1f6b9a
               get_frame(animated[i])]
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "points":
AnishGulati 1f6b9a
        pos = [int(animated[i][0].attrib["value"]),
AnishGulati 1f6b9a
               get_frame(animated[i])]
AnishGulati 1f6b9a
AnishGulati 0b69a9
    elif animated.attrib["type"] == "bool":
AnishGulati 0b69a9
        val = animated[i][0].attrib["value"]
AnishGulati 0b69a9
        if val == "false":
AnishGulati 0b69a9
            val = 0
AnishGulati 0b69a9
        else:
AnishGulati 0b69a9
            val = 1
AnishGulati 0b69a9
        pos = [val, get_frame(animated[i])]
AnishGulati 0b69a9
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "rectangle_size":
AnishGulati 1f6b9a
        pos = parse_value(animated, i)
AnishGulati 1f6b9a
        vec = Vector(pos[0], pos[1], animated.attrib["type"])
AnishGulati 1f6b9a
        vec.add_new_val(float(animated[i][0].attrib["value2"]) * settings.PIX_PER_UNIT)
AnishGulati 1f6b9a
        return vec
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "image_scale":
AnishGulati 1f6b9a
        val = float(animated[i][0].attrib["value"])
AnishGulati 1f6b9a
        val2 = get_frame(animated[i])
AnishGulati 1f6b9a
        vec = Vector(val, val2, animated.attrib["type"])
AnishGulati 1f6b9a
        vec.add_new_val(float(animated[i][0].attrib["value2"]))
AnishGulati 1f6b9a
        return vec
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "scale_layer_zoom":
AnishGulati 1f6b9a
        val = (math.e ** float(animated[i][0].attrib["value"])) * 100
AnishGulati 1f6b9a
        val2 = get_frame(animated[i])
AnishGulati 1f6b9a
        vec = Vector(val, val2, animated.attrib["type"])
AnishGulati 1f6b9a
        vec.add_new_val(val)
AnishGulati 1f6b9a
        return vec
AnishGulati 1f6b9a
AnishGulati 9ae542
    elif animated.attrib["type"] == "stretch_layer_scale":
AnishGulati 9ae542
        val1 = float(animated[i][0][0].text) * 100
AnishGulati 9ae542
        val3 = float(animated[i][0][1].text) * 100
AnishGulati 9ae542
        vec = Vector(val1, get_frame(animated[i]), animated.attrib["type"])
AnishGulati 9ae542
        vec.add_new_val(val3)
AnishGulati 9ae542
        return vec
AnishGulati 9ae542
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "group_layer_scale":
AnishGulati 1f6b9a
        val1 = float(animated[i][0][0].text) * 100
AnishGulati 1f6b9a
        val3 = float(animated[i][0][1].text) * 100
AnishGulati 1f6b9a
        vec = Vector(val1, get_frame(animated[i]), animated.attrib["type"])
AnishGulati 1f6b9a
        vec.add_new_val(val3)
AnishGulati 1f6b9a
        return vec
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "time":
AnishGulati 1f6b9a
        val = parse_time(animated[i][0].attrib["value"])    # Needed in seconds
AnishGulati 1f6b9a
        val2 = get_frame(animated[i])   # Needed in frames
AnishGulati 1f6b9a
        return Vector(val, val2, animated.attrib["type"])
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    elif animated.attrib["type"] == "color":
AnishGulati 1f6b9a
        red = float(animated[i][0][0].text)
AnishGulati 1f6b9a
        green = float(animated[i][0][1].text)
AnishGulati 1f6b9a
        blue = float(animated[i][0][2].text)
AnishGulati 1f6b9a
        alpha = float(animated[i][0][3].text)
AnishGulati 1f6b9a
        red = red ** (1/settings.GAMMA)
AnishGulati 1f6b9a
        green = green ** (1/settings.GAMMA)
AnishGulati 1f6b9a
        blue = blue ** (1/settings.GAMMA)
AnishGulati 1f6b9a
        return Color(red, green, blue, alpha)
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    return Vector(pos[0], pos[1], animated.attrib["type"])
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def parse_value(animated, i):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    To convert the synfig value parameter from units to pixels
AnishGulati 1f6b9a
    and also take into consideration the time parameter
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        animated (lxml.etree._Element) : Stores animation which holds waypoints
AnishGulati 1f6b9a
        i        (int)                 : Iterator for animation
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (list)  : [value, time] is returned
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    pos = [float(animated[i][0].attrib["value"]) * settings.PIX_PER_UNIT,
AnishGulati 1f6b9a
           get_frame(animated[i])]
AnishGulati 1f6b9a
    return pos
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def get_angle(theta):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Converts the .sif angle into lottie angle
AnishGulati 1f6b9a
    .sif uses positive x-axis as the start point and goes anticlockwise
AnishGulati 1f6b9a
    lottie uses positive y-axis as the start point and goes clockwise
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        theta (float) : Stores Synfig format angle
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (int)   : Lottie format angle
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    theta = int(theta)
AnishGulati 1f6b9a
    shift = -int(theta / 360)
AnishGulati 1f6b9a
    theta = theta % 360
AnishGulati 1f6b9a
    theta = (90 - theta) % 360
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    theta = theta + shift * 360
AnishGulati 1f6b9a
    return theta
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def is_animated(node):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Tells whether a parater is animated or not
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        node (lxml.etree._Element) : Animation which stores waypoints
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (int) : Depending upon whether the parameter is animated, following
AnishGulati 1f6b9a
                values are returned
AnishGulati 1f6b9a
                0: If not animated
AnishGulati 1f6b9a
                1: If only single waypoint is present
AnishGulati 1f6b9a
                2: If more than one waypoint is present
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    case = 0
AnishGulati 1f6b9a
    if node.tag == "animated":
AnishGulati 1f6b9a
        if len(node) == 1:
AnishGulati 1f6b9a
            case = 1
AnishGulati 1f6b9a
        else:
AnishGulati 1f6b9a
            case = 2
AnishGulati 1f6b9a
    else:
AnishGulati 1f6b9a
        case = 0
AnishGulati 1f6b9a
    return case
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def clamp_col(color):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    This function converts the colors into int and takes them to the range of
AnishGulati 1f6b9a
    0-255
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        color (float) : Synfig format color value
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (int) : Color value between 0-255
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    color = color ** (1/settings.GAMMA)
AnishGulati 1f6b9a
    color *= 255
AnishGulati 1f6b9a
    color = int(color)
AnishGulati 1f6b9a
    return max(0, min(color, 255))
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def get_color_hex(node):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Convert the <color></color> from rgba to hex format
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        node (lxml.etree._Element) : Synfig format color parameter
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (str) : hex format of color
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    red, green, blue = 1, 0, 0
AnishGulati 1f6b9a
    for col in node:
AnishGulati 1f6b9a
        if col.tag == "r":
AnishGulati 1f6b9a
            red = float(col.text)
AnishGulati 1f6b9a
        elif col.tag == "g":
AnishGulati 1f6b9a
            green = float(col.text)
AnishGulati 1f6b9a
        elif col.tag == "b":
AnishGulati 1f6b9a
            blue = float(col.text)
AnishGulati 1f6b9a
    # Convert to 0-255 range
AnishGulati 1f6b9a
    red, green, blue = clamp_col(red), clamp_col(green), clamp_col(blue)
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    # https://stackoverflow.com/questions/3380726/converting-a-rgb-color-tuple-to-a-six-digit-code-in-python/3380739#3380739
AnishGulati 1f6b9a
    ret = "#{0:02x}{1:02x}{2:02x}".format(red, green, blue)
AnishGulati 1f6b9a
    return ret
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def get_frame(waypoint):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Given a waypoint, it parses the time to frames
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        waypoint (lxml.etree._Element) : Synfig format waypoint
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (int) : the frame at which waypoint is present
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    time = get_time(waypoint)
AnishGulati 1f6b9a
    frame = time * settings.lottie_format["fr"]
AnishGulati 1f6b9a
    frame = round(frame)
AnishGulati 1f6b9a
    return frame
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def get_time(waypoint):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Given a waypoint, it parses the string time to float time
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        waypoint (lxml.etree._Element) : Synfig format waypoint
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (float) : the time in seconds at which the waypoint is present
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    return parse_time(waypoint.attrib["time"])
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def parse_time(time_in_str):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Given a string, it parses time to float time
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        time_in_str (str) : Time in string format
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (float) : the time in seconds at represented by the string
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    time = time_in_str.split(" ")
AnishGulati 1f6b9a
    final = 0
AnishGulati 1f6b9a
    for frame in time:
AnishGulati 1f6b9a
        if frame[-1] == "h":
AnishGulati 1f6b9a
            final += float(frame[:-1]) * 60 * 60
AnishGulati 1f6b9a
        elif frame[-1] == "m":
AnishGulati 1f6b9a
            final += float(frame[:-1]) * 60
AnishGulati 1f6b9a
        elif frame[-1] == "s":
AnishGulati 1f6b9a
            final += float(frame[:-1])
AnishGulati 1f6b9a
        elif frame[-1] == "f":  # This should never happen according to my code
AnishGulati 1f6b9a
            raise ValueError("In waypoint, time is never expected in frames.")
AnishGulati 1f6b9a
    return final
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def get_vector(waypoint):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Given a waypoint, it parses the string vector into Vector class defined in
luz.paz 7040b8
    this converter
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        waypoint (lxml.etree._Element) : Synfig format waypoint
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati a3579e
        (common.Vector.Vector) : x and y axis values stores in Vector format
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    # converting radius and angle to a vector
AnishGulati 1f6b9a
    if waypoint.tag == "radial_composite":
AnishGulati 1f6b9a
        for child in waypoint:
AnishGulati 1f6b9a
            if child.tag == "radius":
AnishGulati 1f6b9a
                radius = float(child[0].attrib["value"])
AnishGulati 1f6b9a
                radius *= settings.PIX_PER_UNIT
AnishGulati 1f6b9a
            elif child.tag == "theta":
AnishGulati 1f6b9a
                angle = float(child[0].attrib["value"])
AnishGulati 1f6b9a
        x, y = radial_to_tangent(radius, angle)
AnishGulati 1f6b9a
    else:
AnishGulati 1f6b9a
        x = float(waypoint[0][0].text)
AnishGulati 1f6b9a
        y = float(waypoint[0][1].text)
AnishGulati 1f6b9a
    return Vector(x, y)
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def radial_to_tangent(radius, angle):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Converts a tangent from radius and angle format to x, y axis co-ordinate
AnishGulati 1f6b9a
    system
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        radius (float) : radius of a vector
AnishGulati 1f6b9a
        angle  (float) : angle in degrees
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (float) : The x-coordinate of the vector
AnishGulati 1f6b9a
        (float) : The y-coordinate of the vector
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    angle = math.radians(angle)
AnishGulati 1f6b9a
    x = radius * math.cos(angle)
AnishGulati 1f6b9a
    y = radius * math.sin(angle)
AnishGulati 1f6b9a
    return x, y
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def set_vector(waypoint, pos):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    Given a waypoint and pos(Vector), it set's the waypoint's vectors
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        waypoint (lxml.etree._Element) : Synfig format waypoint
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (None)
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    waypoint[0][0].text = str(pos.val1)
AnishGulati 1f6b9a
    waypoint[0][1].text = str(pos.val2)
AnishGulati 1f6b9a
AnishGulati 1f6b9a
AnishGulati 1f6b9a
def modify_final_dump(obj):
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    This function will remove unwanted keys from the final dictionary and also
AnishGulati 1f6b9a
    modify the floats according to our precision
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Args:
AnishGulati 1f6b9a
        obj (float | dict | list | tuple) : The object to be modified
AnishGulati 1f6b9a
AnishGulati 1f6b9a
    Returns:
AnishGulati 1f6b9a
        (float | dict | list) : Depending upon arguments, obj type is decided
AnishGulati 1f6b9a
    """
AnishGulati 1f6b9a
    if isinstance(obj, float):
AnishGulati 1f6b9a
        return round(obj, settings.FLOAT_PRECISION)
AnishGulati 1f6b9a
    elif isinstance(obj, dict):
AnishGulati 1f6b9a
        return dict((k, modify_final_dump(v)) for k, v in obj.items() if k not in ["synfig_i", "synfig_o"])
AnishGulati 1f6b9a
    elif isinstance(obj, (list, tuple)):
AnishGulati 1f6b9a
        return list(map(modify_final_dump, obj))
AnishGulati 1f6b9a
    return obj