def gen_value_Keyframed(lottie, animated, idx): """ Generates the dictionary corresponding to properties/valueKeyframed.json in lottie documentation Args: lottie (dict) : Lottie bezier curve stored in this animated (lxml.etree._Element) : Synfig format animation idx (int) : Index of animation Returns: (None) """ lottie["ix"] = idx lottie["a"] = 1 lottie["k"] = [] for i in range(len(animated) - 1): lottie["k"].append({}) gen_value_Keyframe(lottie["k"], animated, i) last_waypoint_frame = get_frame(animated[-1]) lottie["k"].append({}) lottie["k"][-1]["t"] = last_waypoint_frame if "h" in lottie["k"][-2].keys(): lottie["k"][-1]["h"] = 1 lottie["k"][-1]["s"] = lottie["k"][-2]["e"] # specific case for points when prev_points > cur_points if animated.attrib["type"] == "points": if lottie["k"][-2]["s"][0] > lottie["k"][-1]["s"][0]: # Adding 1 frame to the previous time prev_frames = get_frame(animated[-2]) lottie["k"][-1]["t"] = prev_frames + 1 time_adjust(lottie, animated)
def calc_pos_and_size(size_animated, pos_animated, animated_1, animated_2, orig_path, i, i1): """ Between two frames, this function is called if either "only point1's interval is constant" or "only point2's interval is constant". It calculates the position and size property for lottie format. It also adds a new waypoint just before the end of the frame if required. param3: Can be param1 or param2 of rectangle layer param4: Can be param2 or param1 of rectangle layer, but opposite of param3 Args: size_animated (lxml.etree._Element): Holds the size parameter of rectangle layer in Synfig format pos_animated (lxml.etree._Element): Holds the position parameter of rectangle layer in Synfig format animated_1 (lxml.etree._Element): Holds the param3 in Synfig format animated_2 (lxml.etree._Element): Holds the param4 in Synfig format orig_path (dict) : Holds the param4 in Lottie format i (int) : Iterator for animated_2 i1 (int) : Iterator for pos_animated and size_animated Returns: (int, int) : Updated iterators i and i1 are returned """ pos_animated[i1].attrib["after"] = animated_2[i].attrib["after"] size_animated[i1].attrib["after"] = animated_2[i].attrib["after"] copy_tcb(pos_animated[i1], animated_2[i]) copy_tcb(size_animated[i1], animated_2[i]) get_average(pos_animated[i1], animated_1[i], animated_2[i]) get_difference(size_animated[i1], animated_1[i], animated_2[i]) # Inserting a waypoint just before the nextwaypoint # Only if a waypoint can be inserted t_next = get_frame(animated_2[i + 1]) t_present = get_frame(animated_2[i]) ######### Need to check if t_next - t_present < 2 ##### if abs(t_next - t_present) >= 2: pos = get_vector_at_frame(orig_path, t_next - 1) pos = to_Synfig_axis(pos, "vector") new_waypoint = copy.deepcopy(pos_animated[i1]) new_waypoint.attrib["before"] = new_waypoint.attrib["after"] new_waypoint.attrib["time"] = str( (t_next - 1) / settings.lottie_format["fr"]) + "s" new_waypoint[0][0].text, new_waypoint[0][1].text = str(pos[0]), str( pos[1]) n_size_waypoint = copy.deepcopy(new_waypoint) get_average(new_waypoint, new_waypoint, animated_1[i]) get_difference(n_size_waypoint, n_size_waypoint, animated_1[i]) pos_animated.insert(i1 + 1, new_waypoint) size_animated.insert(i1 + 1, n_size_waypoint) i1 += 1 i, i1 = i + 1, i1 + 1 get_average(pos_animated[i1], animated_1[i], animated_2[i]) get_difference(size_animated[i1], animated_1[i], animated_2[i]) return i, i1
def clamped_tangent(p1, p2, p3, animated, i): """ Function corresponding to clamped function in Synfig It generates the tangent when clamped waypoints are used Args: p1 (float) : First point p2 (float) : Second point p3 (float) : Third point animated (lxml.etree._Element) : Synfig format animation i (int) : Iterator over animation Returns: (float) : Clamped tangent is returned """ # pw -> prev_waypoint, w -> waypoint, nw -> next_waypoint pw, w, nw = animated[i - 1], animated[i], animated[i + 1] t1 = get_frame(pw) t2 = get_frame(w) t3 = get_frame(nw) bias = 0.0 tangent = 0.0 pm = p1 + (p3 - p1) * (t2 - t1) / (t3 - t1) if p3 > p1: if p2 >= p3 or p2 <= p1: tangent = tangent * 0.0 else: if p2 > pm: bias = (pm - p2) / (p3 - pm) elif p2 < pm: bias = (pm - p2) / (pm - p1) else: bias = 0.0 tangent = (p2 - p1) * (1.0 + bias) / 2.0 + (p3 - p2) * (1.0 - bias) / 2.0 elif p1 > p2: if p2 >= p1 or p2 <= p3: tangent = tangent * 0.0 else: if p2 > pm: bias = (pm - p2) / (pm - p1) elif p2 < pm: bias = (pm - p2) / (p3 - pm) else: bias = 0.0 tangent = (p2 - p1) * (1.0 + bias) / 2.0 + (p3 - p2) * (1.0 - bias) / 2.0 else: tangent = tangent * 0.0 return tangent
def gen_properties_multi_dimensional_keyframed(lottie, animated, idx): """ Generates the dictionary corresponding to properties/multiDimensionalKeyframed.json Args: lottie (dict) : Lottie generated keyframes will be stored here animated (lxml.etree._Element) : Synfig format animation idx (int) : Index/Count of animation Returns: (None) """ lottie["a"] = 1 lottie["ix"] = idx lottie["k"] = [] for i in range(len(animated) - 1): lottie["k"].append({}) gen_properties_offset_keyframe(lottie["k"], animated, i) last_waypoint_frame = get_frame(animated[-1]) lottie["k"].append({}) lottie["k"][-1]["t"] = last_waypoint_frame if "h" in lottie["k"][-2].keys(): lottie["k"][-1]["h"] = 1 lottie["k"][-1]["s"] = lottie["k"][-2]["e"] # Time adjust of the curves time_adjust(lottie, animated)
def gen_hold_waypoints(inactive_time, layer, anim_type): """ Will only be used to modify opacity waypoints, and set zero values where the layer is inactive Args: inactive_time (set) : Range of time when the layer will be inactive layer (common.Layer.Layer) : Synfig format layer anim_type (str) : Specifies whether it is effects_opacity or opacity (it will effect a factor of 100) Returns: (common.Param.Param) : Modified opacity animation is returned """ opacity = layer.get_param("amount") opacity.animate(anim_type) opacity_dict = {} opacity_dict["o"] = {} opacity.fill_path(opacity_dict, "o") opacity_dict = opacity_dict["o"] for it in inactive_time: # First add waypoints at both points, make it constant interval # then remove any in-between waypoints first = round(it[0] * settings.lottie_format["fr"]) second = round(it[1] * settings.lottie_format["fr"]) insert_waypoint_at_frame(opacity[0], opacity_dict, first, anim_type) insert_waypoint_at_frame(opacity[0], opacity_dict, second, anim_type) # Making it a constant interval for waypoint in opacity[0]: if approximate_equal(get_frame(waypoint), first): st_waypoint = waypoint break st_waypoint.attrib["after"] = "constant" st_waypoint[0].attrib["value"] = str(0) # removing the in between waypoints for waypoint in opacity[0]: this_frame = get_frame(waypoint) if (not approximate_equal(this_frame, first)) and \ (not approximate_equal(this_frame, second)) and \ (this_frame > first and this_frame < second): waypoint.getparent().remove(waypoint) return opacity
def gen_hold_waypoints(deactive_time, layer, anim_type): """ Will only be used to modify opacity waypoints, and set zero values where the layer is deactive Args: deactive_time (set) : Range of time when the layer will be deactive layer (lxml.etree._Element) : Synfig format layer anim_type (str) : Specifies whether it is effects_opacity or opacity (it will effect a factor of 100) Returns: (lxml.etree._Element) : Modified opacity animation is returned """ for chld in layer: if chld.tag == "param" and chld.attrib["name"] == "amount": opacity = chld opacity = gen_dummy_waypoint(opacity, "param", anim_type, "amount") opacity_dict = {} gen_value_Keyframed(opacity_dict, opacity[0], 0) for it in deactive_time: # First add waypoints at both points, make it constant interval # then remove any in-between waypoints first = round(it[0] * settings.lottie_format["fr"]) second = round(it[1] * settings.lottie_format["fr"]) insert_waypoint_at_frame(opacity[0], opacity_dict, first, anim_type) insert_waypoint_at_frame(opacity[0], opacity_dict, second, anim_type) # Making it a constant interval for waypoint in opacity[0]: if approximate_equal(get_frame(waypoint), first): st_waypoint = waypoint break st_waypoint.attrib["after"] = "constant" st_waypoint[0].attrib["value"] = str(0) # removing the in between waypoints for waypoint in opacity[0]: this_frame = get_frame(waypoint) if (not approximate_equal(this_frame, first)) and \ (not approximate_equal(this_frame, second)) and \ (this_frame > first and this_frame < second): waypoint.getparent().remove(waypoint) return opacity
def waypoint_at_frame(anim, frame): """ Returns true if a waypoint is present at 'frame' """ for waypoint in anim: fr = get_frame(waypoint) if fr == frame: return True return False
def get_bool_at_frame(anim, frame): """ Calculates the boolean value at a given frame, given a boolean animation Args: anim (lxml.etree._Element): Boolean animation frame (int) : Frame at which the value is to be calculated Returns: (bool) : True if the value is "true" at that frame : False otherwise """ i = 0 while i < len(anim): cur_fr = get_frame(anim[i]) if frame <= cur_fr: break i += 1 if i == 0: val = anim[0][0].attrib["value"] elif i < len(anim): # frame lies between i-1 and i'th value of animation prev = anim[i - 1][0].attrib["value"] cur = anim[i][0].attrib["value"] cur_frame = get_frame(anim[i]) if frame == cur_frame: val = cur else: if prev == "true" and cur == "false": val = prev elif prev == "false" and cur == "true": val = cur elif prev == "true" and cur == "true": val = cur elif prev == "false" and cur == "false": val = cur elif i >= len(anim): val = anim[-1][0].attrib["value"] if val == "false": val = False else: val = True return val
def get_cross_list(animation_1, animation_2, orig_path_1, orig_path_2): """ This function will return a list('set' technically) at which the point1 and point2 of rectangle will cross each other. This set might contain frames at which waypoints are already present, hence this need to be taken care of Args: animation_1 (lxml.etree._Element): Stores the animation of `point1` parameter in Synfig format animation_2 (lxml.etree._Element): Stores the animation of `point2` parameter in Synfig format orig_path_1 (dict) : Stores the animation of `point1` parameter in Lottie format orig_path_2 (dict) : Stores the animation of `point2` parameter in Lottie format Returns: (set) : Contains the frames at which point1 and point2 cross each other """ en_fr = max(get_frame(animation_1[-1]), get_frame(animation_2[-1])) # Function to determine the sign of a variable sign = lambda a: (1, -1)[a < 0] prev_1 = float(animation_1[0][0][0].text), float(animation_1[0][0][1].text) prev_2 = float(animation_2[0][0][0].text), float(animation_2[0][0][1].text) # The list to be returned ret_list = set() frame = 1 # Loop for all the frames while frame <= en_fr: now_1 = get_vector_at_frame(orig_path_1, frame) now_1 = to_Synfig_axis(now_1, "vector") now_2 = get_vector_at_frame(orig_path_2, frame) now_2 = to_Synfig_axis(now_2, "vector") is_needed = False if sign(prev_1[0] - prev_2[0]) != sign(now_1[0] - now_2[0]): is_needed = True elif sign(prev_1[1] - prev_2[1]) != sign(now_1[1] - now_2[1]): is_needed = True if is_needed: ret_list.add(frame - 1) ret_list.add(frame) prev_1, prev_2 = now_1, now_2 frame += 1 return ret_list
def gen_image_scale(animated_1, animated_2, width, height): """ In Synfig, no scale parameter is available for image layer, so it will be created here for Lottie conversion Args: animated_1 (lxml.etree._Element): point1 animation in Synfig format animated_2 (lxml.etree._Element): point2 animation in Synfig format width (int) : Width of the original image height (int) : Height of the original image Returns: (lxml.etree._Element) : Scale parameter in Synfig format """ st = '<param name="image_scale"><real value="0.0000000000"/></param>' root = etree.fromstring(st) root = gen_dummy_waypoint(root, "param", "image_scale") anim1_path, anim2_path = {}, {} gen_properties_multi_dimensional_keyframed(anim1_path, animated_1, 0) gen_properties_multi_dimensional_keyframed(anim2_path, animated_2, 0) # Filling the first 2 frames with there original scale values fill_image_scale_at_frame(root[0], anim1_path, anim2_path, width, height, 0) fill_image_scale_at_frame(root[0], anim1_path, anim2_path, width, height, 1) mx_fr = max(get_frame(animated_1[-1]), get_frame(animated_2[-1])) fr = 2 while fr <= mx_fr: new_waypoint = copy.deepcopy(root[0][0]) time = fr / settings.lottie_format["fr"] time = str(time) + "s" new_waypoint.attrib["time"] = time root[0].append(new_waypoint) fill_image_scale_at_frame(root[0], anim1_path, anim2_path, width, height, fr) fr += 1 return root
def insert_waypoint_at_frame(animated, orig_path, frame, animated_name): """ This function will only insert a waypoint at 'frame' if no waypoint is present at that 'frame' already Args: animated (lxml.etree._Element): Holds the animation in Synfig xml format orig_path (dict) : Holds the animation in Lottie format frame (int) : The frame at which the waypoint is to be inserted animated_name (str) : The name/type of animation Returns: (None) """ i = 0 while i < len(animated): at_frame = get_frame(animated[i]) if approximate_equal(frame, at_frame): return elif frame < at_frame: break i += 1 pos = get_vector_at_frame(orig_path, frame) pos = to_Synfig_axis(pos, animated_name) if i == len(animated): new_waypoint = copy.deepcopy(animated[i - 1]) else: new_waypoint = copy.deepcopy(animated[i]) if animated_name == "vector": new_waypoint[0][0].text = str(pos[0]) new_waypoint[0][1].text = str(pos[1]) else: new_waypoint[0].attrib["value"] = str(pos) new_waypoint.attrib["time"] = str( frame / settings.lottie_format["fr"]) + "s" if i == 0 or i == len(animated): # No need of tcb value copy as halt interpolation need to be copied here new_waypoint.attrib["before"] = new_waypoint.attrib[ "after"] = "constant" else: copy_tcb_average(new_waypoint, animated[i], animated[i - 1]) new_waypoint.attrib["before"] = animated[i - 1].attrib["after"] new_waypoint.attrib["after"] = animated[i].attrib["before"] # If the interval was constant before, then the whole interval should # remain constant now also if new_waypoint.attrib["before"] == "constant" or new_waypoint.attrib[ "after"] == "constant": new_waypoint.attrib["before"] = new_waypoint.attrib[ "after"] = "constant" animated.insert(i, new_waypoint)
def gen_dummy_waypoint(non_animated, animated_tag, anim_type, animated_name="anything"): """ Makes a non animated parameter to animated parameter by creating a new dummy waypoint with constant animation Args: non_animated (lxml.etree._Element): Holds the non-animated parameter in Synfig xml format animated_tag(str) : Decides the tag of the animation anim_type (str) : Decides the animation type Returns: (lxml.etree._Element) : Updated non-animated parameter, which is now animated """ is_animate = is_animated(non_animated[0]) if is_animate == settings.ANIMATED: # If already animated, no need to add waypoints # Forcibly set it's animation type to the given anim_type :needed in:-> # properties/shapePropKeyframe.py #31 non_animated[0].attrib["type"] = anim_type return non_animated elif is_animate == settings.NOT_ANIMATED: st = '<{animated_tag} name="{animated_name}"><animated type="{anim_type}"><waypoint time="0s" before="constant" after="constant"></waypoint></animated></{animated_tag}>' st = st.format(anim_type=anim_type, animated_name=animated_name, animated_tag=animated_tag) root = etree.fromstring(st) root[0][0].append(copy.deepcopy(non_animated[0])) non_animated = root elif is_animate == settings.SINGLE_WAYPOINT: # Forcibly set it's animation type to the given anim_type non_animated[0].attrib["type"] = anim_type non_animated[0][0].attrib["before"] = non_animated[0][0].attrib[ "after"] = "constant" new_waypoint = copy.deepcopy(non_animated[0][0]) frame = get_frame(non_animated[0][0]) frame += 1 time = frame / settings.lottie_format["fr"] time = str(time) + "s" new_waypoint.attrib["time"] = time non_animated[0].insert(1, new_waypoint) return non_animated
def update_frame_window(node, window): """ Given an animation, finds the minimum and maximum frame at which the waypoints are located Args: node (lxml.etree._Element) : Animation to be searched in window (dict) : max and min frame will be stored in this Returns: (None) """ if is_animated(node) == 2: for waypoint in node: fr = get_frame(waypoint) if fr > window["last"]: window["last"] = fr if fr < window["first"]: window["first"] = fr
def get_animated_time_list(child, time_list): """ Appends all the frames corresponding to the waypoints in the animated(child[0]) list, in time_list Args: child (lxml.etree._Element) : Parent Element of animation time_list (set) : Will store all the frames at which waypoints are present Returns: (None) """ animated = child[0] is_animate = is_animated(animated) if is_animate in {settings.NOT_ANIMATED, settings.SINGLE_WAYPOINT}: return for waypoint in animated: frame = get_frame(waypoint) time_list.add(frame)
def modify_bool_animation(anim): """ Inserts waypoints at such frames so that the animation is similar to that in lottie """ i = 0 while i < len(anim): cur_fr = get_frame(anim[i]) val_now = get_bool_at_frame(anim, cur_fr) # Check at one frame less and one frame more: only cases if cur_fr - 1 >= settings.lottie_format[ "ip"] and not waypoint_at_frame(anim, cur_fr - 1): val_before = get_bool_at_frame(anim, cur_fr - 1) if val_now != val_before: new = copy.deepcopy(anim[i]) new.attrib["time"] = str( (cur_fr - 1) / settings.lottie_format["fr"]) + "s" if val_before: new[0].attrib["value"] = "true" else: new[0].attrib["value"] = "false" anim.insert(i, new) i += 1 if cur_fr + 1 <= settings.lottie_format[ "op"] and not waypoint_at_frame(anim, cur_fr + 1): val_after = get_bool_at_frame(anim, cur_fr + 1) if val_after != val_now: new = copy.deepcopy(anim[i]) new.attrib["time"] = str( (cur_fr + 1) / settings.lottie_format["fr"]) + "s" if val_after: new[0].attrib["value"] = "true" else: new[0].attrib["value"] = "false" anim.insert(i + 1, new) i += 1 i += 1 # Make the animation constant for waypoint in anim: waypoint.attrib["before"] = waypoint.attrib["after"] = "constant"
def both_points_animated(animated_1, animated_2, param_expand, lottie, index): """ This function generates the lottie dictionary for position and size property of lottie(point1 and point2 are used from Synfig), when both point1 and point2 are animated Args: animated_1 (lxml.etree._Element): Holds the parameter `point1`'s animation in Synfig xml format animated_2 (lxml.etree._Element): Holds the parameter `point2`'s animation in Synfig xml format param_expand (lxml.etree._Element): Holds the parameter `expand`'s animation in Synfig xml format lottie (dict) : Lottie format rectangle layer will be store in this index (int) : Stores the index of parameters in rectangle layer Returns: (None) """ animated_1, animated_2 = animated_1[0], animated_2[0] orig_path_1, orig_path_2 = {}, {} expand_path = {} gen_value_Keyframed(expand_path, param_expand[0], 0) gen_properties_multi_dimensional_keyframed(orig_path_1, animated_1, 0) gen_properties_multi_dimensional_keyframed(orig_path_2, animated_2, 0) #################### SECTION 1 ########################### # Insert waypoints in the point1 and point2 parameter at the place where # expand parameter is animated time_list = set() get_animated_time_list(param_expand, time_list) for frame in time_list: insert_waypoint_at_frame(animated_1, orig_path_1, frame, "vector") insert_waypoint_at_frame(animated_2, orig_path_2, frame, "vector") #################### END OF SECTION 1 #################### ### SECTION TRY ### # Every frames value is precomputed in order to achieve maximum similarity # to that of Synfig st_fr = min(get_frame(animated_1[0]), get_frame(animated_2[0])) en_fr = max(get_frame(animated_1[-1]), get_frame(animated_2[-1])) fra = st_fr while fra <= en_fr: insert_waypoint_at_frame(animated_1, orig_path_1, fra, "vector") insert_waypoint_at_frame(animated_2, orig_path_2, fra, "vector") fra += 1 ### END SECTION ### ######################### SECTION 2 ########################## # Insert the waypoints at corresponding positions where point1 is animated # and point2 is animated for waypoint in animated_1: frame = get_frame(waypoint) insert_waypoint_at_frame(animated_2, orig_path_2, frame, "vector") for waypoint in animated_2: frame = get_frame(waypoint) insert_waypoint_at_frame(animated_1, orig_path_1, frame, "vector") ##################### END OF SECTION 2 ####################### ##################### SECTION 3 ############################## # Add the impact of expand parameter amount towards point1 and point2 # parameter assert len(animated_1) == len(animated_2) for waypoint1, waypoint2 in zip(animated_1, animated_2): frame = get_frame(waypoint1) assert frame == get_frame(waypoint2) expand_amount = get_vector_at_frame(expand_path, frame) expand_amount = to_Synfig_axis(expand_amount, "real") pos1, pos2 = get_vector(waypoint1), get_vector(waypoint2) # Comparing the x-coordinates if pos1[0] > pos2[0]: pos1[0] += expand_amount pos2[0] -= expand_amount else: pos1[0] -= expand_amount pos2[0] += expand_amount # Comparing the y-coordinates if pos1[1] > pos2[1]: pos1[1] += expand_amount pos2[1] -= expand_amount else: pos1[1] -= expand_amount pos2[1] += expand_amount set_vector(waypoint1, pos1) set_vector(waypoint2, pos2) ##################### END OF SECTION 3 ####################### #################### SECTION 4 ############################# # Place waypoints at which the x and y cross each other/cross the extremas cross_list = get_cross_list(animated_1, animated_2, orig_path_1, orig_path_2) for frame in cross_list: insert_waypoint_at_frame(animated_1, orig_path_1, frame, "vector") insert_waypoint_at_frame(animated_2, orig_path_2, frame, "vector") #################### END SECTION 4 ######################### ################## SECTION 5 ################################################ # Store the position of rectangle according to the waypoints in pos_animated # Store the size of rectangle according to the waypoints in size_animated pos_animated = copy.deepcopy(animated_1) size_animated = copy.deepcopy(animated_1) size_animated.attrib["type"] = "rectangle_size" i, i1 = 0, 0 while i < len(animated_1) - 1: cur_get_after_1, cur_get_after_2 = animated_1[i].attrib[ "after"], animated_2[i].attrib["after"] next_get_before_1, next_get_before_2 = animated_1[ i + 1].attrib["before"], animated_2[i + 1].attrib["before"] dic_1 = {"linear", "auto", "clamped", "halt"} dic_2 = {"constant"} constant_interval_1 = cur_get_after_1 in dic_2 or next_get_before_1 in dic_2 constant_interval_2 = cur_get_after_2 in dic_2 or next_get_before_2 in dic_2 # Case 1 no "constant" interval is present if (cur_get_after_1 in dic_1) and (cur_get_after_2 in dic_1) and ( next_get_before_1 in dic_1) and (next_get_before_2 in dic_1): get_average(pos_animated[i1], animated_1[i], animated_2[i]) copy_tcb_average(pos_animated[i1], animated_1[i], animated_2[i]) get_difference(size_animated[i1], animated_1[i], animated_2[i]) copy_tcb(size_animated[i1], pos_animated[i1]) i, i1 = i + 1, i1 + 1 get_average(pos_animated[i1], animated_1[i], animated_2[i]) copy_tcb_average(pos_animated[i1], animated_1[i], animated_2[i]) get_difference(size_animated[i1], animated_1[i], animated_2[i]) copy_tcb(size_animated[i1], pos_animated[i1]) # Case 2 only one "constant" interval: could mean two "constant"'s are present elif (constant_interval_1 and not constant_interval_2) or (not constant_interval_1 and constant_interval_2): if constant_interval_1: i, i1 = calc_pos_and_size(size_animated, pos_animated, animated_1, animated_2, orig_path_2, i, i1) elif constant_interval_2: i, i1 = calc_pos_and_size(size_animated, pos_animated, animated_2, animated_1, orig_path_1, i, i1) # Case 3 both are constant elif constant_interval_1 and constant_interval_2: # No need to copy tcb, as it's pos should be "constant" get_average(pos_animated[i1], animated_1[i], animated_2[i]) get_difference(size_animated[i1], animated_1[i], animated_2[i]) i, i1 = i + 1, i1 + 1 get_difference(size_animated[i1], animated_1[i], animated_2[i]) get_average(pos_animated[i1], animated_1[i], animated_2[i]) ######################### SECTION 5 END ############################## ######################### SECTION 6 ################################## # Generate the position and size for lottie format gen_properties_multi_dimensional_keyframed(lottie["p"], pos_animated, index.inc()) gen_value_Keyframed(lottie["s"], size_animated, index.inc())
def gen_value_Keyframe(curve_list, animated, i): """ Generates the dictionary corresponding to properties/valueKeyframe.json in lottie documentation Args: curve_list (list) : Bezier curve in Lottie format animated (lxml.etree._Element) : Synfig format animation i (int) : Iterator for animation Returns: (TypeError) : If hold interval is encountered (None) : Otherwise """ 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"] # Calculate positions of waypoints if animated.attrib["type"] in {"angle", "star_angle_new", "region_angle"}: #if animated.attrib["type"] in {"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 if animated.attrib["type"] == "points": cur_get_after = "constant" cur_get_before = "constant" next_get_after = "constant" next_get_before = "constant" # After effects only supports linear,ease-in,ease-out and constant interpolations for color ##### No support for TCB and clamped interpolations in color is there yet ##### if animated.attrib["type"] == {"color", "linear_gradient"}: if cur_get_after in {"auto", "clamped"}: cur_get_after = "linear" if cur_get_before in {"auto", "clamped"}: cur_get_before = "linear" if next_get_before in {"auto", "clamped"}: next_get_before = "linear" if next_get_after in {"auto", "clamped"}: next_get_after = "linear" cur_pos = parse_position(animated, i) next_pos = parse_position(animated, i + 1) lottie["t"] = get_frame(waypoint) lottie["s"] = cur_pos.get_val() lottie["e"] = next_pos.get_val() lottie["i"] = {} lottie["o"] = {} try: out_val, in_val = calc_tangent(animated, lottie, i) except Exception as excep: # That means halt/constant interval return excep set_tangents(out_val, in_val, cur_pos, next_pos, lottie, animated) if cur_get_after == "halt": # For ease out lottie["o"]["x"][0] = settings.OUT_TANGENT_X lottie["o"]["y"][0] = settings.OUT_TANGENT_Y lottie["synfig_o"] = [0] if next_get_before == "halt": # For ease in lottie["i"]["x"][0] = settings.IN_TANGENT_X lottie["i"]["y"][0] = settings.IN_TANGENT_Y lottie["synfig_i"] = [0] # TCB/!TCB and list is not empty if cur_get_before == "auto" and cur_get_after != "auto" and i > 0: # need value for previous tangents # It may be helpful to store them somewhere prev_ov, prev_iv = calc_tangent(animated, curve_list[-2], i - 1) prev_iv = out_val set_tangents(prev_ov, prev_iv, parse_position(animated, i - 1), cur_pos, curve_list[-2], animated) if cur_get_after == "halt": curve_list[-2]["i"]["x"][0] = settings.IN_TANGENT_X curve_list[-2]["i"]["y"][0] = settings.IN_TANGENT_Y lottie["synfig_i"] = [0]
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 calc_tangent(animated, lottie, i): """ Calculates the tangent, given two waypoints and there interpolation methods Args: animated (lxml.etree._Element) : Synfig format animation lottie (dict) : Lottie format animation stored here i (int) : Iterator for animation Returns: (Misc.Vector) : If waypoint's value is parsed to misc.Vector by misc.parse_position() (Misc.Color) : If waypoint's value is parsed to misc.Color ... (float) : If waypoint's value is parsed to float ... (None) : If "constant" interval is detected """ 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"] if animated.attrib["type"] in {"angle", "star_angle_new", "region_angle"}: #if animated.attrib["type"] in {"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 if animated.attrib["type"] == "points": cur_get_after = "constant" cur_get_before = "constant" next_get_after = "constant" next_get_before = "constant" # After effects only supports linear,ease-in,ease-out and constant interpolations for color ##### No support for TCB and clamped interpolations in color is there yet ##### if animated.attrib["type"] == "color": if cur_get_after in {"auto", "clamped"}: cur_get_after = "linear" if cur_get_before in {"auto", "clamped"}: cur_get_before = "linear" if next_get_before in {"auto", "clamped"}: next_get_before = "linear" if next_get_after in {"auto", "clamped"}: next_get_after = "linear" # Calculate positions of waypoints cur_pos = parse_position(animated, i) prev_pos = copy.deepcopy(cur_pos) next_pos = parse_position(animated, i + 1) after_next_pos = copy.deepcopy(next_pos) if i + 2 <= len(animated) - 1: after_next_pos = parse_position(animated, i + 2) if i - 1 >= 0: prev_pos = parse_position(animated, i - 1) tens, bias, cont = 0, 0, 0 # default values tens1, bias1, cont1 = 0, 0, 0 if "tension" in waypoint.keys(): tens = float(waypoint.attrib["tension"]) if "continuity" in waypoint.keys(): cont = float(waypoint.attrib["continuity"]) if "bias" in waypoint.keys(): bias = float(waypoint.attrib["bias"]) if "tension" in next_waypoint.keys(): tens1 = float(next_waypoint.attrib["tension"]) if "continuity" in next_waypoint.keys(): cont1 = float(next_waypoint.attrib["continuity"]) if "bias" in next_waypoint.keys(): bias1 = float(next_waypoint.attrib["bias"]) ### Special case for color interpolations ### if animated.attrib["type"] == "color": if cur_get_after == "linear" and next_get_before == "linear": return handle_color() # iter next # ANY/TCB ------ ANY/ANY if cur_get_after == "auto": if i >= 1: out_val = ((1 - tens) * (1 + bias) * (1 + cont) *\ (cur_pos - prev_pos))/2 +\ ((1 - tens) * (1 - bias) * (1 - cont) *\ (next_pos - cur_pos))/2 else: out_val = next_pos - cur_pos # t1 = p2 - p1 # iter next # ANY/LINEAR --- ANY/ANY # ANY/EASE --- ANY/ANY if cur_get_after in {"linear", "halt"}: out_val = next_pos - cur_pos # iter next # ANY/ANY ----- LINEAR/ANY # ANY/ANY ----- EASE/ANY if next_get_before in {"linear", "halt"}: in_val = next_pos - cur_pos # iter next # ANY/CLAMPED - ANY/ANY if cur_get_after == "clamped": if i >= 1: ease = "out" out_val = clamped_vector(prev_pos, cur_pos, next_pos, animated, i, lottie, ease) else: out_val = next_pos - cur_pos # t1 = p2 - p1 # iter next after_next # ANY/ANY ----- CLAMPED/ANY ---- ANY/ANY if next_get_before == "clamped": if i + 2 <= len(animated) - 1: ease = "in" in_val = clamped_vector(cur_pos, next_pos, after_next_pos, animated, i + 1, lottie, ease) else: in_val = next_pos - cur_pos # t2 = p2 - p1 # iter next # ANY/CONSTANT ---- ANY/ANY # ANY/ANY ---- CONSTANT/ANY if cur_get_after == "constant" or next_get_before == "constant": lottie["h"] = 1 if animated.attrib["type"] == "vector": del lottie["to"], lottie["ti"] del lottie["i"], lottie["o"] # "e" is not needed, but is still not deleted as # it is of use in the last iteration of animation # See properties/multiDimenstionalKeyframed.py for more details # del lottie["e"] # If the number of points is decresing, then hold interpolation should # have reverse effect. The value should instantly decrease and remain # same for the rest of the interval if animated.attrib["type"] == "points": if i > 0 and prev_pos[0] > cur_pos[0]: t_now = get_frame(animated[i - 1]) + 1 lottie["t"] = t_now return # iter next after_next # ANY/ANY ------ TCB/ANY ------ ANY/ANY if next_get_before == "auto": if i + 2 <= len(animated) - 1: in_val = ((1 - tens1) * (1 + bias1) * (1 - cont1) *\ (next_pos - cur_pos))/2 +\ ((1 - tens1) * (1 - bias1) * (1 + cont1) *\ (after_next_pos - next_pos))/2 else: in_val = next_pos - cur_pos # t2 = p2 - p1 return out_val, in_val