diff --git a/devtools/create_labextension_symlink.py b/devtools/create_labextension_symlink.py new file mode 100644 index 00000000..9412d2a4 --- /dev/null +++ b/devtools/create_labextension_symlink.py @@ -0,0 +1,56 @@ +import os +import sys +import argparse +from pathlib import Path + + +def get_extension_paths(): + base_dir = Path(sys.prefix) + nglview_js_widgets = "nglview-js-widgets" + nbextension_dest = base_dir / "share" / "jupyter" / "nbextensions" / nglview_js_widgets + labextension_dest = base_dir / "share" / "jupyter" / "labextensions" / nglview_js_widgets + return nbextension_dest, labextension_dest + + +def create_symlink(source, destination): + try: + if os.path.islink(destination): + os.remove(destination) + os.symlink(source, destination) + print(f"Created symlink: {destination} -> {source}") + except OSError as e: + print(f"Failed to create symlink: {e}", file=sys.stderr) + + +def remove_symlink(destination): + try: + if os.path.islink(destination): + os.remove(destination) + print(f"Removed symlink: {destination}") + else: + print(f"No symlink to remove at: {destination}") + except OSError as e: + print(f"Failed to remove symlink: {e}", file=sys.stderr) + + +def main(): + parser = argparse.ArgumentParser(description="Create or remove symlinks for nbextension and labextension.") + parser.add_argument('-d', '--delete', action='store_true', help="Remove the symlinks instead of creating them.") + args = parser.parse_args() + + nbextension_dest, labextension_dest = get_extension_paths() + + base_dir = Path(__file__).resolve().parent.parent + nbextension_source = base_dir / 'nglview' / 'nbextension' + labextension_source = base_dir / 'nglview' / 'labextension' + + if args.delete: + remove_symlink(nbextension_dest) + remove_symlink(labextension_dest) + else: + create_symlink(nbextension_source, nbextension_dest) + create_symlink(labextension_source, labextension_dest) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/js/package.json b/js/package.json index 4a860ef7..6701d777 100644 --- a/js/package.json +++ b/js/package.json @@ -52,6 +52,7 @@ "watch": "run-p watch:src watch:labextension", "watch:src": "tsc -w", "watch:labextension": "jupyter labextension watch .", + "watch:install": "nodemon --watch src/widget_ngl.ts --exec 'npm install'", "test": "mocha" }, "dependencies": { @@ -88,7 +89,8 @@ "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "babel-loader": "^8.2.2", - "@babel/plugin-transform-object-rest-spread": "^7.14.5" + "@babel/plugin-transform-object-rest-spread": "^7.14.5", + "nodemon": "^2.0.22" }, "sideEffects": [ "style/*.css", diff --git a/js/src/widget_ngl.ts b/js/src/widget_ngl.ts index 60201dd7..1295a9eb 100644 --- a/js/src/widget_ngl.ts +++ b/js/src/widget_ngl.ts @@ -265,13 +265,16 @@ export }, this); this.stage.viewerControls.signals.changed.add(function () { - this.serialize_camera_orientation(); + setTimeout(() => { + // https://github.com/nglviewer/nglview/issues/948#issuecomment-898121063 + this.serialize_camera_orientation(); + }, 100); + var m = this.stage.viewerControls.getOrientation(); if (that._synced_model_ids.length > 0 && that._ngl_focused == 1) { that._synced_model_ids.forEach(async function (mid) { var model = await that.model.widget_manager.get_model(mid) for (var k in model.views) { - var pview = model.views[k]; var view = await model.views[k] if (view.uuid != that.uuid) { view.stage.viewerControls.orient(m); diff --git a/notebooks/orientation.ipynb b/notebooks/orientation.ipynb new file mode 100644 index 00000000..1d4e817f --- /dev/null +++ b/notebooks/orientation.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 20, + "id": "82035655-b580-4e4d-86bf-5a90563c2840", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c7cd4ef8a8ca410c96f8cad4d48928b2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import nglview as nv\n", + "\n", + "view = nv.demo()\n", + "view" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "7bf1502e-ce30-4b5c-9a72-5a2360322c82", + "metadata": {}, + "outputs": [], + "source": [ + "view.control.rotate([0.6, -0.4, 0.3, -0.4])" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d07a8c2b-806d-42c6-b841-e49460688348", + "metadata": {}, + "outputs": [], + "source": [ + "nv.write_html('index.html', view)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f397830a-2c05-4275-bdbe-40e0b58f621a", + "metadata": {}, + "outputs": [], + "source": [ + "!open index.html" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "5c00397d-cdd4-4196-91e4-fb55d98774a0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[65.20023905320915,\n", + " -93.88834423662117,\n", + " 5.2160191242567215,\n", + " 0,\n", + " -31.29611474554039,\n", + " 13.040047810641841,\n", + " -93.88834423662117,\n", + " 0,\n", + " 88.67232511236445,\n", + " 31.29611474554039,\n", + " -5.216019124256737,\n", + " 0,\n", + " -37.284461975097656,\n", + " -42.47041130065918,\n", + " -44.55349826812744,\n", + " 1]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "view._camera_orientation" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}