Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
19 changes: 11 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
yarn=3

- name: Setup pip cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-3.9-${{ hashFiles('package.json') }}
Expand All @@ -38,7 +38,7 @@ jobs:
run: echo "::set-output name=dir::$(yarn cache dir)"

- name: Setup yarn cache
uses: actions/cache@v2
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand All @@ -47,7 +47,7 @@ jobs:
yarn-

- name: Setup OpenCascade build cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
packages/opencascade/lib/jupytercad.opencascade.js
Expand Down Expand Up @@ -84,6 +84,7 @@ jobs:
python/jupytercad_core/dist/jupytercad*
python/jupytercad_lab/dist/jupytercad*
python/jupytercad_app/dist/jupytercad*
python/jupytercad/dist/jupytercad*
if-no-files-found: error

test_isolated:
Expand Down Expand Up @@ -148,7 +149,7 @@ jobs:
shell: bash -l {0}
run: |
set -eux
cp ./jupytercad_core/dist/jupytercad*.whl ./jupytercad_lab/dist/jupytercad*.whl ./jupytercad_app/dist/jupytercad*.whl .
cp ./jupytercad_core/dist/jupytercad*.whl ./jupytercad_lab/dist/jupytercad*.whl ./jupytercad_app/dist/jupytercad*.whl ./jupytercad/dist/jupytercad*.whl .
python -m pip install jupytercad*.whl

- name: Install dependencies
Expand All @@ -159,7 +160,7 @@ jobs:
run: jlpm install

- name: Set up browser cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/pw-browsers
Expand Down Expand Up @@ -221,6 +222,7 @@ jobs:
pip
jupyterlite-core
jupyterlite-xeus
jupyter_server

- name: Download extension package
uses: actions/download-artifact@v4
Expand All @@ -231,15 +233,16 @@ jobs:
shell: bash -l {0}
run: |
set -eux
cp ./jupytercad_core/dist/jupytercad*.whl ./jupytercad_lab/dist/jupytercad*.whl ./jupytercad_app/dist/jupytercad*.whl .
python -m pip install jupytercad*.whl
cp ./jupytercad_core/dist/jupytercad*.whl jupytercad_core-0.0.1-py3-none-any.whl
cp ./jupytercad_lab/dist/jupytercad*.whl jupytercad_lab-0.0.1-py3-none-any.whl
cp ./jupytercad_app/dist/jupytercad*.whl jupytercad_app-0.0.1-py3-none-any.whl

- name: Build the lite site
shell: bash -l {0}
working-directory: lite
run: |
set -eux
mkdir -p content && cp ../examples/*.jcad ../examples/*.STEP ../examples/*.stl ./content
mkdir -p content && cp ../examples/*.jcad ../examples/*.STEP ../examples/*.stl ../examples/*.ipynb ./content
jupyter lite build --contents content --output-dir dist

- name: Upload github-pages artifact
Expand Down
16 changes: 14 additions & 2 deletions lite/environment.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
name: xeus-python-kernel
channels:
- https://repo.mamba.pm/emscripten-forge
- conda-forge
- https://prefix.dev/emscripten-forge-dev
- https://prefix.dev/conda-forge
dependencies:
- python=3.13
- pip
- xeus-python
- requests
- jupyter_ydoc=2.1.5
- ypywidgets>=0.9.6,<0.10.0
- comm>=0.1.2,<0.2.0
- pydantic>=2,<3
- pip:
- yjs-widgets>=0.4,<0.5
- my-jupyter-shared-drive
- ../jupytercad_core-0.0.1-py3-none-any.whl
- ../jupytercad_lab-0.0.1-py3-none-any.whl
8 changes: 1 addition & 7 deletions lite/jupyter-lite.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
{
"jupyter-lite-schema-version": 0,
"jupyter-config-data": {
"appName": "My JupyterCAD App",
"disabledExtensions": [
"@jupyter/collaboration-extension",
"@jupyter/docprovider-extension"
]
}
"jupyter-config-data": {}
}
6 changes: 3 additions & 3 deletions packages/base/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,12 +268,12 @@ export class JupyterCadPanel extends SplitPanel {

(this._consoleTracker.widgetAdded as any).emit(consolePanel);
await consolePanel.sessionContext.ready;
await consolePanel.console.inject(
`from jupytercad_lab import CadDocument\ndoc = CadDocument("${jcadPath}")`
);
this.addWidget(this._consoleView);
this.setRelativeSizes([2, 1]);
this._consoleOpened = true;
await consolePanel.console.inject(
`from jupytercad_lab import CadDocument\ndoc = CadDocument("${jcadPath}")`
);
consolePanel.console.sessionContext.kernelChanged.connect((_, arg) => {
if (!arg.newValue) {
this.removeConsole();
Expand Down
2 changes: 2 additions & 0 deletions python/jupytercad/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ dependencies = [
"jupytercad_core==3.0.1",
"jupytercad_lab==3.0.1",
"jupytercad_app==3.0.1",
"jupyter-collaboration>=3,<4",
"jupyterlab>=4.3,<5",
]
dynamic = ["version"]
license = {file = "LICENSE"}
Expand Down
2 changes: 1 addition & 1 deletion python/jupytercad_app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"@lumino/widgets": "^2.0.0",
"react": "^18.0.1",
"yjs": "^13.5.40",
"yjs-widgets": "^0.3.7"
"yjs-widgets": "^0.4"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
Expand Down
4 changes: 2 additions & 2 deletions python/jupytercad_core/jupytercad_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
warnings.warn("Importing 'jupytercad_core' outside a proper installation.")
__version__ = "dev"

from .handlers import setup_handlers


def _jupyter_labextension_paths():
return [{"src": "labextension", "dest": "@jupytercad/jupytercad-core"}]
Expand All @@ -24,5 +22,7 @@ def _load_jupyter_server_extension(server_app):
server_app: jupyterlab.labapp.LabApp
JupyterLab application instance
"""
from .handlers import setup_handlers

setup_handlers(server_app.web_app)
server_app.log.info("Registered jupytercad server extension")
2 changes: 0 additions & 2 deletions python/jupytercad_core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
dependencies = [
"jupyter_server>=2.0.6,<3",
"jupyter_ydoc>=2,<4",
"jupyter-collaboration>=3.1.0,<4",
"pydantic>=2,<3",
]
dynamic = ["version", "description", "authors", "urls", "keywords"]
Expand Down
3 changes: 1 addition & 2 deletions python/jupytercad_core/src/jcadplugin/modelfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export class JupyterCadJcadModelFactory
/**
* Whether the model is collaborative or not.
*/
readonly collaborative =
document.querySelectorAll('[data-jupyter-lite-root]')[0] === undefined;
readonly collaborative = true;

/**
* The name of the model.
Expand Down
3 changes: 1 addition & 2 deletions python/jupytercad_core/src/stepplugin/modelfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export class JupyterCadStepModelFactory
/**
* Whether the model is collaborative or not.
*/
readonly collaborative =
document.querySelectorAll('[data-jupyter-lite-root]')[0] === undefined;
readonly collaborative = true;

/**
* The name of the model.
Expand Down
3 changes: 1 addition & 2 deletions python/jupytercad_core/src/stlplugin/modelfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export class JupyterCadStlModelFactory
/**
* Whether the model is collaborative or not.
*/
readonly collaborative =
document.querySelectorAll('[data-jupyter-lite-root]')[0] === undefined;
readonly collaborative = true;

/**
* The name of the model.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
ShapeMetadata,
IAny,
)
from .utils import normalize_path

logger = logging.getLogger(__file__)

Expand Down Expand Up @@ -72,7 +71,7 @@ def _path_to_comm(cls, filePath: Optional[str]) -> Dict:
contentType = None

if filePath is not None:
path = normalize_path(filePath)
path = filePath
file_name = Path(path).name
try:
ext = file_name.split(".")[1].lower()
Expand Down
8 changes: 0 additions & 8 deletions python/jupytercad_lab/jupytercad_lab/notebook/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
from enum import Enum
from urllib.parse import urljoin

Expand All @@ -15,10 +14,3 @@ def multi_urljoin(*parts) -> str:
parts[0],
"/".join(part for part in parts[1:]),
)


def normalize_path(path: str) -> str:
if os.path.isabs(path):
return path
else:
return os.path.abspath(os.path.join(os.getcwd(), path))
4 changes: 2 additions & 2 deletions python/jupytercad_lab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"@lumino/messaging": "^2.0.0",
"@lumino/widgets": "^2.0.0",
"react": "^18.0.1",
"yjs-widgets": "^0.3.7"
"yjs-widgets": "^0.4"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
Expand Down Expand Up @@ -119,7 +119,7 @@
},
"@jupyter/docprovider": {
"singleton": true,
"bundled": false
"bundled": true
},
"yjs-widgets": {
"singleton": true,
Expand Down
2 changes: 1 addition & 1 deletion python/jupytercad_lab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
dependencies = [
"pycrdt",
"ypywidgets>=0.9.0,<0.10.0",
"yjs-widgets>=0.3.7,<0.4",
"yjs-widgets>=0.4,<0.5",
"comm>=0.1.2,<0.2.0",
"pydantic>=2,<3",
"jupytercad_core>=3.0.0a0,<4",
Expand Down
85 changes: 52 additions & 33 deletions python/jupytercad_lab/src/notebookrenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ import {
JupyterCadModel
} from '@jupytercad/schema';

import { showErrorMessage } from '@jupyterlab/apputils';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { Contents } from '@jupyterlab/services';
import { MessageLoop } from '@lumino/messaging';
import { Panel, Widget } from '@lumino/widgets';
import * as Y from 'yjs';
import {
IJupyterYWidget,
IJupyterYWidgetManager,
JupyterYModel
JupyterYModel,
JupyterYDoc
} from 'yjs-widgets';
import { Toolbar } from '@jupyterlab/ui-components';
import { ConsolePanel } from '@jupyterlab/console';
Expand Down Expand Up @@ -94,10 +95,6 @@ export class YJupyterCADLuminoWidget extends Panel {
private _buildWidget = (options: IOptions) => {
const { commands, workerRegistry, model, externalCommands, tracker } =
options;
// Ensure the model filePath is relevant with the shared model path.
if (model.sharedModel.getState('path')) {
model.filePath = model.sharedModel.getState('path') as string;
}
const content = new JupyterCadPanel({
model: model,
workerRegistry: workerRegistry as IJCadWorkerRegistry
Expand Down Expand Up @@ -153,17 +150,57 @@ export const notebookRenderePlugin: JupyterFrontEndPlugin<void> = {
console.error('Missing IJupyterYWidgetManager token!');
return;
}
if (!drive) {
console.error('Missing ICollaborativeDrive token!');
return;
}

class YJupyterCADModelFactory extends YJupyterCADModel {
ydocFactory(commMetadata: ICommMetadata): Y.Doc {
protected async initialize(commMetadata: {
[key: string]: any;
}): Promise<void> {
const { path, format, contentType } = commMetadata;
const fileFormat = format as Contents.FileFormat;

if (!drive) {
showErrorMessage(
'Error using the JupyterCAD Python API',
'You cannot use the JupyterCAD Python API without a collaborative drive. You need to install a package providing collaboration features (e.g. jupyter-collaboration).'
);
throw new Error(
'Failed to create the YDoc without a collaborative drive'
);
}

// The path of the project is relative to the path of the notebook
let currentWidgetPath = '';
const currentWidget = app.shell.currentWidget;
if (
currentWidget instanceof NotebookPanel ||
currentWidget instanceof ConsolePanel
) {
currentWidgetPath = currentWidget.sessionContext.path;
}

let localPath = '';
if (path) {
localPath = PathExt.join(PathExt.dirname(currentWidgetPath), path);

// If the file does not exist yet, create it
try {
await app.serviceManager.contents.get(localPath);
} catch (e) {
await app.serviceManager.contents.save(localPath, {
content: btoa('{}'),
format: 'base64'
});
}
} else {
// If the user did not provide a path, do not create
localPath = PathExt.join(
PathExt.dirname(currentWidgetPath),
'unsaved_project'
);
}

const sharedModel = drive!.sharedModelFactory.createNew({
path,
path: localPath,
format: fileFormat,
contentType,
collaborative: true
Expand All @@ -174,28 +211,10 @@ export const notebookRenderePlugin: JupyterFrontEndPlugin<void> = {
});

this.jupyterCADModel.contentsManager = app.serviceManager.contents;
this.jupyterCADModel.filePath = localPath;

if (!sharedModel) {
// The path of the project is set to the path of the notebook, to be able to
// add local geoJSON/shape file in a "file-less" project.
let currentWidgetPath: string | undefined = undefined;
const currentWidget = app.shell.currentWidget;
if (
currentWidget instanceof NotebookPanel ||
currentWidget instanceof ConsolePanel
) {
currentWidgetPath = currentWidget.sessionContext.path;
}

if (currentWidgetPath) {
this.jupyterCADModel.filePath = PathExt.join(
PathExt.dirname(currentWidgetPath),
'unsaved_project'
);
}
}

return this.jupyterCADModel.sharedModel.ydoc;
this.ydoc = this.jupyterCADModel.sharedModel.ydoc;
this.sharedModel = new JupyterYDoc(commMetadata, this.ydoc);
}
}

Expand Down
Loading
Loading