From 7a53a9576875631a3068712825a85a187ab4d955 Mon Sep 17 00:00:00 2001 From: Robin Deits Date: Sun, 3 Nov 2024 22:43:47 -0500 Subject: [PATCH] Drop Cassette.jl by rewriting animation handlers --- Project.toml | 2 -- notebooks/animation.ipynb | 8 +++--- src/MeshCat.jl | 30 ++++++++++++++++++++- src/animations.jl | 21 ++++++++------- src/atframe.jl | 31 +++++---------------- src/visualizer.jl | 57 +++++++++++++++++++++++---------------- test/visualizer.jl | 4 +-- 7 files changed, 88 insertions(+), 65 deletions(-) diff --git a/Project.toml b/Project.toml index e11e43e..f6c8bb9 100644 --- a/Project.toml +++ b/Project.toml @@ -5,7 +5,6 @@ version = "0.16.3" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" -Cassette = "7057c7e9-c182-5462-911a-8362d720325c" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" @@ -26,7 +25,6 @@ Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] -Cassette = "0.2.5, 0.3" Colors = "0.9, 0.10, 0.11, 0.12" CoordinateTransformations = "0.5, 0.6" DocStringExtensions = "0.5, 0.6, 0.7, 0.8, 0.9" diff --git a/notebooks/animation.ipynb b/notebooks/animation.ipynb index 169cd78..66450c1 100644 --- a/notebooks/animation.ipynb +++ b/notebooks/animation.ipynb @@ -95,7 +95,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = Animation()\n", + "anim = Animation(vis)\n", "\n", "atframe(anim, 0) do\n", " # within the context of atframe, calls to \n", @@ -153,7 +153,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = Animation()\n", + "anim = Animation(vis)\n", "\n", "atframe(anim, 0) do\n", " settransform!(vis[\"/Cameras/default\"], Translation(0, 0, 0))\n", @@ -179,7 +179,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = Animation()\n", + "anim = Animation(vis)\n", "\n", "atframe(anim, 0) do\n", " setprop!(vis[\"/Cameras/default/rotated/\"], \"zoom\", 1.0)\n", @@ -226,7 +226,7 @@ "metadata": {}, "outputs": [], "source": [ - "anim = Animation()\n", + "anim = Animation(vis)\n", "atframe(anim, 0) do\n", " setvisible!(vis[:sphere], false)\n", " settransform!(vis[:box1], Translation(0.0, 0, 0))\n", diff --git a/src/MeshCat.jl b/src/MeshCat.jl index 20d5563..8fa38ce 100644 --- a/src/MeshCat.jl +++ b/src/MeshCat.jl @@ -41,7 +41,6 @@ using Sockets: listen, @ip_str, IPAddr, IPv4, IPv6 using Base64: base64encode using MsgPack: MsgPack, pack using Pkg.Artifacts: @artifact_str -import Cassette import FFMPEG import HTTP import Logging @@ -102,6 +101,35 @@ abstract type AbstractMaterial end include("util.jl") include("trees.jl") using .SceneTrees + +struct AnimationContext + animation + frame::Int +end + +""" +Low-level type which manages the actual meshcat server. See [`Visualizer`](@ref) +for the public-facing interface. +""" +mutable struct CoreVisualizer + tree::SceneNode + connections::Set{HTTP.WebSockets.WebSocket} + host::IPAddr + port::Int + server::HTTP.Server + animation_contexts::Vector{AnimationContext} + + function CoreVisualizer(host::IPAddr = ip"127.0.0.1", default_port=8700) + connections = Set([]) + tree = SceneNode() + port = find_open_port(host, default_port, 500) + core = new(tree, connections, host, port) + core.server = start_server(core) + core.animation_contexts = AnimationContext[] + return core + end +end + include("mesh_files.jl") include("geometry.jl") include("objects.jl") diff --git a/src/animations.jl b/src/animations.jl index c1d574a..7f50e74 100644 --- a/src/animations.jl +++ b/src/animations.jl @@ -46,17 +46,19 @@ function Base.merge!(a::AnimationClip, others::AnimationClip...) end struct Animation - clips::Dict{Path, AnimationClip} + visualizer::CoreVisualizer default_framerate::Int -end + clips::Dict{Path, AnimationClip} -""" -Create a new animation. See [`atframe`](@ref) to adjust object poses or properties -in that animation. + """ + Create a new animation. See [`atframe`](@ref) to adjust object poses or properties + in that animation. + + $(TYPEDSIGNATURES) + """ + Animation(visualizer::CoreVisualizer; fps=30) = new(visualizer, fps, Dict()) +end -$(TYPEDSIGNATURES) -""" -Animation(fps::Int=30) = Animation(Dict{Path, AnimationClip}(), fps) """ Merge multiple animations, storing the result in `a`. @@ -65,6 +67,7 @@ $(TYPEDSIGNATURES) """ function Base.merge!(a::Animation, others::Animation...) for other in others + @assert a.visualizer === other.visualizer @assert a.default_framerate == other.default_framerate merge!(merge!, a.clips, other.clips) # merge clips recursively end @@ -80,7 +83,7 @@ The animations may involve the same properties or different properties (animations of the same property on the same path will have their events interleaved). All animations must have the same framerate. """ -Base.merge(a::Animation, others::Animation...) = merge!(Animation(a.default_framerate), a, others...) +Base.merge(a::Animation, others::Animation...) = merge!(Animation(a.visualizer; fps=a.default_framerate), a, others...) """ Convert the `.tar` file of still images produced by the meshcat "record" feature diff --git a/src/atframe.jl b/src/atframe.jl index d25d5d1..728328d 100644 --- a/src/atframe.jl +++ b/src/atframe.jl @@ -47,28 +47,6 @@ end js_position(t::Transformation) = convert(Vector, t(SVector(0., 0, 0))) -Cassette.@context AnimationCtx - -function Cassette.overdub(ctx::AnimationCtx, ::typeof(settransform!), vis::Visualizer, tform::Transformation) - animation, frame = ctx.metadata - clip = getclip!(animation, vis.path) - _setprop!(clip, frame, "scale", "vector3", js_scaling(tform)) - _setprop!(clip, frame, "position", "vector3", js_position(tform)) - _setprop!(clip, frame, "quaternion", "quaternion", js_quaternion(tform)) -end - -function Cassette.overdub(ctx::AnimationCtx, ::typeof(setprop!), vis::Visualizer, prop::AbstractString, value) - animation, frame = ctx.metadata - clip = getclip!(animation, vis.path) - _setprop!(clip, frame, prop, get_property_type(prop), value) -end - -function Cassette.overdub(ctx::AnimationCtx, ::typeof(setprop!), vis::Visualizer, prop::AbstractString, jstype::AbstractString, value) - animation, frame = ctx.metadata - clip = getclip!(animation, vis.path) - _setprop!(clip, frame, prop, jstype, value) -end - """ Call the given function `f`, but intercept any `settransform!` or `setprop!` calls and apply them to the given animation at the given frame instead. @@ -81,7 +59,7 @@ Usage: vis = Visualizer() setobject!(vis[:cube], Rect(Vec(0.0, 0.0, 0.0), Vec(0.5, 0.5, 0.5))) -anim = Animation() +anim = Animation(vis) # At frame 0, set the cube's position to be the origin atframe(anim, 0) do @@ -97,6 +75,11 @@ setanimation!(vis, anim) ``` """ function atframe(f, animation::Animation, frame::Integer) - Cassette.overdub(AnimationCtx(metadata=(animation, frame)), f) + push!(animation.visualizer.animation_contexts, AnimationContext(animation, frame)) + try + f() + finally + pop!(animation.visualizer.animation_contexts) + end return animation end diff --git a/src/visualizer.jl b/src/visualizer.jl index 8460ff6..138ec90 100644 --- a/src/visualizer.jl +++ b/src/visualizer.jl @@ -1,24 +1,3 @@ -""" -Low-level type which manages the actual meshcat server. See [`Visualizer`](@ref) -for the public-facing interface. -""" -mutable struct CoreVisualizer - tree::SceneNode - connections::Set{HTTP.WebSockets.WebSocket} - host::IPAddr - port::Int - server::HTTP.Server - - function CoreVisualizer(host::IPAddr = ip"127.0.0.1", default_port=8700) - connections = Set([]) - tree = SceneNode() - port = find_open_port(host, default_port, 500) - core = new(tree, connections, host, port) - core.server = start_server(core) - return core - end -end - function find_open_port(host, default_port, max_retries) for port in default_port:(default_port + max_retries) server = try @@ -217,7 +196,15 @@ of its parents, so setting the transform of `vis[:group1]` affects the poses of the objects at `vis[:group1][:box1]` and `vis[:group1][:box2]`. """ function settransform!(vis::Visualizer, tform::Transformation) - send(vis.core, SetTransform(tform, vis.path)) + if !isempty(vis.core.animation_contexts) + ctx = last(vis.core.animation_contexts) + clip = getclip!(ctx.animation, vis.path) + _setprop!(clip, ctx.frame, "scale", "vector3", js_scaling(tform)) + _setprop!(clip, ctx.frame, "position", "vector3", js_position(tform)) + _setprop!(clip, ctx.frame, "quaternion", "quaternion", js_quaternion(tform)) + else + send(vis.core, SetTransform(tform, vis.path)) + end vis end @@ -240,7 +227,29 @@ $(TYPEDSIGNATURES) with the Base.setproperty! function introduced in Julia v0.7) """ function setprop!(vis::Visualizer, property::AbstractString, value) - send(vis.core, SetProperty(vis.path, property, value)) + if !isempty(vis.core.animation_contexts) + ctx = last(vis.core.animation_contexts) + clip = getclip!(ctx.animation, vis.path) + _setprop!(clip, ctx.frame, property, get_property_type(property), value) + else + send(vis.core, SetProperty(vis.path, property, value)) + end + vis +end + +""" +Variation of `setprop!` which accepts an explicit type for the underlying JS property. This property type is only used within an animation context. + +$(TYPEDSIGNATURES) +""" +function setprop!(vis::Visualizer, property::AbstractString, jstype::AbstractString, value) + if !isempty(vis.core.animation_contexts) + ctx = last(vis.core.animation_contexts) + clip = getclip!(ctx.animation, vis.path) + _setprop!(clip, ctx.frame, property, jstype, value) + else + send(vis.core, SetProperty(vis.path, property, value)) + end vis end @@ -275,3 +284,5 @@ For example, if you have `vis::Visualizer` with path `/meshcat/foo`, you can do """ Base.getindex(vis::Visualizer, path...) = Visualizer(vis.core, joinpath(vis.path, path...)) + +Animation(vis::Visualizer, args...; kw...) = Animation(vis.core, args...; kw...) diff --git a/test/visualizer.jl b/test/visualizer.jl index 8dc7683..7461e94 100644 --- a/test/visualizer.jl +++ b/test/visualizer.jl @@ -236,7 +236,7 @@ end end @testset "Animation" begin - anim1 = Animation() + anim1 = Animation(vis) atframe(anim1, 0) do settransform!(vis[:shapes][:box], Translation(0., 0, 0)) end @@ -244,7 +244,7 @@ end settransform!(vis[:shapes][:box], Translation(2., 0, 0) ∘ LinearMap(RotZ(π/2))) end setanimation!(vis, anim1) - anim2 = Animation() + anim2 = Animation(vis) atframe(anim2, 0) do setprop!(vis["/Cameras/default/rotated/"], "zoom", 1) end