def gen_dict(lottie, offset_dict, dilation_dict, fr): """ Generates the constant values for each frame Args: lottie (dict) : Bezier values will be stored in here offset_dict (dict) : Animation of offset in lottie format dilation_dict (dict) : Animation of dilation/speed in lottie format fr (int) : frame number Returns: (None) """ lottie["i"], lottie["o"] = {}, {} lottie["i"]["x"], lottie["i"]["y"] = [], [] lottie["o"]["x"], lottie["o"]["y"] = [], [] lottie["i"]["x"].append(0.5) lottie["i"]["y"].append(0.5) lottie["o"]["x"].append(0.5) lottie["o"]["y"].append(0.5) lottie["t"] = fr speed_f = to_Synfig_axis(get_vector_at_frame(dilation_dict, fr), "real") speed_s = to_Synfig_axis(get_vector_at_frame(dilation_dict, fr + 1), "real") first = get_vector_at_frame( offset_dict, fr) + (fr / settings.lottie_format["fr"]) * speed_f second = get_vector_at_frame(offset_dict, fr + 1) + ( (fr + 1) / settings.lottie_format["fr"]) * speed_s first = min(max(get_time_bound("ip"), first), get_time_bound("op")) second = min(max(get_time_bound("ip"), second), get_time_bound("op")) lottie["s"], lottie["e"] = [first], [second]
def get_tangent_at_frame(t1, t2, split_r, split_a, fr): """ Given a frame, returns the in-tangent and out-tangent at a bline point depending on whether split_radius and split_angle is "true"/"false" Args: t1 (lxml.etree._Element) : Holds Tangent 1/In Tangent t2 (lxml.etree._Element) : Holds Tangent 2/Out Tangent split_r (lxml.etree._Element) : Holds animation of split radius parameter split_a (lxml.etree._Element) : Holds animation of split angle parameter fr (int) : Holds the frame value Returns: (misc.Vector, misc.Vector) : In-tangent and out-tangent at the given frame """ # Get value of split_radius and split_angle at frame sp_r = get_bool_at_frame(split_r[0], fr) sp_a = get_bool_at_frame(split_a[0], fr) # Setting tangent 1 for chld in t1: if chld.tag == "radius_path": dictionary = ast.literal_eval(chld.text) r1 = get_vector_at_frame(dictionary, fr) elif chld.tag == "theta_path": dictionary = ast.literal_eval(chld.text) a1 = get_vector_at_frame(dictionary, fr) x, y = radial_to_tangent(r1, a1) tangent1 = Vector(x, y) # Setting tangent 2 for chld in t2: if chld.tag == "radius_path": dictionary = ast.literal_eval(chld.text) r2 = get_vector_at_frame(dictionary, fr) if not sp_r: # Use t1's radius r2 = r1 elif chld.tag == "theta_path": dictionary = ast.literal_eval(chld.text) a2 = get_vector_at_frame(dictionary, fr) if not sp_a: # Use t1's angle a2 = a1 x, y = radial_to_tangent(r2, a2) tangent2 = Vector(x, y) return tangent1, tangent2
def get_outline_param_at_frame(composite, fr): """ Given a composite and frame, returns the parameters of the outline layer at that frame Args: composite (lxml.etree._Element) : Vertex of outline layer in Synfig format fr (int) : frame number Returns: (misc.Vector) : position of the vertex (float) : width of the vertex (misc.Vector) : Tangent 1 of the vertex (misc.Vector) : Tangent 2 of the vertex (bool) : True if radius split is ticked at this frame (bool) : True if tangent split is ticked at this frame """ for child in composite: if child.tag == "point_path": dictionary = ast.literal_eval(child.text) pos = get_vector_at_frame(dictionary, fr) elif child.tag == "width_path": dictionary = ast.literal_eval(child.text) width = to_Synfig_axis(get_vector_at_frame(dictionary, fr), "real") elif child.tag == "t1": t1 = child[0] elif child.tag == "t2": t2 = child[0] elif child.tag == "split_radius": split_r = child elif child.tag == "split_angle": split_a = child # Convert pos back to Synfig coordinates pos = to_Synfig_axis(pos, "vector") pos_ret = Vector(pos[0], pos[1]) t1, t2 = get_tangent_at_frame(t1, t2, split_r, split_a, fr) # Convert to Synfig units t1 /= settings.PIX_PER_UNIT t2 /= settings.PIX_PER_UNIT split_r_val = get_bool_at_frame(split_r[0], fr) split_a_val = get_bool_at_frame(split_a[0], fr) return pos_ret, width, t1, t2, split_r_val, split_a_val
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 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 synfig_circle(st_val, origin_dict, radius_dict, fr): """ Calculates the points for the circle layer as in Synfig: https://github.com/synfig/synfig/blob/678cc3a7b1208fcca18c8b54a29a20576c499927/synfig-core/src/modules/mod_geometry/circle.cpp Args: st_val (dict) : Lottie format circle is stored in this origin_dict (dict) : Lottie format origin of circle radius_dict (dict) : Lottie format radius of circle fr (int) : Frame number Returns: (None) """ num_splines = 8 angle = 180.0 / num_splines angle *= ((math.pi * 2) / 360) k = 1.0 / math.cos(angle) radius = abs(to_Synfig_axis(get_vector_at_frame(radius_dict, fr), "real")) origin = get_vector_at_frame(origin_dict, fr) matrix = Matrix2() matrix.set_rotate(angle) p0, p1, p2 = Vector(), Vector(), Vector(radius, 0) # Setup chunk list chunk_list = [] chunk_list.append([p2, Vector(), Vector()]) i = 0 while i < num_splines: p0 = p2 p1 = matrix.get_transformed(p0) p2 = matrix.get_transformed(p1) cp1, cp2 = quadratic_to_cubic(p2, k * p1, p0) cur_tan = p2 - cp1 chunk_list[-1][2] = cp2 - p0 chunk_list.append([p2, cur_tan, Vector()]) i += 1 add(chunk_list, st_val, origin)
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 get_outline_grow(fr): """ Gives the value of outline grow parameter at a particular frame """ ret = 0 for og in settings.OUTLINE_GROW: if isinstance(og, (float, int)): ret += og else: for chld in og: if chld.tag == "outline_grow_path": dictionary = ast.literal_eval(chld.text) val = to_Synfig_axis(get_vector_at_frame(dictionary, fr), "real") ret += val ret = math.e**ret return ret
def change_opacity_group(layer, lottie): """ Will make the opacity of underlying layers 0 according to the layers lying inside z range(if it is active)[z-range is non-animatable] Args: layer (lxml.etree._Element) : Synfig format layer lottie (dict) : Lottie format layer Returns: (None) """ for chld in layer: if chld.tag == "param": if chld.attrib["name"] == "z_range": z_range = chld elif chld.attrib["name"] == "z_range_position": z_range_pos = chld elif chld.attrib["name"] == "z_range_depth": z_range_depth = chld elif chld.attrib["name"] == "canvas": canvas = chld for assets in settings.lottie_format["assets"]: if assets["id"] == lottie["refId"]: root = assets break # If z-range is non-active (static value) if z_range[0].attrib["value"] == "false": return pos = gen_dummy_waypoint(z_range_pos, "param", "real", "z_range_position") depth = gen_dummy_waypoint(z_range_depth, "param", "real", "z_range_depth") pos_dict, depth_dict = {}, {} gen_value_Keyframed(pos_dict, pos[0], 0) gen_value_Keyframed(depth_dict, depth[0], 0) z_st, z_en = float('-inf'), float('-inf') active_range = [] # Stores the time and change of layers in z-range fr = settings.lottie_format["ip"] while fr <= settings.lottie_format["op"]: pos_val = to_Synfig_axis(get_vector_at_frame(pos_dict, fr), "real") depth_val = to_Synfig_axis(get_vector_at_frame(depth_dict, fr), "real") st, en = math.ceil(pos_val), math.floor(pos_val + depth_val) if st > en or en < 0: if (fr == settings.lottie_format["ip"]) or (z_st != -1 and z_en != -1): z_st, z_en = -1, -1 active_range.append([fr, z_st, z_en]) elif (st != z_st) or (en != z_en): z_st, z_en = st, en active_range.append([fr, z_st, z_en]) fr += 1 z_value = 0 for c_layer in reversed(canvas[0]): active_time = set() itr = 0 while itr < len(active_range): st, en = active_range[itr][1], active_range[itr][2] if z_value <= en and z_value >= st: now = active_range[itr][0] / settings.lottie_format["fr"] later = get_time_bound("op") if itr + 1 < len(active_range): later = active_range[itr + 1][0] / settings.lottie_format["fr"] active_time.add((now, later)) itr += 1 active_time = sorted(active_time) deactive_time = sorted(flip_time(active_time)) if c_layer.attrib["type"] in set.union(settings.SHAPE_SOLID_LAYER, settings.SOLID_LAYER): anim_type = "effects_opacity" dic = root["layers"][z_value]["ef"][0]["ef"][-1]["v"] elif c_layer.attrib["type"] in set.union(settings.PRE_COMP_LAYER, settings.GROUP_LAYER, settings.IMAGE_LAYER): anim_type = "opacity" dic = root["layers"][z_value]["ks"]["o"] elif c_layer.attrib["type"] in settings.SHAPE_LAYER: anim_type = "opacity" dic = root["layers"][z_value]["shapes"][1]["o"] animation = gen_hold_waypoints(deactive_time, c_layer, anim_type) gen_value_Keyframed(dic, animation[0], 0) z_value += 1
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_dynamic_list_polygon(lottie, dynamic_list): """ Generates the bline corresponding to polygon layer Args: lottie (dict) : Lottie format polygon layer will be stored here dynamic_list (lxml.etree._Element) : Synfig format points of polygon Returns: (None) """ ################## SECTION 1 ################ # Inserting the waypoints if not animated, finding the first and last frame # Calculating the path after this window = {} window["first"] = sys.maxsize window["last"] = -1 count = 0 for entry in dynamic_list: pos = entry update_frame_window(pos[0], window) new_pos = gen_dummy_waypoint(pos, "entry", "vector") pos.getparent().remove(pos) dynamic_list.insert(count, new_pos) append_path(new_pos[0], dynamic_list[count], "pos_path", "vector") count += 1 layer = dynamic_list.getparent().getparent() for chld in layer: if chld.tag == "param": if chld.attrib["name"] == "origin": origin = chld # Animating the origin update_frame_window(origin[0], window) origin_parent = origin.getparent() origin = gen_dummy_waypoint(origin, "param", "vector", "origin") update_child_at_parent(origin_parent, origin, "param", "origin") # Generate path for the origin component origin_dict = {} origin[0].attrib["transform_axis"] = "true" gen_properties_multi_dimensional_keyframed(origin_dict, origin[0], 0) if window["first"] == sys.maxsize and window["last"] == -1: window["first"] = window["last"] = 0 ################ END OF SECTION 1 ############## ################ SECTION 2 ##################### # Generating values for all the frames in the window fr = window["first"] while fr <= window["last"]: st_val, en_val = insert_dict_at(lottie, -1, fr, False) for entry in dynamic_list: # Only two childs, one should be animated, other one is path for child in entry: if child.tag == "pos_path": dictionary = ast.literal_eval(child.text) pos_cur = get_vector_at_frame(dictionary, fr) pos_next = get_vector_at_frame(dictionary, fr + 1) tangent1_cur, tangent2_cur = Vector(0, 0), Vector(0, 0) tangent1_next, tangent2_next = Vector(0, 0), Vector(0, 0) # Adding origin to each vertex origin_cur = get_vector_at_frame(origin_dict, fr) origin_next = get_vector_at_frame(origin_dict, fr + 1) for i in range(len(pos_cur)): pos_cur[i] += origin_cur[i] for i in range(len(pos_next)): pos_next[i] += origin_next[i] # Store values in dictionary st_val["i"].append(tangent1_cur.get_list()) st_val["o"].append(tangent2_cur.get_list()) st_val["v"].append(pos_cur) en_val["i"].append(tangent1_next.get_list()) en_val["o"].append(tangent2_next.get_list()) en_val["v"].append(pos_next) fr += 1 # Setting the final time lottie.append({}) lottie[-1]["t"] = fr
def synfig_outline(bline_point, st_val, origin_dict, outer_width_dict, sharp_cusps_anim, expand_dict, r_tip0_anim, r_tip1_anim, homo_width_anim, fr): """ Calculates the points for the outline layer as in Synfig: https://github.com/synfig/synfig/blob/678cc3a7b1208fcca18c8b54a29a20576c499927/synfig-core/src/modules/mod_geometry/outline.cpp Args: bline_point (lxml.etree._Element) : Synfig format bline points of outline layer st_val (dict) : Lottie format outline stored in this origin_dict (dict) : Lottie format origin of outline layer outer_width_dict (dict) : Lottie format outer width sharp_cusps_anim (lxml.etree._Element) : sharp cusps in Synfig format expand_dict (dict) : Lottie format expand parameter r_tip0_anim (lxml.etree._Element) : Round tip[0] in Synfig format r_tip1_anim (lxml.etree._Element) : Round tip[1] in Synfig format homo_width_anim (lxml.etree._Element) : Homogeneous width in Synfig format fr (int) : Frame number Returns: (None) """ EPSILON = 0.000000001 SAMPLES = 50 CUSP_TANGENT_ADJUST = 0.025 CUSP_THRESHOLD = 0.40 SPIKE_AMOUNT = 4 ROUND_END_FACTOR = 4 outer_width = to_Synfig_axis(get_vector_at_frame(outer_width_dict, fr), "real") expand = to_Synfig_axis(get_vector_at_frame(expand_dict, fr), "real") sharp_cusps = get_bool_at_frame(sharp_cusps_anim[0], fr) r_tip0 = get_bool_at_frame(r_tip0_anim[0], fr) r_tip1 = get_bool_at_frame(r_tip1_anim[0], fr) homo_width = get_bool_at_frame(homo_width_anim[0], fr) gv = get_outline_grow(fr) # Setup chunk list side_a, side_b = [], [] # Check if looped loop = False if "loop" in bline_point.keys() and bline_point.attrib["loop"] == "true": loop = True # Iterators end_it = len(bline_point) next_it = 0 if loop: iter_it = end_it - 1 else: iter_it = next_it next_it += 1 first_point = bline_point[iter_it][0] first_tangent = get_outline_param_at_frame(bline_point[0][0], fr)[3] last_tangent = get_outline_param_at_frame(first_point, fr)[2] # If we are looped and drawing sharp cusps, we 'll need a value # for the incoming tangent. This code fails if we have a "degraded" spline # with just one vertex, so we avoid such case. if loop and sharp_cusps and last_tangent.is_equal_to(Vector( 0, 0)) and len(bline_point) > 1: prev_it = iter_it prev_it -= 1 prev_it %= len(bline_point) prev_point = bline_point[prev_it][0] curve = Hermite( get_outline_param_at_frame(prev_point, fr)[0], get_outline_param_at_frame(first_point, fr)[0], get_outline_param_at_frame(prev_point, fr)[3], get_outline_param_at_frame(first_point, fr)[2]) last_tangent = curve.derivative(1.0 - CUSP_TANGENT_ADJUST) first = not loop while next_it != end_it: bp1 = bline_point[iter_it][0] bp2 = bline_point[next_it][0] # Calculate current vertex and next vertex parameters pos_c, width_c, t1_c, t2_c, split_r_c, split_a_c = get_outline_param_at_frame( bp1, fr) pos_n, width_n, t1_n, t2_n, split_r_n, split_a_n = get_outline_param_at_frame( bp2, fr) # Setup tangents prev_t = t1_c iter_t = t2_c next_t = t1_n split_flag = split_a_c or split_r_c # If iter_it.t2 == 0 and next.t1 == 0, this is a straight line if iter_t.is_equal_to(Vector(0, 0)) and next_t.is_equal_to(Vector( 0, 0)): iter_t = next_t = pos_n - pos_c # If the 2 points are on top of each other, ignore this segment # leave 'first' true if was before if iter_t.is_equal_to(Vector(0, 0)): iter_it = next_it iter_it %= len(bline_point) next_it += 1 continue # Setup the curve curve = Hermite(pos_c, pos_n, iter_t, next_t) # Setup width's iter_w = gv * (width_c * outer_width * 0.5 + expand) next_w = gv * (width_n * outer_width * 0.5 + expand) if first: first_tangent = curve.derivative(CUSP_TANGENT_ADJUST) # Make cusps as necassary if not first and \ sharp_cusps and \ split_flag and \ ((not prev_t.is_equal_to(iter_t)) or (iter_t.is_equal_to(Vector(0, 0)))) and \ (not last_tangent.is_equal_to(Vector(0, 0))): curr_tangent = curve.derivative(CUSP_TANGENT_ADJUST) t1 = last_tangent.perp().norm() t2 = curr_tangent.perp().norm() cross = t1 * t2.perp() perp = (t1 - t2).mag() if cross > CUSP_THRESHOLD: p1 = pos_c + t1 * iter_w p2 = pos_c + t2 * iter_w side_a.append([ line_intersection(p1, last_tangent, p2, curr_tangent), Vector(0, 0), Vector(0, 0) ]) elif cross < -CUSP_THRESHOLD: p1 = pos_c - t1 * iter_w p2 = pos_c - t2 * iter_w side_b.append([ line_intersection(p1, last_tangent, p2, curr_tangent), Vector(0, 0), Vector(0, 0) ]) elif cross > 0.0 and perp > 1.0: amount = max( 0.0, cross / CUSP_THRESHOLD) * (SPIKE_AMOUNT - 1.0) + 1.0 side_a.append([ pos_c + (t1 + t2).norm() * iter_w * amount, Vector(0, 0), Vector(0, 0) ]) elif cross < 0 and perp > 1: amount = max( 0.0, -cross / CUSP_THRESHOLD) * (SPIKE_AMOUNT - 1.0) + 1.0 side_b.append([ pos_c - (t1 + t2).norm() * iter_w * amount, Vector(0, 0), Vector(0, 0) ]) # Precalculate positions and coefficients length = 0.0 points = [] dists = [] n = 0.0 itr = 0 while n < 1.000001: points.append(curve.value(n)) if n: length += (points[itr] - points[itr - 1]).mag() dists.append(length) n += 1.0 / SAMPLES itr += 1 length += (curve.value(1) - points[itr - 1]).mag() div_length = 1 if length > EPSILON: div_length = 1.0 / length # Might not need /3 for the tangents genereated finally - VERY IMPORTANT # Make the outline pt = curve.derivative(CUSP_TANGENT_ADJUST) / 3 n = 0.0 itr = 0 while n < 1.000001: t = curve.derivative( min(max(n, CUSP_TANGENT_ADJUST), 1.0 - CUSP_TANGENT_ADJUST)) / 3 d = t.perp().norm() k = dists[itr] * div_length if not homo_width: k = n w = (next_w - iter_w) * k + iter_w if False and n: # create curve a = points[itr - 1] + d * w b = points[itr] + d * w tk = (b - a).mag() * div_length side_a.append([b, pt * tk, -t * tk]) a = points[itr - 1] - d * w b = points[itr] - d * w tk = (b - a).mag() * div_length side_b.append([b, pt * tk, -t * tk]) else: side_a.append( [points[itr] + d * w, Vector(0, 0), Vector(0, 0)]) side_b.append( [points[itr] - d * w, Vector(0, 0), Vector(0, 0)]) pt = t itr += 1 n += 1.0 / SAMPLES last_tangent = curve.derivative(1.0 - CUSP_TANGENT_ADJUST) side_a.append([ curve.value(1.0) + last_tangent.perp().norm() * next_w, Vector(0, 0), Vector(0, 0) ]) side_b.append([ curve.value(1.0) - last_tangent.perp().norm() * next_w, Vector(0, 0), Vector(0, 0) ]) first = False iter_it = next_it iter_it %= len(bline_point) next_it += 1 if len(side_a) < 2 or len(side_b) < 2: return origin_cur = get_vector_at_frame(origin_dict, fr) move_to(side_a[0][0], st_val, origin_cur) if loop: add(side_a, st_val, origin_cur) add_reverse(side_b, st_val, origin_cur) else: # Insert code for adding end tip if r_tip1: bp = bline_point[-1][0] vertex = get_outline_param_at_frame(bp, fr)[0] tangent = last_tangent.norm() w = gv * (get_outline_param_at_frame(bp, fr)[1] * outer_width * 0.5 + expand) a = vertex + tangent.perp() * w b = vertex - tangent.perp() * w p1 = a + tangent * w * (ROUND_END_FACTOR / 3.0) p2 = b + tangent * w * (ROUND_END_FACTOR / 3.0) tan = tangent * w * (ROUND_END_FACTOR / 3.0) # replace the last point side_a[-1] = [a, Vector(0, 0), tan] add(side_a, st_val, origin_cur) add([[b, -tan, Vector(0, 0)]], st_val, origin_cur) else: add(side_a, st_val, origin_cur) # Insert code for adding beginning tip if r_tip0: bp = bline_point[0][0] vertex = get_outline_param_at_frame(bp, fr)[0] tangent = first_tangent.norm() w = gv * (get_outline_param_at_frame(bp, fr)[1] * outer_width * 0.5 + expand) a = vertex - tangent.perp() * w b = vertex + tangent.perp() * w p1 = a - tangent * w * (ROUND_END_FACTOR / 3.0) p2 = b - tangent * w * (ROUND_END_FACTOR / 3.0) tan = -tangent * w * (ROUND_END_FACTOR / 3.0) # replace the first point side_b[0] = [a, Vector(0, 0), tan] add_reverse(side_b, st_val, origin_cur) add([[b, -tan, Vector(0, 0)]], st_val, origin_cur) else: add_reverse(side_b, st_val, origin_cur)
def gen_bline_region(lottie, bline_point): """ Generates the dictionary corresponding to properties/shapePropKeyframe.json, given a bline/spline Args: lottie (dict) : Lottie generated keyframes will be stored here for shape/path bline_path (lxml.etree._Element) : shape/path store in Synfig format Returns: (None) """ ################### SECTION 1 ######################### # Inserting waypoints if not animated and finding the first and last frame # AFter that, there path will be calculated in lottie format which can # latter be used in get_vector_at_frame() function window = {} window["first"] = sys.maxsize window["last"] = -1 loop = False if "loop" in bline_point.keys(): val = bline_point.attrib["loop"] if val == "false": loop = False else: loop = True for entry in bline_point: composite = entry[0] for child in composite: if child.tag == "point": pos = child elif child.tag == "t1": t1 = child elif child.tag == "t2": t2 = child elif child.tag == "split_radius": split_r = child elif child.tag == "split_angle": split_a = child # Necassary to update this before inserting new waypoints, as new # waypoints might include there on time: 0 seconds update_frame_window(pos[0], window) # Empty the pos and fill in the new animated pos pos = gen_dummy_waypoint(pos, "point", "vector") update_child_at_parent(composite, pos, "point") update_frame_window(split_r[0], window) split_r = gen_dummy_waypoint(split_r, "split_radius", "bool") update_child_at_parent(composite, split_r, "split_radius") update_frame_window(split_a[0], window) split_a = gen_dummy_waypoint(split_a, "split_angle", "bool") update_child_at_parent(composite, split_a, "split_angle") append_path(pos[0], composite, "point_path", "vector") animate_radial_composite(t1[0], window) animate_radial_composite(t2[0], window) layer = bline_point.getparent().getparent() for chld in layer: if chld.tag == "param" and chld.attrib["name"] == "origin": origin = chld # Animating the origin update_frame_window(origin[0], window) origin_parent = origin.getparent() origin = gen_dummy_waypoint(origin, "param", "vector", "origin") update_child_at_parent(origin_parent, origin, "param", "origin") # Generate path for the origin component origin_dict = {} origin[0].attrib["transform_axis"] = "true" gen_properties_multi_dimensional_keyframed(origin_dict, origin[0], 0) # Minimizing the window size if window["first"] == sys.maxsize and window["last"] == -1: window["first"] = window["last"] = 0 ################# END OF SECTION 1 ################### ################ SECTION 2 ########################### # Generating values for all the frames in the window fr = window["first"] while fr <= window["last"]: st_val, en_val = insert_dict_at(lottie, -1, fr, loop) for entry in bline_point: composite = entry[0] for child in composite: if child.tag == "point_path": dictionary = ast.literal_eval(child.text) pos_cur = get_vector_at_frame(dictionary, fr) pos_next = get_vector_at_frame(dictionary, fr + 1) elif child.tag == "t1": t1 = child[0] elif child.tag == "t2": t2 = child[0] elif child.tag == "split_radius": split_r = child elif child.tag == "split_angle": split_a = child tangent1_cur, tangent2_cur = get_tangent_at_frame( t1, t2, split_r, split_a, fr) tangent1_next, tangent2_next = get_tangent_at_frame( t1, t2, split_r, split_a, fr) tangent1_cur, tangent2_cur = convert_tangent_to_lottie( tangent1_cur, tangent2_cur) tangent1_next, tangent2_next = convert_tangent_to_lottie( tangent1_next, tangent2_next) # Adding origin to each vertex origin_cur = get_vector_at_frame(origin_dict, fr) origin_next = get_vector_at_frame(origin_dict, fr + 1) for i in range(len(pos_cur)): pos_cur[i] += origin_cur[i] for i in range(len(pos_next)): pos_next[i] += origin_next[i] # Store values in dictionary st_val["i"].append(tangent1_cur.get_list()) st_val["o"].append(tangent2_cur.get_list()) st_val["v"].append(pos_cur) en_val["i"].append(tangent1_next.get_list()) en_val["o"].append(tangent2_next.get_list()) en_val["v"].append(pos_next) fr += 1 # Setting final time lottie.append({}) lottie[-1]["t"] = fr
def synfig_rectangle(st_val, p1_dict, p2_dict, expand_dict, bevel_dict, bevCircle, fr): """ Calculates the points for the rectangle layer as in Synfig: https://github.com/synfig/synfig/blob/678cc3a7b1208fcca18c8b54a29a20576c499927/synfig-core/src/modules/mod_geometry/rectangle.cpp Args: st_val (dict) : Lottie format rectangle will be stored in this p1_dict (dict) : Lottie format point1 animation p2_dict (dict) : Lottie format point2 animation expand_dict (dict) : Lottie format expand parameter animation bevel_dict (dict) : Lottie format bevel parameter animation bevCircle (lxml.etree._Element) : Animation of bevCircle in Synfig format fr (int) : Frame Number Returns: (None) """ expand = abs(to_Synfig_axis(get_vector_at_frame(expand_dict, fr), "real")) bevel = abs(to_Synfig_axis(get_vector_at_frame(bevel_dict, fr), "real")) p0 = to_Synfig_axis(get_vector_at_frame(p1_dict, fr), "vector") p0 = Vector(p0[0], p0[1]) p1 = to_Synfig_axis(get_vector_at_frame(p2_dict, fr), "vector") p1 = Vector(p1[0], p1[1]) if p1[0] < p0[0]: p0[0], p1[0] = p1[0], p0[0] if p1[1] < p0[1]: p0[1], p1[1] = p1[1], p0[1] bev_circle = get_bool_at_frame(bevCircle[0], fr) w = p1[0] - p0[0] + 2 * expand h = p1[1] - p0[1] + 2 * expand bev = bevel if bevel > 1: bev = 1 if bev_circle: bevx = min(w * bev / 2.0, h * bev / 2.0) bevy = min(w * bev / 2.0, h * bev / 2.0) else: bevx = w * bev / 2.0 bevy = h * bev / 2.0 # Setup chunk list chunk_list = [] if approximate_equal(bevel, 0.0): chunk_list.append( [Vector(p0[0] - expand, p0[1] - expand), Vector(), Vector()]) chunk_list.append( [Vector(p1[0] + expand, p0[1] - expand), Vector(), Vector()]) chunk_list.append( [Vector(p1[0] + expand, p1[1] + expand), Vector(), Vector()]) chunk_list.append( [Vector(p0[0] - expand, p1[1] + expand), Vector(), Vector()]) else: cur = Vector(p1[0] + expand - bevx, p0[1] - expand) chunk_list.append([cur, Vector(), Vector()]) prev = cur cur = Vector(p1[0] + expand, p0[1] - expand + bevy) cp1, cp2 = quadratic_to_cubic(cur, Vector(p1[0] + expand, p0[1] - expand), prev) chunk_list[-1][2] = cp2 - prev chunk_list.append([cur, cur - cp1, Vector()]) prev = cur cur = Vector(p1[0] + expand, p1[1] + expand - bevy) chunk_list.append([cur, Vector(), Vector()]) prev = cur cur = Vector(p1[0] + expand - bevx, p1[1] + expand) cp1, cp2 = quadratic_to_cubic(cur, Vector(p1[0] + expand, p1[1] + expand), prev) chunk_list[-1][2] = cp2 - prev chunk_list.append([cur, cur - cp1, Vector()]) prev = cur cur = Vector(p0[0] - expand + bevx, p1[1] + expand) chunk_list.append([cur, Vector(), Vector()]) prev = cur cur = Vector(p0[0] - expand, p1[1] + expand - bevy) cp1, cp2 = quadratic_to_cubic(cur, Vector(p0[0] - expand, p1[1] + expand), prev) chunk_list[-1][2] = cp2 - prev chunk_list.append([cur, cur - cp1, Vector()]) prev = cur cur = Vector(p0[0] - expand, p0[1] - expand + bevy) chunk_list.append([cur, Vector(), Vector()]) prev = cur cur = Vector(p0[0] - expand + bevx, p0[1] - expand) cp1, cp2 = quadratic_to_cubic(cur, Vector(p0[0] - expand, p0[1] - expand), prev) chunk_list[-1][2] = cp2 - prev chunk_list.append([cur, cur - cp1, Vector()]) prev = cur add(chunk_list, st_val, [0, 0])
def synfig_star(st_val, mx_points, origin_dict, radius1_dict, radius2_dict, angle_dict, points_dict, regular_polygon_anim, fr): """ Calculates the points for the rectangle layer as in Synfig: https://github.com/synfig/synfig/blob/678cc3a7b1208fcca18c8b54a29a20576c499927/synfig-core/src/modules/mod_geometry/star.cpp Args: st_val (dict) : Lottie format star will be stored here mx_points (int) : Maximum points ever in star animation radius1_dict (dict) : Lottie format radius1 animation radius2_dict (dict) : Lottie format radius2 animation angle_dict (dict) : Lottie format angle animation points_dict (dict) : Lottie format points animation regular_polygon_anim (lxml.etree._Element) : Synfig format regularPolygon animation fr (int) : Frame number Returns: (None) """ angle = get_vector_at_frame(angle_dict, fr) points = int(to_Synfig_axis(get_vector_at_frame(points_dict, fr), "real")) radius1 = to_Synfig_axis(get_vector_at_frame(radius1_dict, fr), "real") radius2 = to_Synfig_axis(get_vector_at_frame(radius2_dict, fr), "real") regular_polygon = get_bool_at_frame(regular_polygon_anim[0], fr) origin_cur = get_vector_at_frame(origin_dict, fr) angle = math.radians(angle) dist_between_points = (math.pi * 2) / float(points) vector_list = [] #### tot_points = 2 * mx_points i = 0 while i < points: dist1 = dist_between_points * i + angle dist2 = dist_between_points * i + dist_between_points / 2 + angle vector_list.append( Vector(math.cos(dist1) * radius1, math.sin(dist1) * radius1)) if not regular_polygon: vector_list.append( Vector(math.cos(dist2) * radius2, math.sin(dist2) * radius2)) else: # This condition is needed because in lottie a shape must have equal # number of vertices at each frame vector_list.append( Vector(math.cos(dist1) * radius1, math.sin(dist1) * radius1)) tot_points -= 2 i += 1 if len(vector_list) < 3: # Should throw error return while tot_points > 0: vector_list.append(vector_list[-1]) tot_points -= 1 # Setup chunk list chunk_list = [] chunk_list.append([vector_list[0], Vector(), Vector()]) i = 1 while i < len(vector_list): if math.isnan(vector_list[i][0]) or math.isnan(vector_list[i][1]): break chunk_list.append([vector_list[i], Vector(), Vector()]) i += 1 add(chunk_list, st_val, origin_cur)