def cubic_to(vec, tan1, tan2, lottie, origin_cur, is_rectangle=False, constant_width=False): """ Will have to manipulate the tangents here, but they are not managed as tan1 and tan2 both are zero always Args: vec (common.Vector.Vector) : position of the point tan1 (common.Vector.Vector) : tangent 1 of the point tan2 (common.Vector.Vector) : tangent 2 of the point lottie (dict) : Final position and tangents will be stored here origin_cur (list) : value of the origin at specific frame Returns: (None) """ vec *= settings.PIX_PER_UNIT tan1 *= settings.PIX_PER_UNIT tan2 *= settings.PIX_PER_UNIT if constant_width: tan1, tan2 = convert_tangent_to_lottie(tan1, tan2) else: tan1, tan2 = convert_tangent_to_lottie(3 * tan1, 3 * tan2) pos = change_axis(vec[0], vec[1], not is_rectangle) for i in range(len(pos)): pos[i] += origin_cur[i] lottie["i"].append(tan1.get_list()) lottie["o"].append(tan2.get_list()) lottie["v"].append(pos)
def move_to(vec, lottie, origin_cur): """ Don't have to manipulate the tangents because all of them are zero here Args: vec (common.Vector.Vector) : position of the point lottie (dict) : Final position and tangents will be stored here origin_cur (list) : value of the origin at specific frame Returns: (None) """ vec *= settings.PIX_PER_UNIT lottie["i"].append([0, 0]) lottie["o"].append([0, 0]) pos = change_axis(vec[0], vec[1]) for i in range(len(pos)): pos[i] += origin_cur[i] lottie["v"].append(pos)
def gen_helpers_transform(lottie, layer, pos=settings.DEFAULT_POSITION, anchor=settings.DEFAULT_ANCHOR, scale=settings.DEFAULT_SCALE, rotation=settings.DEFAULT_ROTATION, opacity=settings.DEFAULT_OPACITY): """ Generates the dictionary corresponding to helpers/transform.json Args: lottie (dict) : Lottie format layer layer (lxml.etree._Element) : Synfig format layer pos (:obj: `list | lxml.etree._Element`, optional) : position of layer anchor (:obj: `list | lxml.etree._Element`, optional) : anchor point of layer scale (:obj: `list | lxml.etree._Element`, optional) : scale of layer rotation (:obj: `float | lxml.etree._Element`, optional) : rotation of layer opacity (:obj: `float | lxml.etree._Element`, optional) : Opacity of layer Returns: (None) """ index = Count() lottie["o"] = {} # opacity/Amount lottie["r"] = {} # Rotation of the layer lottie["p"] = {} # Position of the layer lottie["a"] = {} # Anchor point of the layer lottie["s"] = {} # Scale of the layer # setting the position if isinstance(pos, list): gen_properties_value(lottie["p"], pos, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: is_animate = is_animated(pos) if is_animate == 2: gen_properties_multi_dimensional_keyframed(lottie["p"], pos, index.inc()) else: x_val, y_val = 0, 0 if is_animate == 0: x_val = float(pos[0].text) * settings.PIX_PER_UNIT y_val = float(pos[1].text) * settings.PIX_PER_UNIT else: x_val = float(pos[0][0][0].text) * settings.PIX_PER_UNIT y_val = float(pos[0][0][1].text) * settings.PIX_PER_UNIT gen_properties_value(lottie["p"], change_axis(x_val, y_val, True), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the opacity if isinstance(opacity, (float, int)): gen_properties_value(lottie["o"], opacity, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: is_animate = is_animated(opacity) if is_animate == 2: opacity.attrib["type"] = "opacity" gen_value_Keyframed(lottie["o"], opacity, index.inc()) else: if is_animate == 0: val = float( opacity.attrib["value"]) * settings.OPACITY_CONSTANT else: val = float( opacity[0][0].attrib["value"]) * settings.OPACITY_CONSTANT gen_properties_value(lottie["o"], val, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the rotation if isinstance(rotation, (float, int)): gen_properties_value(lottie["r"], rotation, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: is_animate = is_animated(rotation) if is_animate == 2: gen_value_Keyframed(lottie["r"], rotation, index.inc()) else: theta = 0 # default rotation if is_animate == 0: theta = (float(rotation.attrib["value"])) else: theta = (float(rotation[0][0].attrib["value"])) gen_properties_value(lottie["r"], theta, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the anchor point if isinstance(anchor, list): gen_properties_value(lottie["a"], anchor, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: is_animate = is_animated(anchor) if is_animate == 2: gen_properties_multi_dimensional_keyframed(lottie["a"], anchor, index.inc()) else: x_val, y_val = 0, 0 if is_animate == 0: x_val = float(anchor[0].text) * settings.PIX_PER_UNIT y_val = float(anchor[1].text) * settings.PIX_PER_UNIT else: x_val = float(anchor[0][0][0].text) * settings.PIX_PER_UNIT y_val = float(anchor[0][0][1].text) * settings.PIX_PER_UNIT gen_properties_value(lottie["a"], change_axis(x_val, y_val, True), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # setting the scale if isinstance(scale, list): gen_properties_value(lottie["s"], scale, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # This means scale parameter is animated else: is_animate = is_animated(scale) if is_animate == 2: gen_value_Keyframed(lottie["s"], scale, index.inc()) else: zoom = 0 if is_animate == 0: zoom = float(scale.attrib["value"]) else: zoom = float(scale[0][0].attrib["value"]) zoom = (math.e**zoom) * 100 gen_properties_value(lottie["s"], [zoom, zoom], index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO)
def gen_shapes_circle(lottie, layer, idx): """ Generates the dictionary corresponding to shapes/ellipse.json where ellipse will always be considered as circle Args: lottie (dict) : The lottie generated circle layer will be stored in it layer (lxml.etree._Element): Synfig format circle layer idx (int) : Stores the index of the circle layer Returns: (None) """ index = Count() lottie["ty"] = "el" # Type: circle lottie["p"] = {} # Position of circle lottie["d"] = settings.DEFAULT_DIRECTION lottie["s"] = {} # Size of circle lottie["ix"] = idx # setting the index for child in layer: if child.tag == "param": if child.attrib["name"] in {"origin", "center"}: is_animate = is_animated(child[0]) if is_animate == 2: gen_properties_multi_dimensional_keyframed( lottie["p"], child[0], index.inc()) else: x_val, y_val = 0, 0 if is_animate == 0: x_val = float(child[0][0].text) * settings.PIX_PER_UNIT y_val = float(child[0][1].text) * settings.PIX_PER_UNIT else: x_val = float( child[0][0][0][0].text) * settings.PIX_PER_UNIT y_val = float( child[0][0][0][1].text) * settings.PIX_PER_UNIT gen_properties_value(lottie["p"], change_axis(x_val, y_val), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # This will be exported as size of ellipse in lottie format elif child.attrib["name"] == "radius": is_animate = is_animated(child[0]) if is_animate == 2: child[0].attrib['type'] = "circle_radius" gen_value_Keyframed(lottie["s"], child[0], index.inc()) else: radius = 0 # default value for radius if is_animate == 0: radius = float(child[0].attrib["value"]) else: radius = float(child[0][0][0].attrib["value"]) radius_pix = int(settings.PIX_PER_UNIT) * radius diam = radius_pix * 2 gen_properties_value(lottie["s"], [diam, diam], index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO)
def gen_properties_offset_keyframe(curve_list, animated, i): """ Generates the dictionary corresponding to properties/offsetKeyFrame.json Args: curve_list (list) : Stores bezier curve in Lottie format animated (lxml.etree._Element) : Synfig format animation i (int) : Iterator for animation Returns: (TypeError) : If a constant interval is encountered (None) : In all other cases """ lottie = curve_list[-1] waypoint, next_waypoint = animated[i], animated[i + 1] cur_get_after, next_get_before = waypoint.attrib[ "after"], next_waypoint.attrib["before"] cur_get_before, next_get_after = waypoint.attrib[ "before"], next_waypoint.attrib["after"] # "angle" interpolations never call this function, can be removed by confirming if animated.attrib["type"] == "angle": if cur_get_after == "auto": cur_get_after = "linear" if cur_get_before == "auto": cur_get_before = "linear" if next_get_before == "auto": next_get_before = "linear" if next_get_after == "auto": next_get_after = "linear" # Synfig only supports constant interpolations for points # "points" never call this function, can be removed by confirming if animated.attrib["type"] == "points": cur_get_after = "constant" cur_get_before = "constant" next_get_after = "constant" next_get_before = "constant" # Calculate positions of waypoints cur_pos = parse_position(animated, i) next_pos = parse_position(animated, i + 1) lottie["i"] = {} # Time bezier curve, not used in synfig lottie["o"] = {} # Time bezier curve, not used in synfig lottie["i"]["x"] = 0.5 lottie["i"]["y"] = 0.5 lottie["o"]["x"] = 0.5 lottie["o"]["y"] = 0.5 if cur_get_after == "halt": # For ease out ease_out(lottie) if next_get_before == "halt": # For ease in ease_in(lottie) lottie["t"] = get_frame(waypoint) is_transform_axis = False if "transform_axis" in animated.keys(): is_transform_axis = True lottie["s"] = change_axis(cur_pos[0], cur_pos[1], is_transform_axis) lottie["e"] = change_axis(next_pos[0], next_pos[1], is_transform_axis) lottie["to"] = [] lottie["ti"] = [] # Calculating the unchanged tangent try: out_val, in_val = calc_tangent(animated, lottie, i) except Exception as excep: # This means constant interval return excep # This module is only needed for origin animation lottie["to"] = out_val.get_list() lottie["ti"] = in_val.get_list() # TCB/!TCB and list is not empty if cur_get_before == "auto" and cur_get_after != "auto" and i > 0: curve_list[-2]["ti"] = copy.deepcopy(lottie["to"]) curve_list[-2]["ti"] = [ -item / settings.TANGENT_FACTOR for item in curve_list[-2]["ti"] ] curve_list[-2]["ti"][1] = -curve_list[-2]["ti"][1] if cur_get_after == "halt": curve_list[-2]["i"]["x"] = settings.IN_TANGENT_X curve_list[-2]["i"]["y"] = settings.IN_TANGENT_Y # Lottie tangent length is larger than synfig lottie["ti"] = [item / settings.TANGENT_FACTOR for item in lottie["ti"]] lottie["to"] = [item / settings.TANGENT_FACTOR for item in lottie["to"]] # Lottie and synfig use different tangents SEE DOCUMENTATION lottie["ti"] = [-item for item in lottie["ti"]] # IMPORTANT to and ti have to be relative # The y-axis is different in lottie lottie["ti"][1] = -lottie["ti"][1] lottie["to"][1] = -lottie["to"][1] # These tangents will be used in actual calculation of points according to # Synfig lottie["synfig_to"] = [tangent for tangent in lottie["to"]] lottie["synfig_ti"] = [-tangent for tangent in lottie["ti"]] if cur_get_after == "halt": lottie["synfig_to"] = [0 for val in lottie["synfig_to"]] if next_get_before == "halt": lottie["synfig_ti"] = [0 for val in lottie["synfig_ti"]]
def gen_shapes_star(lottie, layer, idx): """ Generates the dictionary corresponding to shapes/star.json Args: lottie (dict) : The lottie generated star layer will be stored in it layer (common.Layer.Layer) : Synfig format star layer idx (int) : Stores the index of the star layer Returns: (None) """ index = Count() lottie["ty"] = "sr" # Type: star lottie["pt"] = {} # Number of points on the star lottie["p"] = {} # Position of star lottie["r"] = {} # Angle / Star's rotation lottie["ir"] = {} # Inner radius lottie["or"] = {} # Outer radius lottie["is"] = {} # Inner roundness of the star lottie["os"] = {} # Outer roundness of the star regular_polygon = {"prop": "false"} # Regular polygon rp = layer.get_param("regular_polygon").get() is_animate = is_animated(rp[0]) if is_animate == 2: regular_polygon["prop"] = "changing" regular_polygon["animated"] = rp[0] elif is_animate == 1: regular_polygon["prop"] = rp[0][0][0].attrib["value"] else: regular_polygon["prop"] = rp[0].attrib["value"] regular_polygon["animate"] = is_animate # Points points = layer.get_param("points").get() is_animate = is_animated(points[0]) if is_animate == 2: # To uniquely identify the points, attribute type is changed points[0].attrib['type'] = 'points' gen_value_Keyframed(lottie["pt"], points[0], index.inc()) else: num_points = 3 # default number of points if is_animate == 0: num_points = int(points[0].attrib["value"]) else: num_points = int(points[0][0][0].attrib["value"]) gen_properties_value(lottie["pt"], num_points, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # Angle angle = layer.get_param("angle").get() is_animate = is_animated(angle[0]) if is_animate == 2: gen_value_Keyframed(lottie["r"], angle[0], index.inc()) else: theta = 0 # default angle for the star if is_animate == 0: theta = get_angle(float(angle[0].attrib["value"])) else: theta = get_angle(float(angle[0][0][0].attrib["value"])) gen_properties_value(lottie["r"], theta, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # Radius1 radius1 = layer.get_param("radius1").get() is_animate = is_animated(radius1[0]) if is_animate == 2: gen_value_Keyframed(lottie["or"], radius1[0], index.inc()) else: r_outer = 0 # default value for outer radius if is_animate == 0: r_outer = float(radius1[0].attrib["value"]) else: r_outer = float(radius1[0][0][0].attrib["value"]) gen_properties_value(lottie["or"], int(settings.PIX_PER_UNIT * r_outer), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # Radius2 radius2 = layer.get_param("radius2").get() is_animate = is_animated(radius2[0]) if is_animate == 2: gen_value_Keyframed(lottie["ir"], radius2[0], index.inc()) else: r_inner = 0 # default value for inner radius if is_animate == 0: r_inner = float(radius2[0].attrib["value"]) else: r_inner = float(radius2[0][0][0].attrib["value"]) gen_properties_value(lottie["ir"], int(settings.PIX_PER_UNIT * r_inner), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # Origin origin = layer.get_param("origin").get() is_animate = is_animated(origin[0]) if is_animate == 2: gen_properties_multi_dimensional_keyframed(lottie["p"], origin[0], index.inc()) else: x_val, y_val = 0, 0 if is_animate == 0: x_val = float(origin[0][0].text) * settings.PIX_PER_UNIT y_val = float(origin[0][1].text) * settings.PIX_PER_UNIT else: x_val = float(origin[0][0][0][0].text) * settings.PIX_PER_UNIT y_val = float(origin[0][0][0][1].text) * settings.PIX_PER_UNIT gen_properties_value(lottie["p"], change_axis(x_val, y_val), index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) # If not animated, then go to if, else if regular_polygon["animate"] in {0, 1}: if regular_polygon["prop"] == "false": lottie["sy"] = 1 # Star Type # inner property is only needed if type is star gen_properties_value(lottie["is"], 0, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) else: lottie["sy"] = 2 # Polygon Type # for polygon type, "ir" and "is" must not be present del lottie["ir"] # If animated, it will always be of type star else: lottie["sy"] = 1 gen_properties_value(lottie["is"], 0, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) gen_properties_value(lottie["os"], 0, index.inc(), settings.DEFAULT_ANIMATED, settings.NO_INFO) lottie["ix"] = idx