def test_hex_short(self): self.assert_nvector_equal( parse_color("#abc"), NVector(0xa/0xf, 0xb/0xf, 0xc/0xf, 1) ) self.assert_nvector_equal( parse_color("#abc"), NVector(0xa/0xf, 0xb/0xf, 0xc/0xf, 1) )
def test_rgba(self): self.assert_nvector_equal( parse_color("rgba(12, 34, 56, 0.7)"), NVector(12/0xff, 34/0xff, 56/0xff, 0.7) ); self.assert_nvector_equal( parse_color("rgba(12%, 34%, 56%, 0.7)"), NVector(0.12, 0.34, 0.56, 0.7) );
def test_hex_long(self): self.assert_nvector_equal( parse_color("#abcdef"), NVector(0xab/0xff, 0xcd/0xff, 0xef/0xff, 1) ) self.assert_nvector_equal( parse_color("#ABCDEF"), NVector(0xab/0xff, 0xcd/0xff, 0xef/0xff, 1) )
def test_current(self): self.assert_nvector_equal( parse_color("currentColor", NVector(0.1, 0.2, 0.3, 0.4)), NVector(0.1, 0.2, 0.3, 0.4) ); self.assert_nvector_equal( parse_color("inherit", NVector(0.1, 0.2, 0.3, 0.4)), NVector(0.1, 0.2, 0.3, 0.4) );
def test_inkscape_center(self): parser = SvgParser() element = ElementTree.Element("g") group = objects.Rect(NVector(100, 200), NVector(300, 400)) dest_transform = objects.Transform() element.attrib[parser.qualified("inkscape", "transform-center-x")] = 0 element.attrib[parser.qualified("inkscape", "transform-center-y")] = 0 parser.parse_transform(element, group, dest_transform) self.assert_transform( dest_transform, anchor_point=NVector(100, 200), position=NVector(100, 200), scale=NVector(100, 100), rotation=0, skew_axis=0, skew=0, ) element.attrib[parser.qualified("inkscape", "transform-center-x")] = 20 element.attrib[parser.qualified("inkscape", "transform-center-y")] = -30 parser.parse_transform(element, group, dest_transform) self.assert_transform( dest_transform, anchor_point=NVector(120, 230), position=NVector(120, 230), scale=NVector(100, 100), rotation=0, skew_axis=0, skew=0, )
def get_fill(obj, ro): # TODO animation fillc = obj.active_material.diffuse_color fill = lottie.objects.Fill(NVector(*fillc[:-1])) fill.name = obj.active_material.name fill.opacity.value = fillc[-1] * 100 return fill
class RenderOptions: scene: bpy.types.Scene line_width: float = 0 camera_angles: NVector = NVector(0, 0, 0) @property def camera(self): return scene.camera def vector_to_camera_norm(self, vector): return NVector(*bpy_extras.object_utils.world_to_camera_view( self.scene, self.scene.camera, vector)) def vpix3d(self, vector): v3d = self.vector_to_camera_norm(vector) v3d.x *= self.scene.render.resolution_x v3d.y *= -self.scene.render.resolution_y return v3d def vpix(self, vector): v2d = self.vpix3d(vector) v2d.components.pop() return v2d def vpix3d_r(self, obj, vector): return (self.vpix3d(obj.matrix_world @ vector)) def vpix_r(self, obj, vector): v2d = self.vpix3d_r(obj, vector) v2d.components.pop() return v2d
def context_to_tgs(context): scene = context.scene root = context.view_layer.layer_collection initial_frame = scene.frame_current try: animation = lottie.objects.Animation() animation.in_point = scene.frame_start animation.out_point = scene.frame_end animation.frame_rate = scene.render.fps animation.width = scene.render.resolution_x animation.height = scene.render.resolution_y animation.name = scene.name layer = animation.add_layer(lottie.objects.ShapeLayer()) ro = RenderOptions(scene) if scene.render.use_freestyle: ro.line_width = scene.render.line_thickness else: ro.line_width = 0 ro.camera_angles = NVector( *scene.camera.rotation_euler) * 180 / math.pi collection_to_group(root, layer, ro) adjust_animation(scene, animation, ro) return animation finally: scene.frame_set(initial_frame)
def test_named(self): self.assert_nvector_equal( parse_color("transparent"), NVector(0, 0, 0, 0) ); self.assert_nvector_equal( parse_color("red"), NVector(1, 0, 0, 1) ); self.assert_nvector_equal( parse_color("lime"), NVector(0, 1, 0, 1) ); self.assert_nvector_equal( parse_color("blue"), NVector(0, 0, 1, 1) );
def test_multi_trans(self): parser = SvgParser() element = ElementTree.Element("g") group = objects.Rect(NVector(100, 200), NVector(300, 400)) dest_transform = objects.Transform() element.attrib["transform"] = "scale(0.7, 0.5) rotate(45) translate(12, 34)" parser.parse_transform(element, group, dest_transform) self.assert_transform( dest_transform, anchor_point=NVector(0, 0), position=NVector(12, 34), scale=NVector(70, 50), rotation=45, skew_axis=0, skew=0, )
def curve_apply_material(obj, g, ro): if obj.data.fill_mode != "NONE": g.add_shape(get_fill(obj, ro)) if ro.line_width > 0: # TODO animation strokec = obj.active_material.line_color stroke = lottie.objects.Stroke(NVector(*strokec[:-1]), ro.line_width) stroke.opacity.value = fillc[-1] * 100 g.add_shape(stroke)
def test_matrix(self): parser = SvgParser() element = ElementTree.Element("g") group = objects.Rect(NVector(100, 200), NVector(300, 400)) dest_transform = objects.Transform() m = TransformMatrix() m.scale(0.7, 0.5) m.rotate(-math.pi/4) m.translate(12, 34) element.attrib["transform"] = m.to_css_2d() parser.parse_transform(element, group, dest_transform) self.assert_transform( dest_transform, anchor_point=NVector(0, 0), position=NVector(12, 34), scale=NVector(70, 50), rotation=45, skew_axis=0, skew=0, )
def assert_convert(self, comps, expect, mode, expect_rt=None): color = self.color(*comps) conv = color.converted(mode) self.assertEqual( conv.mode, mode, "%s -> %s has the wrong color space: %s" % (self.color_mode, mode, conv.mode)) self._veq( conv, NVector(*expect), "%s -> %s has the wrong result: %s (expected %s)" % (self.color_mode, mode, conv, expect)) conv.convert(self.color_mode) self.assertEqual(conv.mode, self.color_mode) if expect_rt is None: expect_rt = color self._veq( conv, expect_rt, "%s -> %s round trip failed: %s (expected %s)" % (self.color_mode, mode, conv, expect_rt))
"lib")) from lottie.utils import script from lottie import objects from lottie import NVector, Color an = objects.Animation(80) precomp = objects.Precomp("myid", an) # Define stuff in the precomposition (a circle moving left to right) layer = objects.ShapeLayer() precomp.add_layer(layer) layer.out_point = 60 circle = layer.add_shape(objects.Ellipse()) circle.size.value = NVector(100, 100) circle.position.add_keyframe(0, NVector(-50, 50)) circle.position.add_keyframe(60, NVector(512 + 50, 50)) fill = layer.add_shape(objects.Fill()) fill.color.add_keyframe(0, Color(1, 1, 0)) fill.color.add_keyframe(60, Color(1, 0, 0)) # plays the precomp as it is pcl0 = an.add_layer(objects.PreCompLayer("myid")) # plays the precomp, offset in time by 20 frames and in space by 100 pixels pcl1 = an.add_layer(objects.PreCompLayer("myid")) pcl1.start_time = 20 pcl1.transform.position.value = NVector(0, 100)
stroke.width.value = 10 g2 = layer.add_shape(objects.Group()) circle = g2.add_shape(objects.Ellipse()) circle.size.value = Point(200, 200) circle.position.value = Point(128 + 256, 256) fill = g2.add_shape(objects.GradientFill()) fill.gradient_type = objects.GradientType.Radial fill.start_point.value = Point(128 + 256, 256) fill.end_point.value = Point(128 + 256 + 100, 256) fill.colors.set_stops([(0, Color(1, 0, 0)), (1, Color(1, 1, 0))]) #fill.highlight_length.add_keyframe(0, -50) #fill.highlight_length.add_keyframe(30, 50) #fill.highlight_length.add_keyframe(59, -50) #fill.highlight_angle.value = 45 fill.highlight_length.value = 90 g3 = layer.add_shape(objects.Group()) circle = g3.add_shape(objects.Ellipse()) circle.size.value = Point(100, 100) circle.position.value = Point(128, 356) fill = g3.add_shape(objects.GradientFill()) fill.start_point.value = Point(100, 0) fill.end_point.value = Point(200, 0) fill.colors.set_stops([(0, NVector(1, 0, 0, 1)), (0.5, NVector(1, .5, 0, 1)), (1, NVector(1, 1, 0, 0))]) script.script_main(an)
def test_sanitize_anim(self): an = objects.Animation() nl = an.add_layer(objects.NullLayer()) nl.transform.scale.add_keyframe(0, NVector(50, 50)) nl.transform.scale.add_keyframe(60, NVector(200, 200)) nl.transform.scale.keyframes[-1].start = None an.width = 128 an.height = 256 an.frame_rate = 69 an.tgs_sanitize() self.assertEqual( an.to_dict(), { "ip": 0, "op": 60, "fr": 60, "w": 512, "h": 512, "ddd": 0, "v": objects.Animation._version, "layers": [ { "ty": 3, "ks": { "a": { "a": 0, "k": [0, 0] }, "p": { "a": 0, "k": [0, 0] }, "s": { "a": 1, "k": [ { "s": [100, 100], "e": [400, 400], "t": 0, "o": { "x": [0], "y": [0] }, "i": { "x": [1], "y": [1] }, }, { "t": 60, }, ] }, "r": { "a": 0, "k": 0 }, "o": { "a": 0, "k": 100 }, "sk": { "a": 0, "k": 0 }, "sa": { "a": 0, "k": 0 }, }, "ao": 0, "ddd": 0, "st": 0, "sr": 1, "bm": 0, "ind": 0, "ip": 0, "op": 60, }, ], "assets": [], })
def gpencil_to_shape(obj, parent, ro): # Object / GreasePencil gpen = parent.add_shape(lottie.objects.Group()) gpen.name = obj.name animated = AnimationWrapper(obj.data) # GPencilLayer for layer in reversed(obj.data.layers): if layer.hide: continue glay = gpen.add_shape(lottie.objects.Group()) glay.name = layer.info opacity = animated.property('layers["%s"].opacity' % layer.info) glay.transform.opacity = opacity.to_lottie_prop(lambda x: x * 100) gframe = None # GPencilFrame for frame in layer.frames: if gframe: if not gframe.transform.opacity.animated: gframe.transform.opacity.add_keyframe( 0, 100, lottie.objects.easing.Jump()) gframe.transform.opacity.add_keyframe(frame.frame_number, 0) gframe = glay.add_shape(lottie.objects.Group()) gframe.name = "frame %s" % frame.frame_number if frame.frame_number != 0: gframe.transform.opacity.add_keyframe( 0, 0, lottie.objects.easing.Jump()) gframe.transform.opacity.add_keyframe(frame.frame_number, 100) # GPencilStroke for stroke in reversed(frame.strokes): gstroke = gframe.add_shape(lottie.objects.Group()) path = gstroke.add_shape(lottie.objects.Path()) path.shape.value.closed = stroke.draw_cyclic pressure = 0 for p in stroke.points: add_point_to_poly(path.shape.value, p, ro, obj) pressure += p.pressure pressure /= len(stroke.points) # Material matp = obj.data.materials[stroke.material_index] # TODO Gradients / animations # MaterialGPencilStyle material = matp.grease_pencil if material.show_fill: fill_sh = gstroke.add_shape(lottie.objects.Fill()) fill_sh.name = matp.name fill_sh.color.value = NVector(*material.fill_color[:-1]) fill_sh.opacity.value = material.fill_color[-1] * 100 if material.show_stroke: stroke_sh = lottie.objects.Stroke() gstroke.add_shape(stroke_sh) stroke_sh.name = matp.name if stroke.end_cap_mode == "ROUND": stroke_sh.line_cap = lottie.objects.LineCap.Round elif stroke.end_cap_mode == "FLAT": stroke_sh.line_cap = lottie.objects.LineCap.Butt stroke_w = stroke.line_width * pressure * obj.data.pixel_factor if obj.data.stroke_thickness_space == "WORLDSPACE": # TODO do this properly stroke_w /= 9 stroke_sh.width.value = stroke_w stroke_sh.color.value = NVector(*material.color[:-1]) stroke_sh.opacity.value = material.color[-1] * 100 return gpen
star.inner_radius.value = 20 star.outer_radius.value = 50 star.position.value = Point(50, 50) star_layer.add_shape(objects.Fill(Color(1, 1, 0))) star_layer.add_shape(objects.Stroke(Color(0, 0, 0), 5)) star_layer.transform.anchor_point = star.position star_layer.transform.position.value = Point(50, 256) star_layer.transform.rotation.add_keyframe(0, 0) star_layer.transform.rotation.add_keyframe(last_frame, -360) circle_layer = objects.ShapeLayer() an.add_layer(circle_layer) circle_layer.parent = base circle = circle_layer.add_shape(objects.Ellipse()) circle.size.value = NVector(100, 100) circle_layer.add_shape(objects.Fill(Color(1, 0, 0))) circle_layer.add_shape(objects.Stroke(Color(0, 0, 0), 5)) circle_layer.transform.position.add_keyframe(0, Point(256, 512-50)) circle_layer.transform.position.add_keyframe(last_frame/2, Point(256, 50)) circle_layer.transform.position.add_keyframe(last_frame, Point(256, 512-50)) scl = base.add_child(objects.SolidColorLayer("#0000ff")) scl.transform.scale.value.x *= 0.2 scl.transform.position.value.x = 205 star_background = star_layer.add_child(objects.SolidColorLayer("#0000ff", 100, 100))
def _parse_color(color): # Inkscape colors if re.match(r"^[0-9a-fA-F]{8}$", color): return NVector(*(int(color[i:i + 2], 16) / 0xff for i in range(0, 8, 2))) return parse_color(color)
def vector_to_camera_norm(self, vector): return NVector(*bpy_extras.object_utils.world_to_camera_view( self.scene, self.scene.camera, vector))
def test_hsla(self): self.assert_nvector_equal( parse_color("hsla(0, 100%, 50%, 0.7)"), NVector(1, 0, 0, 0.7) ); self.assert_nvector_equal( parse_color("hsla(60, 100%, 50%, 0.7)"), NVector(1, 1, 0, 0.7) ); self.assert_nvector_equal( parse_color("hsla(120, 100%, 50%, 0.7)"), NVector(0, 1, 0, 0.7) ); self.assert_nvector_equal( parse_color("hsla(180, 100%, 50%, 0.7)"), NVector(0, 1, 1, 0.7) ); self.assert_nvector_equal( parse_color("hsla(240, 100%, 50%, 0.7)"), NVector(0, 0, 1, 0.7) ); self.assert_nvector_equal( parse_color("hsla(300, 100%, 50%, 0.7)"), NVector(1, 0, 1, 0.7) ); self.assert_nvector_equal( parse_color("hsla(360, 100%, 50%, 0.7)"), NVector(1, 0, 0, 0.7) ); self.assert_nvector_equal( parse_color("hsla(120, 100%, 0%, 0.7)"), NVector(0, 0, 0, 0.7) ); self.assert_nvector_equal( parse_color("hsla(120, 100%, 100%, 0.7)"), NVector(1, 1, 1, 0.7) ); self.assert_nvector_equal( parse_color("hsla(120, 100%, 25%, 0.7)"), NVector(0, 0.5, 0, 0.7) ); self.assert_nvector_equal( parse_color("hsla(120, 75%, 75%, 0.7)"), NVector(0.5625, 0.9375, 0.5625, 0.7) );
def to_vector(self): return NVector(*(v for k, v in sorted(self.value.items())))
last_frame = 180 an = objects.Animation(last_frame) layer = objects.ShapeLayer() an.add_layer(layer) layer.auto_orient = True group = layer.add_shape(objects.Group()) star = objects.Star() star.inner_radius.value = 20 star.outer_radius.value = 50 star.position.value = Point(0, 0) group.add_shape(star) group.add_shape(objects.Ellipse(NVector(0, 35), NVector(16, 16))) layer.add_shape(objects.Fill(Color(1, 1, 0))) tl = 120 layer.transform.position.add_keyframe(last_frame / 4 * 0, Point(+50, 256), out_tan=NVector(0, -tl), in_tan=NVector(-tl, 0)) layer.transform.position.add_keyframe(last_frame / 4 * 1, Point(256, +50), out_tan=NVector(+tl, 0), in_tan=NVector(0, -tl)) layer.transform.position.add_keyframe(last_frame / 4 * 2, Point(462, 256), out_tan=NVector(0, +tl),