Skip to content

Commit

Permalink
[python/viewer] New dedicated widgets manager. (#244)
Browse files Browse the repository at this point in the history
* [python/viewer] Add new dedicated widgets manager.
* [python/viewer] Enable to capture widgets on top of three.js rendering. 
* [python/viewer] Always capture frames in '.png' format.
* [python/viewer] Disable legend if not all robots have colors when replaying trajectories.
* [python/viewer] Fix replay when at least one of the trajectories is empty.

Co-authored-by: Alexis Duburcq <[email protected]>
  • Loading branch information
duburcqa and Alexis Duburcq authored Dec 8, 2020
1 parent 7248150 commit 94ab739
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 183 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
cmake_minimum_required(VERSION 3.10)

# Set the build version
set(BUILD_VERSION 1.4.22)
set(BUILD_VERSION 1.4.23)

# Add definition of Jiminy version for C++ headers
add_definitions("-DJIMINY_VERSION=\"${BUILD_VERSION}\"")
Expand Down
20 changes: 20 additions & 0 deletions python/jiminy_py/src/jiminy_py/meshcat/html2canvas.min.js

Large diffs are not rendered by default.

53 changes: 16 additions & 37 deletions python/jiminy_py/src/jiminy_py/meshcat/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>
<div id="widgets"></div>
<div id="meshcat-pane"></div>

<script type="text/javascript" src="main.min.js"></script>
<script type="text/javascript" src="html2canvas.min.js"></script>
<script type="text/javascript" src="webm-writer-0.3.0.js"></script>
<script type="text/javascript" src="legend.js"></script>
<script type="text/javascript" src="widgets.js"></script>
<script>
// Instantiate a new Meshcat viewer
var viewer = new MeshCat.Viewer(document.getElementById("meshcat-pane"), false);
Expand All @@ -24,37 +26,9 @@
if (cmd.type == "ready") {
viewer.connection.send("meshcat:ok");
} else if (cmd.type == "legend") {
var legend = document.getElementById("legend");
if (legend == null) {
createLegend("legend");
legend = document.getElementById("legend");
}
if (cmd.text) {
setLegendItem(legend, cmd.id, cmd.text, cmd.color);
} else {
removeLegendItem(legend, cmd.id);
}
updateLegend(cmd);
} else if (cmd.type == "logo") {
var logo = document.getElementById("logo");
if (cmd.data) {
if (logo == null) {
logo = document.createElement("img");
logo.id = "logo";
logo.draggable = false;
logo.style.position = "fixed";
logo.style.bottom = "20px";
logo.style.left = "20px";
logo.style.pointerEvents = "none";
document.body.prepend(logo);
}
logo.setAttribute('src', 'data:image/png;base64,' + cmd.data);
logo.style.width = cmd.width.toString() + "px";
logo.style.height = cmd.height.toString() + "px";
} else {
if (logo !== null) {
document.body.removeChild(logo);
}
}
updateLogo(cmd);
} else {
handle_command.call(this, cmd);
}
Expand Down Expand Up @@ -128,12 +102,9 @@
// not disable controls update while moving the camera using
// the mouse, which is nice because it enforces the camera
// to be "straight".
viewer.capture_image = function() {
viewer.camera.updateProjectionMatrix();
viewer.renderer.render(viewer.scene, viewer.camera);
viewer.animator.after_render();
viewer.needs_render = false;
return viewer.renderer.domElement.toDataURL('image/webp', 1.0);
viewer.capture_image = async function() {
var snapshot_canvas = await captureFrameAndWidgets(viewer);
return snapshot_canvas.toDataURL('image/png'); // Export to webp is about 70% slower than png.
};
function animate() {
if (continue_animating) {
Expand Down Expand Up @@ -183,6 +154,14 @@
height: 100vh;
overflow: hidden;
}

#widgets {
width: 100vw;
height: 100vh;
position: absolute;
pointer-events: none;
overflow: hidden;
}
</style>
<script id="embedded-json"></script>
</body>
Expand Down
79 changes: 0 additions & 79 deletions python/jiminy_py/src/jiminy_py/meshcat/legend.js

This file was deleted.

6 changes: 3 additions & 3 deletions python/jiminy_py/src/jiminy_py/meshcat/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ async def start_video_recording_async(client: HTMLResponse,
viewer.animator.capturer = new WebMWriter({{
quality: 0.99999, // Lossless codex VP8L is not supported
frameRate: {fps}
}});
}}
""")
Expand All @@ -161,8 +160,9 @@ async def start_video_recording_async(client: HTMLResponse,
async def add_video_frame_async(client: HTMLResponse) -> Awaitable[None]:
await client.html.page.evaluate("""
() => {
viewer.renderer.render(viewer.scene, viewer.camera);
viewer.animator.capturer.addFrame(viewer.renderer.domElement);
captureFrameAndWidgets(viewer).then(function(canvas) {
viewer.animator.capturer.addFrame(canvas);
});
}
""")

Expand Down
179 changes: 179 additions & 0 deletions python/jiminy_py/src/jiminy_py/meshcat/widgets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
var widgets = document.getElementById("widgets");
var widgetsCanvas;
var widgetsNeedRender = true;

window.onresize = function(event) {
widgetsNeedRender = true;
};

// ***************** Legend utilities ******************

function draggable(el) {
el.style.cursor = "move";
el.style.pointerEvents = "auto";
el.style.userSelect = "none";

el.addEventListener("mousedown", function(e) {
var offsetX = e.clientX - parseInt(window.getComputedStyle(this).left, 10);
var offsetY = e.clientY - parseInt(window.getComputedStyle(this).top, 10);

function mouseMoveHandler(e) {
el.style.top = (e.clientY - offsetY) + "px";
el.style.left = (e.clientX - offsetX) + "px";
}

function reset() {
window.removeEventListener("mousemove", mouseMoveHandler);
window.removeEventListener("mouseup", reset);
}

window.addEventListener("mousemove", mouseMoveHandler);
window.addEventListener("mouseup", reset);

widgetsNeedRender = true;
});
}

function createLegend(id) {
var legend = document.createElement("div");
legend.id = id;
legend.style.position = "fixed";
legend.style.top = "20px";
legend.style.left = "20px";
legend.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
legend.style.border = "2px solid";
legend.style.borderRadius = "5px";
legend.style.borderColor = "Silver";
legend.style.padding = "4px 10px 4px 8px";
widgets.prepend(legend);

draggable(legend);
}

function setLegendItem(legend, id, text, color) {
var box = document.createElement("div");
box.style.display = "inline-block";
box.style.height = "20px";
box.style.width = "20px";
box.style.margin = "4px 8px 4px 0px";
box.style.backgroundColor = color;

var label = document.createElement("span");
label.textContent = text;
label.style.fontFamily = "Dejavu Sans";
label.style.textAlign = "center";
label.style.alignItems = "center";

var boxContainer = document.getElementById(id);
if (boxContainer == null) {
boxContainer = document.createElement("div");
boxContainer.id = id;
boxContainer.style.display = "flex";
boxContainer.style.alignItems = "center";
legend.appendChild(boxContainer);
}
while (boxContainer.firstChild) {
boxContainer.removeChild(boxContainer.lastChild);
}
boxContainer.appendChild(box);
boxContainer.appendChild(label);
}

function removeLegendItem(legend, id) {
var elem = document.getElementById(id);
if (elem !== null) {
legend = elem.parentNode;
legend.removeChild(elem);
if (!legend.childElementCount)
{
widgets.removeChild(legend);
}
}
}

function updateLegend(cmd) {
var legend = document.getElementById("legend");
if (legend == null) {
createLegend("legend");
legend = document.getElementById("legend");
}
if (cmd.text) {
setLegendItem(legend, cmd.id, cmd.text, cmd.color);
} else {
removeLegendItem(legend, cmd.id);
}
widgetsNeedRender = true;
}

// ***************** Logo utilities ******************

function createLogo(id) {
var logo = document.createElement("img");
logo.id = id;
logo.draggable = false;
logo.style.position = "fixed";
logo.style.bottom = "20px";
logo.style.left = "20px";
widgets.prepend(logo);
}

function setLogo(logo, dataURL, width, height) {
logo.setAttribute('src', 'data:image/png;base64,' + dataURL);
logo.style.width = width.toString() + "px";
logo.style.height = height.toString() + "px";
}

function removeLogo(logo) {
widgets.removeChild(logo);
}

function updateLogo(cmd) {
var logo = document.getElementById("logo");
if (cmd.data) {
if (logo == null) {
createLogo("logo");
logo = document.getElementById("logo");
}
setLogo(logo, cmd.data, cmd.width, cmd.height);
} else {
if (logo !== null) {
removeLogo(logo);
}
}
widgetsNeedRender = true;
}

// **************** Widgets utilities ******************

async function getWidgetsCanvas(viewer) {
if (widgetsNeedRender) {
viewer.camera.updateProjectionMatrix();
viewer.renderer.render(viewer.scene, viewer.camera);
widgetsCanvas = await html2canvas(widgets, {
allowTaint: true,
useCORS: true,
backgroundColor: "rgba(0,0,0,0)",
removeContainer: true
});
widgetsNeedRender = false;
}
return widgetsCanvas;
}

async function captureFrameAndWidgets(viewer) {
var snapshotCanvas = document.createElement('canvas');
var ctx = snapshotCanvas.getContext('2d');
ctx.canvas.width = document.documentElement.clientWidth;
ctx.canvas.height = document.documentElement.clientHeight;

var widgetsCanvas = await getWidgetsCanvas(viewer);
viewer.camera.updateProjectionMatrix();
viewer.renderer.render(viewer.scene, viewer.camera);
viewer.animator.after_render();
viewer.needs_render = false;

ctx.drawImage(viewer.renderer.domElement, 0, 0);
ctx.drawImage(widgetsCanvas, 0, 0);

return snapshotCanvas;
}
Loading

0 comments on commit 94ab739

Please sign in to comment.