Skip to content

Commit

Permalink
.jcad file export
Browse files Browse the repository at this point in the history
  • Loading branch information
martinRenou committed Feb 6, 2024
1 parent ca90510 commit 03c49e3
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 1 deletion.
69 changes: 69 additions & 0 deletions packages/base/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ import {
explodedViewIcon,
extrusionIcon,
intersectionIcon,
requestAPI,
sphereIcon,
torusIcon,
unionIcon
} from './tools';
import { JupyterCadPanel, JupyterCadWidget } from './widget';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { PathExt } from '@jupyterlab/coreutils';

export function newName(type: string, model: IJupyterCadModel): string {
const sharedModel = model.sharedModel;
Expand Down Expand Up @@ -404,6 +407,43 @@ const CAMERA_FORM = {
}
};

const EXPORT_FORM = {
title: 'Export to .jcad',
schema: {
type: 'object',
required: ['Name'],
additionalProperties: false,
properties: {
Name: {
title: 'File name',
description: 'The exported file name',
type: 'string'
}
}
},
default: (context: DocumentRegistry.IContext<IJupyterCadModel>) => {
return {
Name: PathExt.basename(context.path).replace(
PathExt.extname(context.path),
'.jcad'
)
};
},
syncData: (context: DocumentRegistry.IContext<IJupyterCadModel>) => {
return (props: IDict) => {
const { Name } = props;
console.log(`export to ${Name}`);
requestAPI<{ done: boolean }>('jupytercad/export', {
method: 'POST',
body: JSON.stringify({
path: context.path,
newName: Name
})
});
};
}
};

/**
* Add the FreeCAD commands to the application's command registry.
*/
Expand Down Expand Up @@ -645,6 +685,33 @@ export function addCommands(
await dialog.launch();
}
});

commands.addCommand(CommandIDs.exportJcad, {
label: trans.__('Export to .jcad'),
isEnabled: () => {
return tracker.currentWidget
? tracker.currentWidget.context.model.sharedModel.exportable
: false;
},
iconClass: 'fa fa-file-export',
execute: async () => {
const current = tracker.currentWidget;

if (!current) {
return;
}

const dialog = new FormDialog({
context: current.context,
title: EXPORT_FORM.title,
schema: EXPORT_FORM.schema,
sourceData: EXPORT_FORM.default(tracker.currentWidget?.context),
syncData: EXPORT_FORM.syncData(tracker.currentWidget?.context),
cancelButton: true
});
await dialog.launch();
}
});
}

/**
Expand All @@ -670,6 +737,8 @@ export namespace CommandIDs {
export const updateAxes = 'jupytercad:updateAxes';
export const updateExplodedView = 'jupytercad:updateExplodedView';
export const updateCameraSettings = 'jupytercad:updateCameraSettings';

export const exportJcad = 'jupytercad:exportJcad';
}

namespace Private {
Expand Down
1 change: 1 addition & 0 deletions packages/schema/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface IJupyterCadDoc extends YDocument<IJupyterCadDocChange> {
metadata: JSONObject;
outputs: JSONObject;
readonly editable: boolean;
readonly exportable: boolean;

objectExists(name: string): boolean;
getObjectByName(name: string): IJCadObject | undefined;
Expand Down
2 changes: 2 additions & 0 deletions packages/schema/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ export class JupyterCadDoc
}

editable = true;
exportable = false;

private _getObjectAsYMapByName(name: string): Y.Map<any> | undefined {
for (const obj of this._objects) {
Expand Down Expand Up @@ -547,6 +548,7 @@ export class JupyterCadStepDoc extends JupyterCadDoc {
}

editable = false;
exportable = true;

private _sourceObserver = (events: Y.YEvent<any>[]): void => {
const changes: Array<{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ServerApp": {
"jpserver_extensions": {
"jupytercad_core": true
}
}
}
14 changes: 14 additions & 0 deletions python/jupytercad_core/jupytercad_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@
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"}]


def _load_jupyter_server_extension(server_app):
"""Registers the API handler to receive HTTP requests from the frontend extension.
Parameters
----------
server_app: jupyterlab.labapp.LabApp
JupyterLab application instance
"""
setup_handlers(server_app.web_app)
server_app.log.info("Registered jupytercad server extension")
52 changes: 52 additions & 0 deletions python/jupytercad_core/jupytercad_core/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import json
from pathlib import Path

from jupyter_server.base.handlers import APIHandler
from jupyter_server.utils import url_path_join, ApiPath, to_os_path
import tornado


class JCadExportHandler(APIHandler):
@tornado.web.authenticated
def post(self):
body = self.get_json_body()

# Get filename removing the drive prefix
file_name = body["path"].split(":")[1]
export_name = body["newName"]

root_dir = Path(self.contents_manager.root_dir).resolve()
file_name = Path(to_os_path(ApiPath(file_name), str(root_dir)))

with open(file_name, "r") as fobj:
file_content = fobj.read()

jcad = dict(
objects=[
dict(
name=Path(export_name).stem,
visible=True,
shape="Part::Any",
parameters=dict(
Content=file_content, Type=str(Path(Path(file_name).suffix[1:]))
),
)
],
metadata={},
options={},
outputs={},
)

with open(Path(file_name).parents[0] / export_name, "w") as fobj:
fobj.write(json.dumps(jcad, indent=2))

self.finish(json.dumps({"done": True}))


def setup_handlers(web_app):
host_pattern = ".*$"

base_url = web_app.settings["base_url"]
route_pattern = url_path_join(base_url, "jupytercad", "export")
handlers = [(route_pattern, JCadExportHandler)]
web_app.add_handlers(host_pattern, handlers)
1 change: 1 addition & 0 deletions python/jupytercad_core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ exclude = [".github", "binder"]
[tool.hatch.build.targets.wheel.shared-data]
"install.json" = "share/jupyter/labextensions/@jupytercad/jupytercad-core/install.json"
"jupytercad_core/labextension" = "share/jupyter/labextensions/@jupytercad/jupytercad-core"
"jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d"

[tool.hatch.build.hooks.version]
path = "jupytercad_core/_version.py"
Expand Down
3 changes: 3 additions & 0 deletions python/jupytercad_lab/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ const controlPanel: JupyterFrontEndPlugin<void> = {
* Populates the application menus for the notebook.
*/
function populateMenus(mainMenu: IMainMenu, isEnabled: () => boolean): void {
mainMenu.fileMenu.addItem({
command: CommandIDs.exportJcad
});
// Add undo/redo hooks to the edit menu.
mainMenu.editMenu.undoers.redo.add({
id: CommandIDs.redo,
Expand Down
4 changes: 4 additions & 0 deletions scripts/dev-install.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def install_dev():
execute(f"pip uninstall {py_package} -y")
execute("jlpm clean:all", cwd=root_path / "python" / py_package)
execute(f"pip install -e {python_package_prefix}/{py_package}")

if py_package == "jupytercad_core":
execute("jupyter server extension enable jupytercad_core")

if py_package != "jupytercad_app":
execute(
f"jupyter labextension develop {python_package_prefix}/{py_package} --overwrite"
Expand Down
47 changes: 46 additions & 1 deletion ui-tests/tests/ui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test.describe('UI Test', () => {
});
});

test.describe('File rendering test', () => {
test.describe('File operations', () => {
test.beforeAll(async ({ request }) => {
const content = galata.newContentsHelper(request);
await content.deleteDirectory('/examples');
Expand Down Expand Up @@ -76,6 +76,51 @@ test.describe('UI Test', () => {
}
});
}

test(`Should be able to do export .STEP to .jcad`, async ({ page }) => {
await page.goto();

const fileName = '3M_CONNECTOR.STEP';
const fullPath = `examples/${fileName}`;
await page.notebook.openByPath(fullPath);
await page.notebook.activate(fullPath);

await page.waitForTimeout(3000);

// Export to jcad
await page.getByRole('menuitem', { name: 'File' }).click();
await page.getByText('Export to .jcad').click();

const accept = await page.locator('div.jp-Dialog-buttonLabel', {
hasText: 'Submit'
});
accept.click();

await page.waitForTimeout(1000);

// Refresh file browser
const filebrowserId = 'filebrowser';
await page.sidebar.openTab(filebrowserId);
expect(await page.sidebar.isTabOpen(filebrowserId)).toBeTruthy();
await page.filebrowser.openDirectory('examples');
await page.filebrowser.refresh();

// Open new jcad file
const newFileName = '3M_CONNECTOR.jcad';
const newFullPath = `examples/${newFileName}`;

await page.notebook.openByPath(newFullPath);
await page.notebook.activate(newFullPath);

await page.waitForTimeout(1000);

const main = await page.$('#jp-main-split-panel');
if (main) {
expect(await main.screenshot()).toMatchSnapshot({
name: `JCAD-export-${fileName}.png`
});
}
});
});

test.describe('File operator test', () => {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 03c49e3

Please sign in to comment.