Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update jupyter web_visualizer for ipywidgets 8 #6239

Merged
merged 3 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions cpp/open3d/visualization/webrtc_server/PeerConnectionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,19 @@ PeerConnectionManager::PeerConnectionManager(
// Register api in http server.
func_["/api/getMediaList"] = [this](const struct mg_request_info *req_info,
const Json::Value &in) -> Json::Value {
utility::LogInfo("[Called HTTP API] /api/getMediaList");
utility::LogDebug("[Called HTTP API] /api/getMediaList");
return this->GetMediaList();
};

func_["/api/getIceServers"] = [this](const struct mg_request_info *req_info,
const Json::Value &in) -> Json::Value {
utility::LogInfo("[Called HTTP API] /api/getIceServers");
utility::LogDebug("[Called HTTP API] /api/getIceServers");
return this->GetIceServers();
};

func_["/api/call"] = [this](const struct mg_request_info *req_info,
const Json::Value &in) -> Json::Value {
utility::LogInfo("[Called HTTP API] /api/call");
utility::LogDebug("[Called HTTP API] /api/call");
std::string peerid;
std::string url; // window_uid.
std::string options;
Expand All @@ -167,7 +167,7 @@ PeerConnectionManager::PeerConnectionManager(
func_["/api/getIceCandidate"] =
[this](const struct mg_request_info *req_info,
const Json::Value &in) -> Json::Value {
utility::LogInfo("[Called HTTP API] /api/getIceCandidate");
utility::LogDebug("[Called HTTP API] /api/getIceCandidate");
std::string peerid;
if (req_info->query_string) {
CivetServer::getParam(req_info->query_string, "peerid", peerid);
Expand All @@ -178,7 +178,7 @@ PeerConnectionManager::PeerConnectionManager(
func_["/api/addIceCandidate"] =
[this](const struct mg_request_info *req_info,
const Json::Value &in) -> Json::Value {
utility::LogInfo("[Called HTTP API] /api/addIceCandidate");
utility::LogDebug("[Called HTTP API] /api/addIceCandidate");
std::string peerid;
if (req_info->query_string) {
CivetServer::getParam(req_info->query_string, "peerid", peerid);
Expand All @@ -188,7 +188,7 @@ PeerConnectionManager::PeerConnectionManager(

func_["/api/hangup"] = [this](const struct mg_request_info *req_info,
const Json::Value &in) -> Json::Value {
utility::LogInfo("[Called HTTP API] /api/hangup");
utility::LogDebug("[Called HTTP API] /api/hangup");
std::string peerid;
if (req_info->query_string) {
CivetServer::getParam(req_info->query_string, "peerid", peerid);
Expand Down Expand Up @@ -713,7 +713,7 @@ void PeerConnectionManager::SendInitFramesToPeer(const std::string &peerid) {

void PeerConnectionManager::CloseWindowConnections(
const std::string &window_uid) {
utility::LogInfo("PeerConnectionManager::CloseWindowConnections: {}",
utility::LogDebug("PeerConnectionManager::CloseWindowConnections: {}",
window_uid);
std::set<std::string> peerids;
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class PeerConnectionManager {
const std::string state =
webrtc::DataChannelInterface::DataStateString(
data_channel_->state());
utility::LogInfo(
utility::LogDebug(
"DataChannelObserver::OnStateChange label: {}, state: {}, "
"peerid: {}",
label, state, peerid_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ void WebRTCWindowSystem::SendInitFrames(const std::string &window_uid) {
std::string WebRTCWindowSystem::CallHttpAPI(const std::string &entry_point,
const std::string &query_string,
const std::string &data) const {
utility::LogInfo("[Called HTTP API (custom handshake)] {}", entry_point);
utility::LogDebug("[Called HTTP API (custom handshake)] {}", entry_point);

std::string query_string_trimmed = "";
if (!query_string.empty() && query_string[0] == '?') {
Expand Down
2 changes: 1 addition & 1 deletion cpp/pybind/make_install_pip_package.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
# Note: Since `make python-package` clears PYTHON_COMPILED_MODULE_DIR every time,
# it is guaranteed that there is only one wheel in ${PYTHON_PACKAGE_DST_DIR}/pip_package/*.whl
file(GLOB WHEEL_FILE "${PYTHON_PACKAGE_DST_DIR}/pip_package/*.whl")
execute_process(COMMAND ${Python3_EXECUTABLE} -m pip uninstall open3d --yes)
execute_process(COMMAND ${Python3_EXECUTABLE} -m pip uninstall open3d open3d-cpu --yes)
execute_process(COMMAND ${Python3_EXECUTABLE} -m pip install ${WHEEL_FILE} -U)
8 changes: 8 additions & 0 deletions python/js/amd-public-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// In an AMD module, we set the public path using the magic requirejs 'module' dependency
// See https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#module
// Since 'module' is a requirejs magic module, we must include 'module' in the webpack externals configuration.
var module = require('module');
var url = new URL(module.uri, document.location)
// Using lastIndexOf('/')+1 gives us the empty string if there is no '/', so pathname becomes '/'
url.pathname = url.pathname.slice(0, url.pathname.lastIndexOf('/') + 1);
__webpack_public_path__ = `${url.origin}${url.pathname}`;
9 changes: 0 additions & 9 deletions python/js/lib/embed.js

This file was deleted.

8 changes: 1 addition & 7 deletions python/js/lib/extension.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
// This file contains the javascript that is run when the notebook is loaded.
// It contains some requirejs configuration and the `load_ipython_extension`
// which is required for any notebook extension.
//
// Some static assets may be required by the custom widget javascript. The base
// url for the notebook is not known at build time and is therefore computed
// dynamically.
__webpack_public_path__ = document.querySelector('body').getAttribute('data-base-url') + 'nbextensions/open3d';


// Configure requirejs
if (window.require) {
Expand All @@ -21,5 +15,5 @@ if (window.require) {

// Export the required load_ipython_extension
module.exports = {
load_ipython_extension: function() {}
load_ipython_extensionn() {}
};
13 changes: 6 additions & 7 deletions python/js/lib/labplugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ var base = require('@jupyter-widgets/base');
module.exports = {
id: 'open3d:plugin',
requires: [base.IJupyterWidgetRegistry],
activate: function(app, widgets) {
widgets.registerWidget({
name: 'open3d',
version: plugin.version,
exports: plugin
});
activate: (app, widgets) => {
widgets.registerWidget({
name: 'open3d',
version: plugin.version,
exports: plugin
});
},
autoStart: true
};

81 changes: 32 additions & 49 deletions python/js/lib/web_visualizer.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,8 @@
// ----------------------------------------------------------------------------
// - Open3D: www.open3d.org -
// - Open3D: www.open3d.org -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018-2023 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// Copyright(c) 2018-2023 www.open3d.org
// SPDX - License - Identifier: MIT
// ----------------------------------------------------------------------------

// Jupyter widget for Open3D WebRTC visualizer. See web_visualizer.py for the
Expand All @@ -44,49 +25,51 @@ let WebRtcStreamer = require("./webrtcstreamer");
//
// When serializing the entire widget state for embedding, only values that
// differ from the defaults will be specified.
let WebVisualizerModel = widgets.DOMWidgetModel.extend({
defaults: _.extend(widgets.DOMWidgetModel.prototype.defaults(), {
_model_name: "WebVisualizerModel",
_view_name: "WebVisualizerView",
_model_module: "open3d",
_view_module: "open3d",
// @...@ is configured by cpp/pybind/make_python_package.cmake.
_model_module_version: "@PROJECT_VERSION_THREE_NUMBER@",
_view_module_version: "@PROJECT_VERSION_THREE_NUMBER@",
}),
});
class WebVisualizerModel extends widgets.DOMWidgetModel {
defaults() {
return _.extend(widgets.DOMWidgetModel.prototype.defaults(), {
_model_name: "WebVisualizerModel",
_view_name: "WebVisualizerView",
_model_module: "open3d",
_view_module: "open3d",
// @...@ is configured by cpp/pybind/make_python_package.cmake.
_model_module_version: "@PROJECT_VERSION_THREE_NUMBER@",
_view_module_version: "@PROJECT_VERSION_THREE_NUMBER@",
});
}
}

// Custom View. Renders the widget model.
let WebVisualizerView = widgets.DOMWidgetView.extend({
sleep: function (time_ms) {
class WebVisualizerView extends widgets.DOMWidgetView {
sleep(time_ms) {
return new Promise((resolve) => setTimeout(resolve, time_ms));
},
}

logAndReturn: function (value) {
logAndReturn(value) {
console.log("logAndReturn: ", value);
return value;
},
}

callResultReady: function (callId) {
callResultReady(callId) {
let pyjs_channel = this.model.get("pyjs_channel");
console.log("Current pyjs_channel:", pyjs_channel);
let callResultMap = JSON.parse(this.model.get("pyjs_channel"));
return callId in callResultMap;
},
}

extractCallResult: function (callId) {
extractCallResult(callId) {
if (!this.callResultReady(callId)) {
throw "extractCallResult not ready yet.";
}
let callResultMap = JSON.parse(this.model.get("pyjs_channel"));
return callResultMap[callId];
},
}

/**
* Hard-coded to call "call_http_api". Args and return value are all
* strings.
*/
callPython: async function (func, args = []) {
async callPython(func, args = []) {
let callId = this.callId.toString();
this.callId++;
let message = {
Expand Down Expand Up @@ -116,9 +99,9 @@ let WebVisualizerView = widgets.DOMWidgetView.extend({
json_result
);
return json_result;
},
};

commsCall: function (url, data = {}) {
commsCall(url, data = {}) {
// https://stackoverflow.com/a/736970/1255535
// parseUrl(url).hostname
// parseUrl(url).entryPoint
Expand Down Expand Up @@ -180,9 +163,9 @@ let WebVisualizerView = widgets.DOMWidgetView.extend({
} else {
throw "Unsupported entryPoint: " + entryPoint;
}
},
}

render: function () {
render() {
let windowUID = this.model.get("window_uid");
let onClose = function () {
console.log("onClose() called for window_uid:", windowUID);
Expand Down Expand Up @@ -214,8 +197,8 @@ let WebVisualizerView = widgets.DOMWidgetView.extend({
this.commsCall.bind(this)
);
this.webRtcClient.connect(windowUID);
},
});
}
}

module.exports = {
WebVisualizerModel: WebVisualizerModel,
Expand Down
6 changes: 3 additions & 3 deletions python/js/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "open3d",
"version": "@PROJECT_VERSION_THREE_NUMBER@",
"description": "Open3D: A Modern Library for 3D Data Processing",
"author": "Open3D.org",
"description": "@PROJECT_DESCRIPTION@",
"author": "@PROJECT_EMAIL@",
"main": "lib/index.js",
"repository": {
"type": "git",
Expand Down Expand Up @@ -36,7 +36,7 @@
"rimraf": "^2.6.1"
},
"dependencies": {
"@jupyter-widgets/base": "^1.1 || ^2 || ^3 || ^4",
"@jupyter-widgets/base": "^2 || ^3 || ^4 || ^5 || ^6",
"lodash": "^4.17.4",
"webrtc-adapter": "^4.2.2"
},
Expand Down
34 changes: 14 additions & 20 deletions python/js/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ module.exports = (env, argv) => {
// some configuration for requirejs, and provides the legacy
// "load_ipython_extension" function which is required for any notebook
// extension.
//
entry: "./lib/extension.js",
output: {
filename: "extension.js",
path: path.resolve(__dirname, "..", "open3d", "nbextension"),
libraryTarget: "amd",
publicPath: "", // publicPath is set in extension.js
},
devtool,
},
Expand All @@ -32,47 +30,43 @@ module.exports = (env, argv) => {
// This bundle contains the implementation for the custom widget views and
// custom widget.
// It must be an amd module
//
entry: "./lib/index.js",
entry: ['./amd-public-path.js', './lib/index.js'],
output: {
filename: "index.js",
path: path.resolve(__dirname, "..", "open3d", "nbextension"),
libraryTarget: "amd",
publicPath: "",
publicPath: "", // Set in amd-public-path.js
},
devtool,
module: {
rules: rules,
},
externals: ["@jupyter-widgets/base"],
// 'module' is the magic requirejs dependency used to set the publicPath
externals: ['@jupyter-widgets/base', 'module']
},
{
// Embeddable open3d bundle
//
// This bundle is generally almost identical to the notebook bundle
// containing the custom widget views and models.
//
// The only difference is in the configuration of the webpack public path
// for the static assets.
//
// It will be automatically distributed by unpkg to work with the static
// widget embedder.
//
// The target bundle is always `dist/index.js`, which is the path required
// by the custom widget embedder.
// This bundle is identical to the notebook bundle containing the custom
// widget views and models. The only difference is it is placed in the
// dist/ directory and shipped with the npm package for use from a CDN
// like jsdelivr.
//
entry: "./lib/embed.js",
// The target bundle is always `dist/index.js`, which is the path
// required by the custom widget embedder.
entry: ['./amd-public-path.js', './lib/index.js'],
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
libraryTarget: "amd",
publicPath: "https://unpkg.com/open3d@" + version + "/dist/",
publicPath: '', // Set in amd-public-path.js
},
devtool,
module: {
rules: rules,
},
externals: ["@jupyter-widgets/base"],
// 'module' is the magic requirejs dependency used to set the publicPath
externals: ['@jupyter-widgets/base', 'module']
},
];
};
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[build-system]
requires = ["ipywidgets>=7.6.0", "pygments>=2.7.4", "jupyter_packaging~=0.10", "jupyterlab>=3.0.0,==3.*", "setuptools>=40.8.0", "wheel"]
requires = ["ipywidgets>=8.0.3", "pygments>=2.7.4", "jupyter_packaging~=0.10", "jupyterlab>=3.0.0,==3.*", "setuptools>=50.3.2", "wheel==0.38.4"]
build-backend = "setuptools.build_meta"
2 changes: 1 addition & 1 deletion python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import jupyterlab # noqa # pylint: disable=unused-import
except ImportError as error:
print(error.__class__.__name__ + ": " + error.message)
print("Run `pip install jupyter_packaging ipywidgets jupyterlab`.")
print("Run `pip install -r requirements-jupyter-build.txt`.")

here = os.path.dirname(os.path.abspath(__file__))
js_dir = os.path.join(here, "js")
Expand Down