From 73649694b971441ea1c2b4ee5510dd29c6aa4239 Mon Sep 17 00:00:00 2001 From: Duc Trung LE Date: Wed, 15 Nov 2023 09:08:15 +0100 Subject: [PATCH] Create worker registry --- packages/base/src/fcplugin/modelfactory.ts | 180 ++++----- packages/base/src/fcplugin/plugins.ts | 354 +++++++++--------- packages/base/src/index.ts | 1 - packages/base/src/jcadplugin/modelfactory.ts | 102 ----- packages/base/src/jcadplugin/plugins.ts | 152 -------- packages/base/src/mainview.tsx | 12 +- packages/base/src/token.ts | 45 +++ packages/base/src/widget.tsx | 17 +- packages/occ-worker/src/index.ts | 1 + packages/occ-worker/src/occworker.ts | 63 ++++ packages/schema/package.json | 3 +- .../jupytercad_core/jcad_ydoc.py | 69 ++++ python/jupytercad-core/pyproject.toml | 6 +- .../jupytercad-core}/src/factory.ts | 22 +- python/jupytercad-core/src/index.ts | 87 +---- python/jupytercad-core/src/plugin.ts | 74 ++++ python/jupytercad-core/src/workerregistry.ts | 30 ++ python/jupytercad-lab/package.json | 6 + .../jupytercad-lab/src/jcadplugin/plugins.ts | 17 +- yarn.lock | 4 +- 20 files changed, 621 insertions(+), 624 deletions(-) delete mode 100644 packages/base/src/jcadplugin/modelfactory.ts delete mode 100644 packages/base/src/jcadplugin/plugins.ts create mode 100644 packages/occ-worker/src/occworker.ts create mode 100644 python/jupytercad-core/jupytercad_core/jcad_ydoc.py rename {packages/base => python/jupytercad-core}/src/factory.ts (71%) create mode 100644 python/jupytercad-core/src/plugin.ts create mode 100644 python/jupytercad-core/src/workerregistry.ts diff --git a/packages/base/src/fcplugin/modelfactory.ts b/packages/base/src/fcplugin/modelfactory.ts index acd80825..2f5774b9 100644 --- a/packages/base/src/fcplugin/modelfactory.ts +++ b/packages/base/src/fcplugin/modelfactory.ts @@ -1,101 +1,101 @@ -import { - IAnnotationModel, - IJupyterCadDoc, - JupyterCadModel -} from '@jupytercad/schema'; -import { DocumentRegistry } from '@jupyterlab/docregistry'; -import { Contents } from '@jupyterlab/services'; +// import { +// IAnnotationModel, +// IJupyterCadDoc, +// JupyterCadModel +// } from '@jupytercad/schema'; +// import { DocumentRegistry } from '@jupyterlab/docregistry'; +// import { Contents } from '@jupyterlab/services'; -/** - * A Model factory to create new instances of JupyterCadModel. - */ -export class JupyterCadFCModelFactory - implements DocumentRegistry.IModelFactory -{ - constructor(options: JupyterCadFCModelFactory.IOptions) { - this._annotationModel = options.annotationModel; - } +// /** +// * A Model factory to create new instances of JupyterCadModel. +// */ +// export class JupyterCadFCModelFactory +// implements DocumentRegistry.IModelFactory +// { +// constructor(options: JupyterCadFCModelFactory.IOptions) { +// this._annotationModel = options.annotationModel; +// } - /** - * Whether the model is collaborative or not. - */ - readonly collaborative = true; +// /** +// * Whether the model is collaborative or not. +// */ +// readonly collaborative = true; - /** - * The name of the model. - * - * @returns The name - */ - get name(): string { - return 'jupytercad-fcmodel'; - } +// /** +// * The name of the model. +// * +// * @returns The name +// */ +// get name(): string { +// return 'jupytercad-fcmodel'; +// } - /** - * The content type of the file. - * - * @returns The content type - */ - get contentType(): Contents.ContentType { - return 'FCStd'; - } +// /** +// * The content type of the file. +// * +// * @returns The content type +// */ +// get contentType(): Contents.ContentType { +// return 'FCStd'; +// } - /** - * The format of the file. - * - * @returns the file format - */ - get fileFormat(): Contents.FileFormat { - return 'base64'; - } +// /** +// * The format of the file. +// * +// * @returns the file format +// */ +// get fileFormat(): Contents.FileFormat { +// return 'base64'; +// } - /** - * Get whether the model factory has been disposed. - * - * @returns disposed status - */ - get isDisposed(): boolean { - return this._disposed; - } +// /** +// * Get whether the model factory has been disposed. +// * +// * @returns disposed status +// */ +// get isDisposed(): boolean { +// return this._disposed; +// } - /** - * Dispose the model factory. - */ - dispose(): void { - this._disposed = true; - } +// /** +// * Dispose the model factory. +// */ +// dispose(): void { +// this._disposed = true; +// } - /** - * Get the preferred language given the path on the file. - * - * @param path path of the file represented by this document model - * @returns The preferred language - */ - preferredLanguage(path: string): string { - return ''; - } +// /** +// * Get the preferred language given the path on the file. +// * +// * @param path path of the file represented by this document model +// * @returns The preferred language +// */ +// preferredLanguage(path: string): string { +// return ''; +// } - /** - * Create a new instance of JupyterCadModel. - * - * @returns The model - */ - createNew( - options: DocumentRegistry.IModelOptions - ): JupyterCadModel { - const model = new JupyterCadModel({ - sharedModel: options.sharedModel, - languagePreference: options.languagePreference, - annotationModel: this._annotationModel - }); - return model; - } +// /** +// * Create a new instance of JupyterCadModel. +// * +// * @returns The model +// */ +// createNew( +// options: DocumentRegistry.IModelOptions +// ): JupyterCadModel { +// const model = new JupyterCadModel({ +// sharedModel: options.sharedModel, +// languagePreference: options.languagePreference, +// annotationModel: this._annotationModel +// }); +// return model; +// } - private _annotationModel: IAnnotationModel; - private _disposed = false; -} +// private _annotationModel: IAnnotationModel; +// private _disposed = false; +// } -export namespace JupyterCadFCModelFactory { - export interface IOptions { - annotationModel: IAnnotationModel; - } -} +// export namespace JupyterCadFCModelFactory { +// export interface IOptions { +// annotationModel: IAnnotationModel; +// } +// } diff --git a/packages/base/src/fcplugin/plugins.ts b/packages/base/src/fcplugin/plugins.ts index 2f40e5fd..961ecc18 100644 --- a/packages/base/src/fcplugin/plugins.ts +++ b/packages/base/src/fcplugin/plugins.ts @@ -1,177 +1,177 @@ -import { - ICollaborativeDrive, - SharedDocumentFactory -} from '@jupyter/docprovider'; -import { IAnnotationModel, JupyterCadDoc } from '@jupytercad/schema'; -import { - JupyterFrontEnd, - JupyterFrontEndPlugin -} from '@jupyterlab/application'; -import { - ICommandPalette, - IThemeManager, - showErrorMessage, - WidgetTracker -} from '@jupyterlab/apputils'; -import { IFileBrowserFactory } from '@jupyterlab/filebrowser'; -import { ILauncher } from '@jupyterlab/launcher'; -import { fileIcon } from '@jupyterlab/ui-components'; - -import { JupyterCadWidgetFactory } from '../factory'; -import { IAnnotationToken, IJupyterCadDocTracker } from '../token'; -import { requestAPI } from '../tools'; -import { IJupyterCadWidget } from '../types'; -import { JupyterCadFCModelFactory } from './modelfactory'; - -const FACTORY = 'Jupytercad Freecad Factory'; - -// const PALETTE_CATEGORY = 'JupyterCAD'; - -namespace CommandIDs { - export const createNew = 'jupytercad:create-new-FCStd-file'; -} - -const activate = async ( - app: JupyterFrontEnd, - tracker: WidgetTracker, - themeManager: IThemeManager, - annotationModel: IAnnotationModel, - browserFactory: IFileBrowserFactory, - drive: ICollaborativeDrive, - launcher: ILauncher | null, - palette: ICommandPalette | null -): Promise => { - const fcCheck = await requestAPI<{ installed: boolean }>( - 'cad/backend-check', - { - method: 'POST', - body: JSON.stringify({ - backend: 'FreeCAD' - }) - } - ); - const { installed } = fcCheck; - const backendCheck = () => { - if (!installed) { - showErrorMessage( - 'FreeCAD is not installed', - 'FreeCAD is required to open FCStd files' - ); - } - return installed; - }; - const widgetFactory = new JupyterCadWidgetFactory({ - name: FACTORY, - modelName: 'jupytercad-fcmodel', - fileTypes: ['FCStd'], - defaultFor: ['FCStd'], - tracker, - commands: app.commands, - backendCheck - }); - - // Registering the widget factory - app.docRegistry.addWidgetFactory(widgetFactory); - - // Creating and registering the model factory for our custom DocumentModel - const modelFactory = new JupyterCadFCModelFactory({ annotationModel }); - app.docRegistry.addModelFactory(modelFactory); - // register the filetype - app.docRegistry.addFileType({ - name: 'FCStd', - displayName: 'FCStd', - mimeTypes: ['application/octet-stream'], - extensions: ['.FCStd', 'fcstd'], - fileFormat: 'base64', - contentType: 'FCStd' - }); - - const FCStdSharedModelFactory: SharedDocumentFactory = () => { - return new JupyterCadDoc(); - }; - drive.sharedModelFactory.registerDocumentFactory( - 'FCStd', - FCStdSharedModelFactory - ); - - widgetFactory.widgetCreated.connect((sender, widget) => { - // Notify the instance tracker if restore data needs to update. - widget.context.pathChanged.connect(() => { - tracker.save(widget); - }); - themeManager.themeChanged.connect((_, changes) => - widget.context.model.themeChanged.emit(changes) - ); - - tracker.add(widget); - app.shell.activateById('jupytercad::leftControlPanel'); - app.shell.activateById('jupytercad::rightControlPanel'); - }); - - app.commands.addCommand(CommandIDs.createNew, { - label: args => (args['isPalette'] ? 'New FCStd Editor' : 'FCStd Editor'), - caption: 'Create a new FCStd Editor', - icon: args => (args['isPalette'] ? undefined : fileIcon), - execute: async args => { - // Get the directory in which the FCStd file must be created; - // otherwise take the current filebrowser directory - const cwd = (args['cwd'] || - browserFactory.tracker.currentWidget?.model.path) as string; - - // Create a new untitled Blockly file - let model = await app.serviceManager.contents.newUntitled({ - path: cwd, - type: 'file', - ext: '.FCStd' - }); - - console.debug('Model:', model); - model = await app.serviceManager.contents.save(model.path, { - ...model, - format: 'base64', - size: undefined, - content: btoa('') - }); - - // Open the newly created file with the 'Editor' - return app.commands.execute('docmanager:open', { - path: model.path, - factory: FACTORY - }); - } - }); - - // Add the command to the launcher - if (launcher) { - /* launcher.add({ - command: CommandIDs.createNew, - category: 'Other', - rank: 1 - }); */ - } - - // Add the command to the palette - if (palette) { - /* palette.addItem({ - command: CommandIDs.createNew, - args: { isPalette: true }, - category: PALETTE_CATEGORY - }); */ - } -}; - -const fcplugin: JupyterFrontEndPlugin = { - id: 'jupytercad:fcplugin', - requires: [ - IJupyterCadDocTracker, - IThemeManager, - IAnnotationToken, - IFileBrowserFactory, - ICollaborativeDrive - ], - optional: [ILauncher, ICommandPalette], - autoStart: true, - activate -}; - -export default fcplugin; +// import { +// ICollaborativeDrive, +// SharedDocumentFactory +// } from '@jupyter/docprovider'; +// import { IAnnotationModel, JupyterCadDoc } from '@jupytercad/schema'; +// import { +// JupyterFrontEnd, +// JupyterFrontEndPlugin +// } from '@jupyterlab/application'; +// import { +// ICommandPalette, +// IThemeManager, +// showErrorMessage, +// WidgetTracker +// } from '@jupyterlab/apputils'; +// import { IFileBrowserFactory } from '@jupyterlab/filebrowser'; +// import { ILauncher } from '@jupyterlab/launcher'; +// import { fileIcon } from '@jupyterlab/ui-components'; + +// import { JupyterCadWidgetFactory } from '../factory'; +// import { IAnnotationToken, IJupyterCadDocTracker } from '../token'; +// import { requestAPI } from '../tools'; +// import { IJupyterCadWidget } from '../types'; +// import { JupyterCadFCModelFactory } from './modelfactory'; + +// const FACTORY = 'Jupytercad Freecad Factory'; + +// // const PALETTE_CATEGORY = 'JupyterCAD'; + +// namespace CommandIDs { +// export const createNew = 'jupytercad:create-new-FCStd-file'; +// } + +// const activate = async ( +// app: JupyterFrontEnd, +// tracker: WidgetTracker, +// themeManager: IThemeManager, +// annotationModel: IAnnotationModel, +// browserFactory: IFileBrowserFactory, +// drive: ICollaborativeDrive, +// launcher: ILauncher | null, +// palette: ICommandPalette | null +// ): Promise => { +// const fcCheck = await requestAPI<{ installed: boolean }>( +// 'cad/backend-check', +// { +// method: 'POST', +// body: JSON.stringify({ +// backend: 'FreeCAD' +// }) +// } +// ); +// const { installed } = fcCheck; +// const backendCheck = () => { +// if (!installed) { +// showErrorMessage( +// 'FreeCAD is not installed', +// 'FreeCAD is required to open FCStd files' +// ); +// } +// return installed; +// }; +// const widgetFactory = new JupyterCadWidgetFactory({ +// name: FACTORY, +// modelName: 'jupytercad-fcmodel', +// fileTypes: ['FCStd'], +// defaultFor: ['FCStd'], +// tracker, +// commands: app.commands, +// backendCheck +// }); + +// // Registering the widget factory +// app.docRegistry.addWidgetFactory(widgetFactory); + +// // Creating and registering the model factory for our custom DocumentModel +// const modelFactory = new JupyterCadFCModelFactory({ annotationModel }); +// app.docRegistry.addModelFactory(modelFactory); +// // register the filetype +// app.docRegistry.addFileType({ +// name: 'FCStd', +// displayName: 'FCStd', +// mimeTypes: ['application/octet-stream'], +// extensions: ['.FCStd', 'fcstd'], +// fileFormat: 'base64', +// contentType: 'FCStd' +// }); + +// const FCStdSharedModelFactory: SharedDocumentFactory = () => { +// return new JupyterCadDoc(); +// }; +// drive.sharedModelFactory.registerDocumentFactory( +// 'FCStd', +// FCStdSharedModelFactory +// ); + +// widgetFactory.widgetCreated.connect((sender, widget) => { +// // Notify the instance tracker if restore data needs to update. +// widget.context.pathChanged.connect(() => { +// tracker.save(widget); +// }); +// themeManager.themeChanged.connect((_, changes) => +// widget.context.model.themeChanged.emit(changes) +// ); + +// tracker.add(widget); +// app.shell.activateById('jupytercad::leftControlPanel'); +// app.shell.activateById('jupytercad::rightControlPanel'); +// }); + +// app.commands.addCommand(CommandIDs.createNew, { +// label: args => (args['isPalette'] ? 'New FCStd Editor' : 'FCStd Editor'), +// caption: 'Create a new FCStd Editor', +// icon: args => (args['isPalette'] ? undefined : fileIcon), +// execute: async args => { +// // Get the directory in which the FCStd file must be created; +// // otherwise take the current filebrowser directory +// const cwd = (args['cwd'] || +// browserFactory.tracker.currentWidget?.model.path) as string; + +// // Create a new untitled Blockly file +// let model = await app.serviceManager.contents.newUntitled({ +// path: cwd, +// type: 'file', +// ext: '.FCStd' +// }); + +// console.debug('Model:', model); +// model = await app.serviceManager.contents.save(model.path, { +// ...model, +// format: 'base64', +// size: undefined, +// content: btoa('') +// }); + +// // Open the newly created file with the 'Editor' +// return app.commands.execute('docmanager:open', { +// path: model.path, +// factory: FACTORY +// }); +// } +// }); + +// // Add the command to the launcher +// if (launcher) { +// /* launcher.add({ +// command: CommandIDs.createNew, +// category: 'Other', +// rank: 1 +// }); */ +// } + +// // Add the command to the palette +// if (palette) { +// /* palette.addItem({ +// command: CommandIDs.createNew, +// args: { isPalette: true }, +// category: PALETTE_CATEGORY +// }); */ +// } +// }; + +// const fcplugin: JupyterFrontEndPlugin = { +// id: 'jupytercad:fcplugin', +// requires: [ +// IJupyterCadDocTracker, +// IThemeManager, +// IAnnotationToken, +// IFileBrowserFactory, +// ICollaborativeDrive +// ], +// optional: [ILauncher, ICommandPalette], +// autoStart: true, +// activate +// }; + +// export default fcplugin; diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index 08e84fac..ee0e4f63 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -26,7 +26,6 @@ // import { addCommands, CommandIDs } from './commands'; export * from './commands'; -export * from './factory'; export * from './formdialog'; export * from './mainview'; export * from './token'; diff --git a/packages/base/src/jcadplugin/modelfactory.ts b/packages/base/src/jcadplugin/modelfactory.ts deleted file mode 100644 index bc88c90c..00000000 --- a/packages/base/src/jcadplugin/modelfactory.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { DocumentRegistry } from '@jupyterlab/docregistry'; -import { Contents } from '@jupyterlab/services'; - -import { - IAnnotationModel, - IJupyterCadDoc, - JupyterCadModel -} from '@jupytercad/schema'; - -/** - * A Model factory to create new instances of JupyterCadModel. - */ -export class JupyterCadJcadModelFactory - implements DocumentRegistry.IModelFactory -{ - constructor(options: JupyterCadJcadModelFactory.IOptions) { - this._annotationModel = options.annotationModel; - } - - /** - * Whether the model is collaborative or not. - */ - readonly collaborative = true; - - /** - * The name of the model. - * - * @returns The name - */ - get name(): string { - return 'jupytercad-jcadmodel'; - } - - /** - * The content type of the file. - * - * @returns The content type - */ - get contentType(): Contents.ContentType { - return 'jcad'; - } - - /** - * The format of the file. - * - * @returns the file format - */ - get fileFormat(): Contents.FileFormat { - return 'text'; - } - - /** - * Get whether the model factory has been disposed. - * - * @returns disposed status - */ - get isDisposed(): boolean { - return this._disposed; - } - - /** - * Dispose the model factory. - */ - dispose(): void { - this._disposed = true; - } - - /** - * Get the preferred language given the path on the file. - * - * @param path path of the file represented by this document model - * @returns The preferred language - */ - preferredLanguage(path: string): string { - return ''; - } - - /** - * Create a new instance of JupyterCadModel. - * - * @returns The model - */ - createNew( - options: DocumentRegistry.IModelOptions - ): JupyterCadModel { - const model = new JupyterCadModel({ - sharedModel: options.sharedModel, - languagePreference: options.languagePreference, - annotationModel: this._annotationModel - }); - return model; - } - - private _annotationModel: IAnnotationModel; - private _disposed = false; -} - -export namespace JupyterCadJcadModelFactory { - export interface IOptions { - annotationModel: IAnnotationModel; - } -} diff --git a/packages/base/src/jcadplugin/plugins.ts b/packages/base/src/jcadplugin/plugins.ts deleted file mode 100644 index e75b1c4e..00000000 --- a/packages/base/src/jcadplugin/plugins.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - ICollaborativeDrive, - SharedDocumentFactory -} from '@jupyter/docprovider'; -import { IAnnotationModel, JupyterCadDoc } from '@jupytercad/schema'; -import { - JupyterFrontEnd, - JupyterFrontEndPlugin -} from '@jupyterlab/application'; -import { - ICommandPalette, - IThemeManager, - WidgetTracker -} from '@jupyterlab/apputils'; -import { IFileBrowserFactory } from '@jupyterlab/filebrowser'; -import { ILauncher } from '@jupyterlab/launcher'; -import { fileIcon } from '@jupyterlab/ui-components'; - -import { JupyterCadWidgetFactory } from '../factory'; -import { IAnnotationToken, IJupyterCadDocTracker } from '../token'; -import { IJupyterCadWidget } from '../types'; -import { JupyterCadJcadModelFactory } from './modelfactory'; - -const FACTORY = 'Jupytercad Jcad Factory'; -const PALETTE_CATEGORY = 'JupyterCAD'; - -namespace CommandIDs { - export const createNew = 'jupytercad:create-new-jcad-file'; -} - -const activate = ( - app: JupyterFrontEnd, - tracker: WidgetTracker, - themeManager: IThemeManager, - annotationModel: IAnnotationModel, - browserFactory: IFileBrowserFactory, - drive: ICollaborativeDrive, - launcher: ILauncher | null, - palette: ICommandPalette | null -): void => { - const widgetFactory = new JupyterCadWidgetFactory({ - name: FACTORY, - modelName: 'jupytercad-jcadmodel', - fileTypes: ['jcad'], - defaultFor: ['jcad'], - tracker, - commands: app.commands - }); - - // Registering the widget factory - app.docRegistry.addWidgetFactory(widgetFactory); - - // Creating and registering the model factory for our custom DocumentModel - const modelFactory = new JupyterCadJcadModelFactory({ annotationModel }); - app.docRegistry.addModelFactory(modelFactory); - // register the filetype - app.docRegistry.addFileType({ - name: 'jcad', - displayName: 'JCAD', - mimeTypes: ['text/json'], - extensions: ['.jcad', '.JCAD'], - fileFormat: 'text', - contentType: 'jcad' - }); - - const jcadSharedModelFactory: SharedDocumentFactory = () => { - return new JupyterCadDoc(); - }; - drive.sharedModelFactory.registerDocumentFactory( - 'jcad', - jcadSharedModelFactory - ); - - widgetFactory.widgetCreated.connect((sender, widget) => { - widget.context.pathChanged.connect(() => { - tracker.save(widget); - }); - themeManager.themeChanged.connect((_, changes) => - widget.context.model.themeChanged.emit(changes) - ); - tracker.add(widget); - app.shell.activateById('jupytercad::leftControlPanel'); - app.shell.activateById('jupytercad::rightControlPanel'); - }); - - app.commands.addCommand(CommandIDs.createNew, { - label: args => 'New JCAD File', - caption: 'Create a new JCAD Editor', - icon: args => (args['isPalette'] ? undefined : fileIcon), - execute: async args => { - // Get the directory in which the JCAD file must be created; - // otherwise take the current filebrowser directory - const cwd = (args['cwd'] || - browserFactory.tracker.currentWidget?.model.path) as string; - - // Create a new untitled Blockly file - let model = await app.serviceManager.contents.newUntitled({ - path: cwd, - type: 'file', - ext: '.jcad' - }); - - console.debug('Model:', model); - model = await app.serviceManager.contents.save(model.path, { - ...model, - format: 'text', - size: undefined, - content: '{\n\t"objects": [],\n\t"options": {},\n\t"metadata": {}\n}' - }); - - // Open the newly created file with the 'Editor' - return app.commands.execute('docmanager:open', { - path: model.path, - factory: FACTORY - }); - } - }); - - // Add the command to the launcher - if (launcher) { - launcher.add({ - command: CommandIDs.createNew, - category: 'Other', - rank: 1 - }); - } - - // Add the command to the palette - if (palette) { - palette.addItem({ - command: CommandIDs.createNew, - args: { isPalette: true }, - category: PALETTE_CATEGORY - }); - } -}; - -const jcadPlugin: JupyterFrontEndPlugin = { - id: 'jupytercad:jcadplugin', - requires: [ - IJupyterCadDocTracker, - IThemeManager, - IAnnotationToken, - IFileBrowserFactory, - ICollaborativeDrive - ], - optional: [ILauncher, ICommandPalette], - autoStart: true, - activate -}; - -export default jcadPlugin; diff --git a/packages/base/src/mainview.tsx b/packages/base/src/mainview.tsx index 8165b50a..7ed13354 100644 --- a/packages/base/src/mainview.tsx +++ b/packages/base/src/mainview.tsx @@ -4,6 +4,7 @@ import { IMainMessage, IWorkerMessage, MainAction, + OCC_WORKER_ID, WorkerAction } from '@jupytercad/occ-worker'; import { @@ -33,6 +34,7 @@ import { v4 as uuid } from 'uuid'; import { FloatingAnnotation } from './annotation/view'; import { getCSSVariableColor, throttle } from './tools'; import { AxeHelper, CameraSettings, ExplodedView } from './types'; +import { IJCadWorker, IJCadWorkerRegistry } from './token'; // Apply the BVH extension THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; @@ -61,6 +63,7 @@ export type BasicMesh = THREE.Mesh< interface IProps { view: ObservableMap; jcadModel: IJupyterCadModel; + workerRegistry?: IJCadWorkerRegistry; } interface IStates { @@ -92,7 +95,6 @@ interface IPickedResult { export class MainView extends React.Component { constructor(props: IProps) { super(props); - this._geometry = new THREE.BufferGeometry(); this._geometry.setDrawRange(0, 3 * 10000); @@ -110,7 +112,8 @@ export class MainView extends React.Component { }; this._model = this.props.jcadModel; - this._worker = this._model.getWorker(); + this._worker = props.workerRegistry?.getWorker(OCC_WORKER_ID); + console.log('worker is ', this._worker); this._pointer = new THREE.Vector2(); this._collaboratorPointers = {}; @@ -394,6 +397,8 @@ export class MainView extends React.Component { break; } case MainAction.INITIALIZED: { + console.log('im here11'); + if (!this._model) { return; } @@ -1209,8 +1214,7 @@ export class MainView extends React.Component { private divRef = React.createRef(); // Reference of render div private _model: IJupyterCadModel; - private _worker?: Worker = undefined; - private _messageChannel?: MessageChannel; + private _worker?: IJCadWorker = undefined; private _pointer: THREE.Vector2; private _syncPointer: ( diff --git a/packages/base/src/token.ts b/packages/base/src/token.ts index 8876c1c7..b7a782d6 100644 --- a/packages/base/src/token.ts +++ b/packages/base/src/token.ts @@ -13,3 +13,48 @@ export const IJupyterCadDocTracker = new Token( export const IAnnotationToken = new Token( 'jupytercadAnnotationModel' ); + +export interface IJCadWorker { + ready: Promise; + initialize(): string; + registerHandler( + messageHandler: ((msg: any) => void) | ((msg: any) => Promise), + thisArg?: any + ): void; + postMessage(msg: any): void; +} +export interface IJCadWorkerRegistry { + /** + * + * + * @param {string} workerId + * @param {IJCadWorker} worker + */ + registerWorker(workerId: string, worker: IJCadWorker): void; + + /** + * + * + * @param {string} workerId + */ + unregisterWorker(workerId: string): void; + + /** + * + * + * @param {string} workerId + * @return {*} {(IJCadWorker | undefined)} + */ + getWorker(workerId: string): IJCadWorker | undefined; + + /** + * + * + * @return {*} {IJCadWorker[]} + */ + getAllWorkers(): IJCadWorker[]; +} + +export const IJCadWorkerRegistry = new Token( + 'jupytercadWorkerRegistry' +); diff --git a/packages/base/src/widget.tsx b/packages/base/src/widget.tsx index e61ddf31..9327f520 100644 --- a/packages/base/src/widget.tsx +++ b/packages/base/src/widget.tsx @@ -13,6 +13,7 @@ import { ExplodedView, IJupyterCadWidget } from './types'; +import { IJCadWorkerRegistry } from './token'; export class JupyterCadWidget extends DocumentWidget @@ -43,11 +44,14 @@ export class JupyterCadPanel extends ReactWidget { * * @param context - The documents context. */ - constructor(options: { model: IJupyterCadModel }) { + constructor(options: { + model: IJupyterCadModel; + workerRegistry?: IJCadWorkerRegistry; + }) { super(); this.addClass('jp-jupytercad-panel'); this._jcadModel = options.model; - + this._workerRegistry = options.workerRegistry; this._view = new ObservableMap(); } @@ -98,9 +102,16 @@ export class JupyterCadPanel extends ReactWidget { } render(): JSX.Element { - return ; + return ( + + ); } private _view: ObservableMap; + private _workerRegistry: IJCadWorkerRegistry | undefined; private _jcadModel: IJupyterCadModel; } diff --git a/packages/occ-worker/src/index.ts b/packages/occ-worker/src/index.ts index f1d693d8..433998d0 100644 --- a/packages/occ-worker/src/index.ts +++ b/packages/occ-worker/src/index.ts @@ -1,2 +1,3 @@ export * from './types'; export * from './occparser'; +export * from './occworker'; diff --git a/packages/occ-worker/src/occworker.ts b/packages/occ-worker/src/occworker.ts new file mode 100644 index 00000000..de010042 --- /dev/null +++ b/packages/occ-worker/src/occworker.ts @@ -0,0 +1,63 @@ +import { IJCadWorker } from '@jupytercad/schema'; +import { PromiseDelegate } from '@lumino/coreutils'; +import { v4 as uuid } from 'uuid'; + +import { MainAction, WorkerAction } from './types'; + +export const OCC_WORKER_ID = 'jupytercadOccWorker'; + +export class OccWorker implements IJCadWorker { + constructor(options: OccWorker.IOptions) { + this._nativeWorker = options.worker; + } + + get ready(): Promise { + return this._ready.promise; + } + + initialize(): string { + const messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = this._processMessage.bind(this); + const id = uuid(); + const initMessage = { + action: WorkerAction.REGISTER, + payload: { id } + }; + this._nativeWorker.postMessage(initMessage, [messageChannel.port2]); + return id; + } + + registerHandler( + messageHandler: ((msg: any) => void) | ((msg: any) => Promise), + thisArg?: any + ): void { + if (!this._handlerSet.has(messageHandler)) { + if (thisArg) { + messageHandler.bind(thisArg); + } + this._handlerSet.add(messageHandler); + } + } + + postMessage(msg: { id: string; [key: string]: any }): void { + this._nativeWorker.postMessage(msg); + } + + private _processMessage(msg: any): void { + if (msg.action === MainAction.INITIALIZED) { + this._ready.resolve(); + } else { + this._handlerSet.forEach(cb => cb(msg)); + } + } + + private _ready = new PromiseDelegate(); + private _handlerSet = new Set(); + private _nativeWorker: Worker; +} + +export namespace OccWorker { + export interface IOptions { + worker: Worker; + } +} diff --git a/packages/schema/package.json b/packages/schema/package.json index e6df0adf..8fb218e7 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -32,8 +32,7 @@ "clean": "rimraf tsconfig.tsbuildinfo", "clean:lib": "rimraf lib tsconfig.tsbuildinfo", "clean:all": "jlpm run clean:lib", - "watch": "run-p watch:worker watch:src", - "watch:src": "tsc -w" + "watch": "tsc -w" }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.9", diff --git a/python/jupytercad-core/jupytercad_core/jcad_ydoc.py b/python/jupytercad-core/jupytercad_core/jcad_ydoc.py new file mode 100644 index 00000000..bf93dc71 --- /dev/null +++ b/python/jupytercad-core/jupytercad_core/jcad_ydoc.py @@ -0,0 +1,69 @@ +import json +from typing import Any, Callable +from functools import partial + +import y_py as Y +from jupyter_ydoc.ybasedoc import YBaseDoc + + +class YJCad(YBaseDoc): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._ysource = self._ydoc.get_text("source") + self._yobjects = self._ydoc.get_array("objects") + self._yoptions = self._ydoc.get_map("options") + self._ymeta = self._ydoc.get_map("metadata") + + def version(self) -> str: + return "0.1.0" + + def get(self) -> str: + """ + Returns the content of the document. + :return: Document's content. + :rtype: Any + """ + objects = json.loads(self._yobjects.to_json()) + options = json.loads(self._yoptions.to_json()) + meta = json.loads(self._ymeta.to_json()) + return json.dumps( + dict(objects=objects, options=options, metadata=meta), indent=2 + ) + + def set(self, value: str) -> None: + """ + Sets the content of the document. + :param value: The content of the document. + :type value: Any + """ + valueDict = json.loads(value) + newObj = [] + for obj in valueDict["objects"]: + newObj.append(Y.YMap(obj)) + with self._ydoc.begin_transaction() as t: + length = len(self._yobjects) + self._yobjects.delete_range(t, 0, length) + # workaround for https://github.com/y-crdt/ypy/issues/126: + # self._yobjects.extend(t, newObj) + for o in newObj: + self._yobjects.append(t, o) + self._yoptions.update(t, valueDict["options"].items()) + self._ymeta.update(t, valueDict["metadata"].items()) + + def observe(self, callback: Callable[[str, Any], None]): + self.unobserve() + self._subscriptions[self._ystate] = self._ystate.observe( + partial(callback, "state") + ) + self._subscriptions[self._ysource] = self._ysource.observe( + partial(callback, "source") + ) + self._subscriptions[self._yobjects] = self._yobjects.observe_deep( + partial(callback, "objects") + ) + self._subscriptions[self._yoptions] = self._yoptions.observe_deep( + partial(callback, "options") + ) + self._subscriptions[self._ymeta] = self._ymeta.observe_deep( + partial(callback, "meta") + ) diff --git a/python/jupytercad-core/pyproject.toml b/python/jupytercad-core/pyproject.toml index 0f9feb90..6102fc44 100644 --- a/python/jupytercad-core/pyproject.toml +++ b/python/jupytercad-core/pyproject.toml @@ -25,10 +25,14 @@ classifiers = [ dependencies = [ "jupyter_server>=2.0.6,<3", "jupyter_ydoc>=1.1.0,<2", - "y-py>=0.6.0,<0.7" + "y-py>=0.6.0,<0.7", + "jupyter-collaboration>=1.2.0,<2" ] dynamic = ["version", "description", "authors", "urls", "keywords"] +[project.entry-points.jupyter_ydoc] +jcad = "jupytercad_core.jcad_ydoc:YJCad" + [tool.hatch.version] source = "nodejs" diff --git a/packages/base/src/factory.ts b/python/jupytercad-core/src/factory.ts similarity index 71% rename from packages/base/src/factory.ts rename to python/jupytercad-core/src/factory.ts index 91449491..cf9537d2 100644 --- a/packages/base/src/factory.ts +++ b/python/jupytercad-core/src/factory.ts @@ -2,13 +2,18 @@ import { JupyterCadModel } from '@jupytercad/schema'; import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry'; import { CommandRegistry } from '@lumino/commands'; -import { IJupyterCadTracker } from './token'; -import { ToolbarWidget } from './toolbar/widget'; -import { JupyterCadPanel, JupyterCadWidget } from './widget'; +import { + JupyterCadPanel, + JupyterCadWidget, + IJupyterCadTracker, + ToolbarWidget, + IJCadWorkerRegistry +} from '@jupytercad/base'; -interface IOptios extends DocumentRegistry.IWidgetFactoryOptions { +interface IOptions extends DocumentRegistry.IWidgetFactoryOptions { tracker: IJupyterCadTracker; commands: CommandRegistry; + workerRegistry: IJCadWorkerRegistry; backendCheck?: () => boolean; } @@ -16,11 +21,12 @@ export class JupyterCadWidgetFactory extends ABCWidgetFactory< JupyterCadWidget, JupyterCadModel > { - constructor(options: IOptios) { + constructor(options: IOptions) { const { backendCheck, ...rest } = options; super(rest); this._backendCheck = backendCheck; this._commands = options.commands; + this._workerRegistry = options.workerRegistry; } /** @@ -39,7 +45,10 @@ export class JupyterCadWidgetFactory extends ABCWidgetFactory< } } const { model } = context; - const content = new JupyterCadPanel({ model }); + const content = new JupyterCadPanel({ + model, + workerRegistry: this._workerRegistry + }); const toolbar = new ToolbarWidget({ commands: this._commands, model @@ -48,5 +57,6 @@ export class JupyterCadWidgetFactory extends ABCWidgetFactory< } private _commands: CommandRegistry; + private _workerRegistry: IJCadWorkerRegistry; private _backendCheck?: () => boolean; } diff --git a/python/jupytercad-core/src/index.ts b/python/jupytercad-core/src/index.ts index f7d9ee77..3ddf6194 100644 --- a/python/jupytercad-core/src/index.ts +++ b/python/jupytercad-core/src/index.ts @@ -1,80 +1,9 @@ import { - AnnotationModel, - IAnnotationToken, - IJupyterCadDocTracker, - IJupyterCadTracker, - JupyterCadWidget -} from '@jupytercad/base'; -import { IAnnotationModel, JupyterCadModel } from '@jupytercad/schema'; -import { - JupyterFrontEnd, - JupyterFrontEndPlugin -} from '@jupyterlab/application'; -import { WidgetTracker } from '@jupyterlab/apputils'; -import { IMainMenu } from '@jupyterlab/mainmenu'; -import { ITranslator } from '@jupyterlab/translation'; - -const NAME_SPACE = 'jupytercad'; - -const plugin: JupyterFrontEndPlugin = { - id: 'jupytercad:core:tracker', - autoStart: true, - requires: [ITranslator], - optional: [IMainMenu], - provides: IJupyterCadDocTracker, - activate: ( - app: JupyterFrontEnd, - translator: ITranslator, - mainMenu?: IMainMenu - ): IJupyterCadTracker => { - const tracker = new WidgetTracker({ - namespace: NAME_SPACE - }); - // TODO create worker registry - JupyterCadModel.worker = new Worker( - new URL('@jupytercad/occ-worker/lib/worker', (import.meta as any).url) - ); - - console.log('jupytercad:core:tracker is activated!'); - - /** - * @TODO Move commands to jupytercad-lab package - */ - - /** - * Whether there is an active notebook. - */ - // const isEnabled = (): boolean => { - // return ( - // tracker.currentWidget !== null && - // tracker.currentWidget === app.shell.currentWidget - // ); - // }; - - // addCommands(app, tracker, translator); - // if (mainMenu) { - // populateMenus(mainMenu, isEnabled); - // } - - return tracker; - } -}; - -const annotationPlugin: JupyterFrontEndPlugin = { - id: 'jupytercad:annotation', - autoStart: true, - requires: [IJupyterCadDocTracker], - provides: IAnnotationToken, - activate: (app: JupyterFrontEnd, tracker: IJupyterCadTracker) => { - const annotationModel = new AnnotationModel({ - context: tracker.currentWidget?.context - }); - - tracker.currentChanged.connect((_, changed) => { - annotationModel.context = changed?.context || undefined; - }); - return annotationModel; - } -}; - -export default [plugin, annotationPlugin]; + annotationPlugin, + trackerPlugin, + workerRegistryPlugin +} from './plugin'; + +export * from './workerregistry'; +export * from './factory'; +export default [trackerPlugin, annotationPlugin, workerRegistryPlugin]; diff --git a/python/jupytercad-core/src/plugin.ts b/python/jupytercad-core/src/plugin.ts new file mode 100644 index 00000000..1196f643 --- /dev/null +++ b/python/jupytercad-core/src/plugin.ts @@ -0,0 +1,74 @@ +import { + AnnotationModel, + IAnnotationToken, + IJupyterCadDocTracker, + IJupyterCadTracker, + JupyterCadWidget +} from '@jupytercad/base'; +import { OCC_WORKER_ID, OccWorker } from '@jupytercad/occ-worker'; +import { IAnnotationModel } from '@jupytercad/schema'; +import { IJCadWorkerRegistry } from '@jupytercad/base'; +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; +import { WidgetTracker } from '@jupyterlab/apputils'; +import { IMainMenu } from '@jupyterlab/mainmenu'; +import { ITranslator } from '@jupyterlab/translation'; + +import { JupyterCadWorkerRegistry } from './workerregistry'; + +const NAME_SPACE = 'jupytercad'; + +export const trackerPlugin: JupyterFrontEndPlugin = { + id: 'jupytercad:core:tracker', + autoStart: true, + requires: [ITranslator], + optional: [IMainMenu], + provides: IJupyterCadDocTracker, + activate: ( + app: JupyterFrontEnd, + translator: ITranslator, + mainMenu?: IMainMenu + ): IJupyterCadTracker => { + const tracker = new WidgetTracker({ + namespace: NAME_SPACE + }); + console.log('jupytercad:core:tracker is activated!'); + return tracker; + } +}; + +export const annotationPlugin: JupyterFrontEndPlugin = { + id: 'jupytercad:core:annotation', + autoStart: true, + requires: [IJupyterCadDocTracker], + provides: IAnnotationToken, + activate: (app: JupyterFrontEnd, tracker: IJupyterCadTracker) => { + const annotationModel = new AnnotationModel({ + context: tracker.currentWidget?.context + }); + + tracker.currentChanged.connect((_, changed) => { + annotationModel.context = changed?.context || undefined; + }); + return annotationModel; + } +}; + +export const workerRegistryPlugin: JupyterFrontEndPlugin = + { + id: 'jupytercad:core:worker-registry', + autoStart: true, + requires: [], + provides: IJCadWorkerRegistry, + activate: (app: JupyterFrontEnd): IJCadWorkerRegistry => { + const workerRegistry = new JupyterCadWorkerRegistry(); + const worker = new Worker( + new URL('@jupytercad/occ-worker/lib/worker', (import.meta as any).url) + ); + const occWorker = new OccWorker({ worker }); + workerRegistry.registerWorker(OCC_WORKER_ID, occWorker); + return workerRegistry; + } + }; diff --git a/python/jupytercad-core/src/workerregistry.ts b/python/jupytercad-core/src/workerregistry.ts new file mode 100644 index 00000000..847b8550 --- /dev/null +++ b/python/jupytercad-core/src/workerregistry.ts @@ -0,0 +1,30 @@ +import { IJCadWorker, IJCadWorkerRegistry } from '@jupytercad/base'; + +export class JupyterCadWorkerRegistry implements IJCadWorkerRegistry { + constructor() { + this._registry = new Map(); + } + registerWorker(workerId: string, worker: IJCadWorker): void { + if (!this._registry.has(workerId)) { + this._registry.set(workerId, worker); + } else { + console.error('Worker is already registered'); + } + } + + unregisterWorker(workerId: string): void { + if (!this._registry.has(workerId)) { + this._registry.delete(workerId); + } + } + + getWorker(workerId: string): IJCadWorker | undefined { + return this._registry.get(workerId); + } + + getAllWorkers(): IJCadWorker[] { + return [...this._registry.values()]; + } + + private _registry: Map; +} diff --git a/python/jupytercad-lab/package.json b/python/jupytercad-lab/package.json index 5fe7980a..202a49b2 100644 --- a/python/jupytercad-lab/package.json +++ b/python/jupytercad-lab/package.json @@ -55,6 +55,8 @@ "@jupyter/collaboration": "^1.0.0", "@jupyter/docprovider": "^1.0.0", "@jupyter/ydoc": "^0.3.4 || ^1.0.2", + "@jupytercad/base": "^0.3.3", + "@jupytercad/jupytercad-core": "^0.3.3", "@jupytercad/occ-worker": "^0.3.3", "@jupytercad/opencascade": "^0.3.3", "@jupytercad/schema": "^0.3.3", @@ -137,6 +139,10 @@ "@jupytercad/base": { "singleton": true, "bundled": false + }, + "@jupytercad/jupytercad-core": { + "singleton": true, + "bundled": false } } } diff --git a/python/jupytercad-lab/src/jcadplugin/plugins.ts b/python/jupytercad-lab/src/jcadplugin/plugins.ts index cf37a69e..c8c78839 100644 --- a/python/jupytercad-lab/src/jcadplugin/plugins.ts +++ b/python/jupytercad-lab/src/jcadplugin/plugins.ts @@ -4,10 +4,11 @@ import { } from '@jupyter/docprovider'; import { IAnnotationToken, + IJCadWorkerRegistry, IJupyterCadDocTracker, - IJupyterCadWidget, - JupyterCadWidgetFactory + IJupyterCadWidget } from '@jupytercad/base'; +import { JupyterCadWidgetFactory } from '@jupytercad/jupytercad-core'; import { IAnnotationModel, JupyterCadDoc } from '@jupytercad/schema'; import { JupyterFrontEnd, @@ -38,6 +39,7 @@ const activate = ( annotationModel: IAnnotationModel, browserFactory: IFileBrowserFactory, drive: ICollaborativeDrive, + workerRegistry: IJCadWorkerRegistry, launcher: ILauncher | null, palette: ICommandPalette | null ): void => { @@ -47,14 +49,17 @@ const activate = ( fileTypes: ['jcad'], defaultFor: ['jcad'], tracker, - commands: app.commands + commands: app.commands, + workerRegistry }); // Registering the widget factory app.docRegistry.addWidgetFactory(widgetFactory); // Creating and registering the model factory for our custom DocumentModel - const modelFactory = new JupyterCadJcadModelFactory({ annotationModel }); + const modelFactory = new JupyterCadJcadModelFactory({ + annotationModel + }); app.docRegistry.addModelFactory(modelFactory); // register the filetype app.docRegistry.addFileType({ @@ -103,7 +108,6 @@ const activate = ( ext: '.jcad' }); - console.debug('Model:', model); model = await app.serviceManager.contents.save(model.path, { ...model, format: 'text', @@ -145,7 +149,8 @@ const jcadPlugin: JupyterFrontEndPlugin = { IThemeManager, IAnnotationToken, IFileBrowserFactory, - ICollaborativeDrive + ICollaborativeDrive, + IJCadWorkerRegistry ], optional: [ILauncher, ICommandPalette], autoStart: true, diff --git a/yarn.lock b/yarn.lock index d074009e..103e4cfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1001,7 +1001,7 @@ __metadata: languageName: unknown linkType: soft -"@jupytercad/jupytercad-core@workspace:python/jupytercad-core": +"@jupytercad/jupytercad-core@^0.3.3, @jupytercad/jupytercad-core@workspace:python/jupytercad-core": version: 0.0.0-use.local resolution: "@jupytercad/jupytercad-core@workspace:python/jupytercad-core" dependencies: @@ -1034,6 +1034,8 @@ __metadata: "@jupyter/collaboration": ^1.0.0 "@jupyter/docprovider": ^1.0.0 "@jupyter/ydoc": ^0.3.4 || ^1.0.2 + "@jupytercad/base": ^0.3.3 + "@jupytercad/jupytercad-core": ^0.3.3 "@jupytercad/occ-worker": ^0.3.3 "@jupytercad/opencascade": ^0.3.3 "@jupytercad/schema": ^0.3.3