def get_list_at_frame_2(self, fr): """ Returns list of WidthPoint's at a particular frame """ wplist = [] for entry in self.get_entry_list(): pos = entry["position"].get_value(fr) pos = to_Synfig_axis(pos, "real") width = entry["width"].get_value(fr) width = to_Synfig_axis(width, "real") side_before = entry["side_before"].get() side_before = int(side_before[0].attrib["value"]) side_after = entry["side_after"].get() side_after = int(side_after[0].attrib["value"]) lower_bound = entry["lower_bound"].get() lower_bound = float(lower_bound[0].attrib["value"]) upper_bound = entry["upper_bound"].get() upper_bound = float(upper_bound[0].attrib["value"]) wplist.append( common.WidthPoint.WidthPoint(pos, width, side_before, side_after, False, lower_bound, upper_bound)) return wplist
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_list_at_frame(self, fr): """ Returns list of Dashitems at a particular frame Refer: https://github.com/synfig/synfig/blob/15607089680af560ad031465d31878425af927eb/synfig-core/src/synfig/valuenodes/valuenode_dilist.cpp#L129 """ dilist = [] rising = [False] for entry in self.get_entry_list(): amount = entry["ActivepointList"].amount_at_time(fr, rising) assert (amount >= 0) assert (amount <= 1) offset = to_Synfig_axis(entry["offset"].get_value(fr), "real") length = to_Synfig_axis(entry["length"].get_value(fr), "real") side_before = entry["side_before"].get() side_before = int(side_before[0].attrib["value"]) side_after = entry["side_after"].get() side_after = int(side_after[0].attrib["value"]) curr = DashItem(offset, length, side_before, side_after) if amount > 1 - 0.0000001: # lgtm [py/redundant-comparison] dilist.append(curr) return dilist
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 get_value_at_frame(self, entry, fr): """ """ pos = to_Synfig_axis(entry["point"].get_value(fr), "vector") pos = common.Vector.Vector(pos[0], pos[1]) width = to_Synfig_axis(entry["width"].get_value(fr), "real") origin = to_Synfig_axis(entry["origin"].get_value(fr), "real") t1 = entry["t1"] t2 = entry["t2"] split_r = entry["split_radius"] split_a = entry["split_angle"] t1, t2 = get_tangent_at_frame(t1, t2, fr) # convert to synfig units t1 /= settings.PIX_PER_UNIT t2 /= settings.PIX_PER_UNIT split_r_val = split_r.get_value(fr) split_a_val = split_a.get_value(fr) return BlinePoint(pos, width, split_r_val, split_a_val, t1, t2, origin)
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_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: # should be dict val = to_Synfig_axis(og.get_value(fr), "real") ret += val ret = math.e ** ret return ret
def get_outline_param_at_frame(entry, fr): """ Given a entry and frame, returns the parameters of the outline layer at that frame Args: entry (dict) : Vertex of outline layer in Synfig format fr (int) : frame number Returns: (common.Vector.Vector) : position of the vertex (float) : width of the vertex (common.Vector.Vector) : Tangent 1 of the vertex (common.Vector.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 """ pos = entry["point"].get_value(fr) # Convert pos back to Synfig coordinates pos = to_Synfig_axis(pos, "vector") pos_ret = Vector(pos[0], pos[1]) width = entry["width"].get_value(fr) width = to_Synfig_axis(width, "real") t1 = entry["t1"] t2 = entry["t2"] split_r = entry["split_radius"] split_a = entry["split_angle"] 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 = split_r.get_value(fr) split_a_val = split_a.get_value(fr) return pos_ret, width, t1, t2, split_r_val, split_a_val
def get_value_at_frame(self, entry, fr): pos = entry["position"].get_value(fr) pos = to_Synfig_axis(pos, "real") width = entry["width"].get_value(fr) width = to_Synfig_axis(width, "real") side_before = entry["side_before"].get() side_before = int(side_before[0].attrib["value"]) side_after = entry["side_after"].get() side_after = int(side_after[0].attrib["value"]) lower_bound = entry["lower_bound"].get() lower_bound = float(lower_bound[0].attrib["value"]) upper_bound = entry["upper_bound"].get() upper_bound = float(upper_bound[0].attrib["value"]) curr = common.WidthPoint.WidthPoint(pos, width, side_before, side_after, False, lower_bound, upper_bound) return curr
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 synfig_circle(st_val, origin_param, radius_param, 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 (common.Param.Param) : Lottie format origin of circle radius (common.Param.Param) : 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(radius_param.get_value(fr), "real")) origin = origin_param.get_value(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 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 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 (common.Layer.Layer) : Synfig format layer lottie (dict) : Lottie format layer Returns: (None) """ z_range = layer.get_param("z_range") z_range_pos = layer.get_param("z_range_position") z_range_depth = layer.get_param("z_range_depth") canvas = Canvas(layer.get_param("canvas")) 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 z_range_pos.animate("real") z_range_depth.animate("real") 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(z_range_pos.get_value(fr), "real") depth_val = to_Synfig_axis(z_range_depth.get_value(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.get_layer_list()): if not c_layer.is_active() or not c_layer.to_render(): continue 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) inactive_time = sorted(flip_time(active_time)) if c_layer.get_type() in set.union(settings.SHAPE_SOLID_LAYER, settings.SOLID_LAYER): anim_type = "effects_opacity" sw = 1 elif c_layer.get_type() in set.union(settings.PRE_COMP_LAYER, settings.GROUP_LAYER, settings.IMAGE_LAYER): anim_type = "opacity" sw = 2 elif c_layer.get_type() in settings.SHAPE_LAYER: anim_type = "opacity" sw = 3 dic = root["layers"][z_value]["shapes"][1]["o"] animation = gen_hold_waypoints(inactive_time, c_layer, anim_type) animation.animate(anim_type) if sw == 1: animation.fill_path(root["layers"][z_value]["ef"][0]["ef"][-1], "v") # See effects/fill.py: Opacity is the last property, and hence we are using [-1]. # We should actually search for "opacity", but due to multiple elements with same # "ty"(which should not happen), we are using [-1]. "ty" here means type which uniquely # identifies the effect in Lottie, but 'horizontal feather', 'vertical feather' and # 'opacity' in Lottie have the same type and hence we can not search for "opacity" # uniquely elif sw == 2: animation.fill_path(root["layers"][z_value]["ks"], "o") elif sw == 3: animation.fill_path(root["layers"][z_value]["shapes"][1], "o") z_value += 1
def synfig_rectangle(st_val, point1_p, point2_p, expand_p, bevel_p, 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 point1_p (self.Param.Param) : Lottie format point1 animation point2_p (self.Param.Param) : Lottie format point2 animation expand_p (self.Param.Param) : Lottie format expand parameter animation bevel_p (self.Param.Param) : 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(expand_p.get_value(fr), "real")) bevel = abs(to_Synfig_axis(bevel_p.get_value(fr), "real")) p0 = to_Synfig_axis(point1_p.get_value(fr), "vector") p0 = Vector(p0[0], p0[1]) p1 = to_Synfig_axis(point2_p.get_value(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 = bevCircle.get_value(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], True)
def __get_value(self, frame): """ Returns the value of the parameter at a given frame """ if self.param.tag in settings.BONES or self.param[0].tag in settings.CONVERT_METHODS: if self.param.tag == "bone": cur_origin = self.subparams["origin"].__get_value(frame) # Now adding the parent's effects in this bone guid = self.subparams["parent"][0].attrib["guid"] canvas = self.get_canvas() bone = canvas.get_bone(guid) shifted_origin, shifted_angle, lls, rls = bone.__get_value(frame) a1, a2 = math.radians(shifted_angle), math.radians(shifted_angle+90) # Calculating this bones angle with respect to parent bone's # angle if "angle" in self.subparams.keys(): angle = to_Synfig_axis(self.subparams["angle"].__get_value(frame), "angle") else: angle = 0 # Calculating the local length scale local_length_scale = self.subparams["scalelx"].__get_value(frame) # Calculating the recursive length scale this_rls = self.subparams["scalex"].__get_value(frame) # In current angle's direction absolute_angle = shifted_angle+angle aa1 = math.radians(absolute_angle) this_rls = [this_rls * math.cos(aa1), this_rls * math.sin(aa1)] # Calculate returning recursive length ret_rls = [this_rls[0]*rls[0], this_rls[1]*rls[1]] ##### REMOVE AFTER DEBUGGING ret_rls = [1, 1] # Multiplying the current bone origin with the scale cur_origin = [i*lls for i in cur_origin] ret = shifted_origin # Adding effect of x component ret[0] = ret[0] + (cur_origin[0] * math.cos(a1) + cur_origin[1] * math.cos(a2)) * rls[0] ret[1] = ret[1] + (cur_origin[0] * math.sin(a1) + cur_origin[1] * math.sin(a2)) * rls[1] return ret, absolute_angle, local_length_scale, ret_rls elif self.param.tag == "bone_root": origin = [0, 0] angle = 0 local_length_scale = 1 recursive_length_scale = [1, 1] # x and y axis return origin, angle, local_length_scale, recursive_length_scale elif self.param[0].tag == "add": ret = self.subparams["add"].subparams["lhs"].__get_value(frame) ret2 = self.subparams["add"].subparams["rhs"].__get_value(frame) mul = self.subparams["add"].subparams["scalar"].__get_value(frame) if isinstance(ret, list): ret[0] += ret2[0] ret[1] += ret2[1] ret = [it*mul for it in ret] else: ret += ret2 ret *= mul elif self.param[0].tag == "exp": exp = self.subparams["exp"].subparams["exp"].__get_value(frame) scale = self.subparams["exp"].subparams["scale"].__get_value(frame) ret = scale * math.exp(exp) elif self.param[0].tag == "average": lst = self.subparams["average"].subparams["entry"] if not isinstance(lst, list): lst = [lst] ret = [0, 0] if not isinstance(lst[0].__get_value(frame), list): ret = 0 for it in lst: val = it.__get_value(frame) if isinstance(val, list): ret[0], ret[1] = ret[0] + val[0], ret[1] + val[1] else: ret += val if isinstance(ret, list): ret[0], ret[1] = ret[0] / len(lst), ret[1] / len(lst) else: ret /= float(len(lst)) elif self.param[0].tag == "weighted_average": self.subparams["weighted_average"].extract_subparams() lst = self.subparams["weighted_average"].subparams["entry"] if not isinstance(lst, list): # When only one entry is present lst = [lst] ret = [0, 0] den = 0 if not isinstance(lst[0].subparams["weighted_vector"].subparams["value"].__get_value(frame), list): ret = 0 for it in lst: weight = it.subparams["weighted_vector"].subparams["weight"].__get_value(frame) value = it.subparams["weighted_vector"].subparams["value"].__get_value(frame) den += weight if isinstance(value, list): ret[0], ret[1] = ret[0] + value[0]*weight, ret[1] + value[1]*weight else: ret += value*weight if isinstance(ret, list): ret[0], ret[1] = ret[0] / den, ret[1] / den else: ret /= float(den) elif self.param[0].tag == "composite": # Only available for vectors x = self.subparams["composite"].subparams["x"].__get_value(frame) y = self.subparams["composite"].subparams["y"].__get_value(frame) ret = [x, y] elif self.param[0].tag == "linear": slope = self.subparams["linear"].subparams["slope"].__get_value(frame) offset = self.subparams["linear"].subparams["offset"].__get_value(frame) if isinstance(slope, list): ret = [0, 0] ret[0] = offset[0] + slope[0]*(frame/settings.lottie_format["fr"]) ret[1] = offset[1] + slope[1]*(frame/settings.lottie_format["fr"]) else: ret = offset + slope*(frame/settings.lottie_format["fr"]) elif self.param[0].tag == "radial_composite": # Only for vectors rad = self.subparams["radial_composite"].subparams["radius"].__get_value(frame) angle = to_Synfig_axis(self.subparams["radial_composite"].subparams["theta"].__get_value(frame), "angle") angle = math.radians(angle) x = rad * math.cos(angle) y = rad * math.sin(angle) ret = [x, y] elif self.param[0].tag == "scale": link = self.subparams["scale"].subparams["link"].__get_value(frame) scalar = self.subparams["scale"].subparams["scalar"].__get_value(frame) if isinstance(link, list): link[0] *= scalar link[1] *= scalar else: link *= scalar ret = link elif self.param[0].tag == "subtract": lhs = self.subparams["subtract"].subparams["lhs"].__get_value(frame) rhs = self.subparams["subtract"].subparams["rhs"].__get_value(frame) scalar = self.subparams["subtract"].subparams["scalar"].__get_value(frame) if isinstance(lhs, list): ret = [0, 0] ret[0] = (lhs[0] - rhs[0]) * scalar ret[1] = (lhs[1] - rhs[1]) * scalar else: ret = (lhs - rhs) * scalar elif self.param[0].tag == "switch": link_off = self.subparams["switch"].subparams["link_off"].__get_value(frame) link_on = self.subparams["switch"].subparams["link_on"].__get_value(frame) switch = self.subparams["switch"].subparams["switch"].__get_value(frame) if isinstance(link_on, list): ret = [0, 0] ret[0] = link_on[0] * switch + link_off[0] * (1 - switch) ret[1] = link_on[1] * switch + link_off[1] * (1 - switch) else: ret = link_on * switch + link_off * (1 - switch) elif self.param[0].tag == "bone_link": guid = self.subparams["bone_link"].subparams["bone"][0].attrib["guid"] bone = self.get_bone_from_canvas(guid) ret_origin, ret_angle, lls, rls = bone.__get_value(frame) # Adding the base value effect here base_value = self.subparams["bone_link"].subparams["base_value"].__get_value(frame) a1, a2 = math.radians(ret_angle), math.radians(ret_angle+90) ret = ret_origin # base_value to be arranged according to the local scale base_value = [lls*i for i in base_value] ret[0] = ret[0] + (base_value[0] * math.cos(a1) - base_value[1] * math.cos(a2)) * rls[0] ret[1] = ret[1] + (base_value[0] * math.sin(a1) - base_value[1] * math.sin(a2)) * rls[1] ret = [ret[0], ret[1]] elif self.param[0].tag == "sine": angle = self.subparams["sine"].subparams["angle"].__get_value(frame) amp = self.subparams["sine"].subparams["amp"].__get_value(frame) angle = math.radians(angle) if isinstance(amp, list): ret = [0, 0] ret[0] = math.sin(angle) * amp[0] ret[1] = math.sin(angle) * amp[1] else: ret = math.sin(angle)*amp elif self.param[0].tag == "cos": angle = self.subparams["cos"].subparams["angle"].__get_value(frame) amp = self.subparams["cos"].subparams["amp"].__get_value(frame) angle = math.radians(angle) if isinstance(amp, list): ret = [0, 0] ret[0] = math.cos(angle) * amp[0] ret[1] = math.cos(angle) * amp[1] else: ret = math.cos(angle)*amp elif self.param[0].tag == "fromint": link = self.subparams["fromint"].subparams["link"].__get_value(frame) if isinstance(link, list): ret = [0, 0] ret[0] = round(link[0])*settings.PIX_PER_UNIT ret[1] = round(link[1])*settings.PIX_PER_UNIT else: ret = round(link)*settings.PIX_PER_UNIT elif self.param[0].tag == "atan2": y = self.subparams["atan2"].subparams["y"].__get_value(frame) x = self.subparams["atan2"].subparams["x"].__get_value(frame) rad = math.pi/180 ret = math.atan2(y,x)/rad elif self.param[0].tag == "vectorangle": vector = self.subparams["vectorangle"].subparams["vector"].__get_value(frame) rad = math.pi/180 ret = math.atan2(vector[1],vector[0])/rad else: ret = self.get_single_value(frame) if isinstance(ret, list): # Need to change the calculation inside get_single_value, this # is just a hack ret = [ret[0], -ret[1]] return ret
def synfig_outline(bline, st_val, origin_p, outer_width_p, sharp_cusps_p, expand_p, r_tip0_p, r_tip1_p, homo_width_p, 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 (common.Bline.Bline) : Synfig format bline points of outline layer st_val (dict) : Lottie format outline stored in this origin_p (common.Param.Param) : Lottie format origin of outline layer outer_width_p (common.Param.Param) : Lottie format outer width sharp_cusps_p (common.Param.Param) : sharp cusps in Synfig format expand_p (common.Param.Param) : Lottie format expand parameter r_tip0_p (common.Param.Param) : Round tip[0] in Synfig format r_tip1_p (common.Param.Param) : Round tip[1] in Synfig format homo_width_p (common.Param.Param) : 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 bline_list = bline.get_list_at_frame(fr) outer_width = to_Synfig_axis(outer_width_p.get_value(fr), "real") expand = to_Synfig_axis(expand_p.get_value(fr), "real") sharp_cusps = sharp_cusps_p.get_value(fr) r_tip0 = r_tip0_p.get_value(fr) r_tip1 = r_tip1_p.get_value(fr) homo_width = homo_width_p.get_value(fr) gv = get_outline_grow(fr) # Setup chunk list side_a, side_b = [], [] # Check if looped loop = bline.get_loop() # Iterators end_it = len(bline_list) next_it = 0 if loop: iter_it = end_it - 1 else: iter_it = next_it next_it += 1 first_point = bline_list[iter_it] first_tangent = bline_list[0].get_tangent2() last_tangent = first_point.get_tangent1() # 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_list) > 1: prev_it = iter_it prev_it -= 1 prev_it %= len(bline_list) prev_point = bline_list[prev_it] curve = Hermite(prev_point.get_vertex(), first_point.get_vertex(), prev_point.get_tangent2(), first_point.get_tangent1()) last_tangent = curve.derivative(1.0 - CUSP_TANGENT_ADJUST) first = not loop while next_it != end_it: bp1 = bline_list[iter_it] bp2 = bline_list[next_it] # Setup tangents prev_t = bp1.get_tangent1() iter_t = bp1.get_tangent2() next_t = bp2.get_tangent1() split_flag = bp1.get_split_tangent_angle() or bp1.get_split_tangent_radius() # 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 = bp2.get_vertex() - bp1.get_vertex() # 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_list) next_it += 1 continue # Setup the curve curve = Hermite(bp1.get_vertex(), bp2.get_vertex(), iter_t, next_t) # Setup width's iter_w = gv*(bp1.get_width() * outer_width * 0.5 + expand) next_w = gv*(bp2.get_width() * 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 = bp1.get_vertex() + t1*iter_w p2 = bp1.get_vertex() + 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 = bp1.get_vertex() - t1*iter_w p2 = bp1.get_vertex() - 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([bp1.get_vertex() + (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([bp1.get_vertex() - (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 generated 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_list) next_it += 1 if len(side_a) < 2 or len(side_b) < 2: return origin_cur = origin_p.get_value(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_list[-1] vertex = bp.get_vertex() tangent = last_tangent.norm() w = gv*(bp.get_width() * 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_list[0] vertex = bp.get_vertex() tangent = first_tangent.norm() w = gv*(bp.get_width() * 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 synfig_star(st_val, mx_points, origin_p, radius1_p, radius2_p, angle_p, points_p, regular_polygon_p, 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_p (common.Param.Param) : Lottie format radius1 animation radius2_p (common.Param.Param) : Lottie format radius2 animation angle_p (common.Param.Param) : Lottie format angle animation points_p (common.Param.Param) : Lottie format points animation regular_polygon_p (common.Param.Param) : Synfig format regularPolygon animation fr (int) : Frame number Returns: (None) """ angle = angle_p.get_value(fr) points = int(to_Synfig_axis(points_p.get_value(fr), "real")) radius1 = to_Synfig_axis(radius1_p.get_value(fr), "real") radius2 = to_Synfig_axis(radius2_p.get_value(fr), "real") regular_polygon = regular_polygon_p.get_value(fr) origin_cur = origin_p.get_value(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)