diff --git a/packages/base/src/3dview/mainviewmodel.ts b/packages/base/src/3dview/mainviewmodel.ts index 8e162056..4de6db77 100644 --- a/packages/base/src/3dview/mainviewmodel.ts +++ b/packages/base/src/3dview/mainviewmodel.ts @@ -2,7 +2,6 @@ import { IWorkerMessage } from '@jupytercad/occ-worker'; import { IAnnotation, IDict, - IDisplayShape, IJcadObjectDocChange, IJCadWorker, IJCadWorkerRegistry, @@ -106,8 +105,6 @@ export class MainViewModel implements IDisposable { } }); - this._saveMeta(result); - if (this._firstRender) { const postShapes = this._jcadModel.sharedModel .outputs as any as IDict; @@ -231,15 +228,6 @@ export class MainViewModel implements IDisposable { } } - private _saveMeta(payload: IDisplayShape['payload']['result']) { - if (!this._jcadModel) { - return; - } - Object.entries(payload).forEach(([objName, data]) => { - this._jcadModel.sharedModel.setShapeMeta(objName, data.meta); - }); - } - private async _onSharedObjectsChanged( _: IJupyterCadDoc, change: IJcadObjectDocChange diff --git a/packages/base/src/commands.ts b/packages/base/src/commands.ts index a20109fc..b221d2d5 100644 --- a/packages/base/src/commands.ts +++ b/packages/base/src/commands.ts @@ -217,6 +217,11 @@ export async function executeOperator( } // Everything's good, we can apply the change to the shared model + + const objMeta = dryRunResult.shapeMetadata?.[objectModel.name]; + if (objMeta) { + objectModel.shapeMetadata = objMeta; + } sharedModel.transact(() => { transaction(sharedModel); }); @@ -1046,7 +1051,7 @@ namespace Private { title: value.title, sourceData: value.default(current.context.model), schema: FORM_SCHEMA[value.shape], - syncData: (props: IDict) => { + syncData: async (props: IDict) => { const { Name, ...parameters } = props; const objectModel: IJCadObject = { shape: value.shape as Parts, @@ -1056,8 +1061,30 @@ namespace Private { }; const sharedModel = current.context.model.sharedModel; + if (sharedModel) { if (!sharedModel.objectExists(objectModel.name)) { + // Try a dry run with the update content to verify its feasibility + const currentJcadContent = current.context.model.getContent(); + const updatedContent: IJCadContent = { + ...currentJcadContent, + objects: [...currentJcadContent.objects, objectModel] + }; + const dryRunResult = + await current.content.currentViewModel.dryRun(updatedContent); + if (dryRunResult.status === 'error') { + showErrorMessage( + `Failed to create the ${value.shape} shape`, + `The ${value.shape} tool was unable to create the desired shape due to invalid parameter values. The values you entered may not be compatible with the dimensions of your piece.` + ); + + return; + } + + const objMeta = dryRunResult.shapeMetadata?.[objectModel.name]; + if (objMeta) { + objectModel.shapeMetadata = objMeta; + } sharedModel.addObject(objectModel); } else { showErrorMessage( diff --git a/packages/base/src/panelview/objectproperties.tsx b/packages/base/src/panelview/objectproperties.tsx index 6d92791b..33441dd2 100644 --- a/packages/base/src/panelview/objectproperties.tsx +++ b/packages/base/src/panelview/objectproperties.tsx @@ -143,11 +143,18 @@ class ObjectPropertiesReact extends React.Component { } // Dry run was successful, ready to apply the update now + const meta: IDict = dryRunResult.shapeMetadata?.[objectName] ?? {}; const obj = model.sharedModel.getObjectByName(objectName); if (obj) { - model.sharedModel.updateObjectByName(objectName, 'parameters', { - ...obj['parameters'], - ...properties + model.sharedModel.updateObjectByName(objectName, { + data: { + key: 'parameters', + value: { + ...obj['parameters'], + ...properties + } + }, + meta }); } } diff --git a/packages/occ-worker/src/worker.ts b/packages/occ-worker/src/worker.ts index 59234474..66d8669c 100644 --- a/packages/occ-worker/src/worker.ts +++ b/packages/occ-worker/src/worker.ts @@ -56,8 +56,9 @@ self.onmessage = async (event: MessageEvent): Promise => { break; } case WorkerAction.DRY_RUN: { + let response: IDict = {}; try { - WorkerHandler[WorkerAction.DRY_RUN](message.payload); + response = WorkerHandler[WorkerAction.DRY_RUN](message.payload); } catch (e) { let msg = ''; @@ -80,13 +81,17 @@ self.onmessage = async (event: MessageEvent): Promise => { ); return; } - + const shapeMetadata: IDict = {}; + Object.entries(response.result ?? {}).forEach(([objName, data]) => { + shapeMetadata[objName] = (data as any)?.['meta']; + }); sendToMain( { action: MainAction.DRY_RUN_RESPONSE, payload: { id: message.payload.id, - status: 'ok' + status: 'ok', + shapeMetadata } }, id diff --git a/packages/schema/src/doc.ts b/packages/schema/src/doc.ts index 199f15a8..d79dc701 100644 --- a/packages/schema/src/doc.ts +++ b/packages/schema/src/doc.ts @@ -158,15 +158,18 @@ export class JupyterCadDoc }); } - updateObjectByName(name: string, key: string, value: any): void { + updateObjectByName( + name: string, + payload: { data: { key: string; value: any }; meta?: IDict } + ): void { const obj = this._getObjectAsYMapByName(name); if (!obj) { return; } + const { key, value } = payload.data; this.transact(() => { // Special case for changing parameters, we may need to update dependencies - console.log('update ', key); if (key === 'parameters') { switch (obj.get('shape')) { case 'Part::Cut': { @@ -190,6 +193,9 @@ export class JupyterCadDoc } obj.set(key, value); + if (payload.meta) { + obj.set('shapeMetadata', payload.meta); + } }); } diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index 284b8026..4223a416 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -95,7 +95,10 @@ export interface IJupyterCadDoc extends YDocument { removeObjectByName(name: string): void; addObject(value: IJCadObject): void; addObjects(value: Array): void; - updateObjectByName(name: string, key: string, value: any): void; + updateObjectByName( + name: string, + payload: { data: { key: string; value: any }; meta?: IDict } + ): void; getDependants(name: string): string[]; getOption(key: keyof IJCadOptions): IDict | undefined; @@ -258,6 +261,7 @@ export interface IDryRunResponsePayload { id: string; status: 'ok' | 'error'; message?: string; + shapeMetadata?: IDict; } export interface IDryRunResponse extends IMainMessageBase { diff --git a/python/jupytercad_core/jupytercad_core/jcad_ydoc.py b/python/jupytercad_core/jupytercad_core/jcad_ydoc.py index f5d68669..0e7836f8 100644 --- a/python/jupytercad_core/jupytercad_core/jcad_ydoc.py +++ b/python/jupytercad_core/jupytercad_core/jcad_ydoc.py @@ -31,6 +31,7 @@ def get(self) -> str: return json.dumps( dict(objects=objects, options=options, metadata=meta, outputs=outputs), indent=2, + sort_keys=True, ) def set(self, value: str) -> None: