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 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_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 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)