Skip to content

Commit 349e04f

Browse files
authored
Step file export to .jcad (#238)
* .jcad file export * Put another timeout * Nevermind
1 parent 732ea7c commit 349e04f

File tree

10 files changed

+154
-1
lines changed

10 files changed

+154
-1
lines changed

packages/base/src/commands.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ import {
2222
explodedViewIcon,
2323
extrusionIcon,
2424
intersectionIcon,
25+
requestAPI,
2526
sphereIcon,
2627
torusIcon,
2728
unionIcon
2829
} from './tools';
2930
import { JupyterCadPanel, JupyterCadWidget } from './widget';
31+
import { DocumentRegistry } from '@jupyterlab/docregistry';
32+
import { PathExt } from '@jupyterlab/coreutils';
3033

3134
export function newName(type: string, model: IJupyterCadModel): string {
3235
const sharedModel = model.sharedModel;
@@ -404,6 +407,43 @@ const CAMERA_FORM = {
404407
}
405408
};
406409

410+
const EXPORT_FORM = {
411+
title: 'Export to .jcad',
412+
schema: {
413+
type: 'object',
414+
required: ['Name'],
415+
additionalProperties: false,
416+
properties: {
417+
Name: {
418+
title: 'File name',
419+
description: 'The exported file name',
420+
type: 'string'
421+
}
422+
}
423+
},
424+
default: (context: DocumentRegistry.IContext<IJupyterCadModel>) => {
425+
return {
426+
Name: PathExt.basename(context.path).replace(
427+
PathExt.extname(context.path),
428+
'.jcad'
429+
)
430+
};
431+
},
432+
syncData: (context: DocumentRegistry.IContext<IJupyterCadModel>) => {
433+
return (props: IDict) => {
434+
const { Name } = props;
435+
console.log(`export to ${Name}`);
436+
requestAPI<{ done: boolean }>('jupytercad/export', {
437+
method: 'POST',
438+
body: JSON.stringify({
439+
path: context.path,
440+
newName: Name
441+
})
442+
});
443+
};
444+
}
445+
};
446+
407447
/**
408448
* Add the FreeCAD commands to the application's command registry.
409449
*/
@@ -645,6 +685,33 @@ export function addCommands(
645685
await dialog.launch();
646686
}
647687
});
688+
689+
commands.addCommand(CommandIDs.exportJcad, {
690+
label: trans.__('Export to .jcad'),
691+
isEnabled: () => {
692+
return tracker.currentWidget
693+
? tracker.currentWidget.context.model.sharedModel.exportable
694+
: false;
695+
},
696+
iconClass: 'fa fa-file-export',
697+
execute: async () => {
698+
const current = tracker.currentWidget;
699+
700+
if (!current) {
701+
return;
702+
}
703+
704+
const dialog = new FormDialog({
705+
context: current.context,
706+
title: EXPORT_FORM.title,
707+
schema: EXPORT_FORM.schema,
708+
sourceData: EXPORT_FORM.default(tracker.currentWidget?.context),
709+
syncData: EXPORT_FORM.syncData(tracker.currentWidget?.context),
710+
cancelButton: true
711+
});
712+
await dialog.launch();
713+
}
714+
});
648715
}
649716

650717
/**
@@ -670,6 +737,8 @@ export namespace CommandIDs {
670737
export const updateAxes = 'jupytercad:updateAxes';
671738
export const updateExplodedView = 'jupytercad:updateExplodedView';
672739
export const updateCameraSettings = 'jupytercad:updateCameraSettings';
740+
741+
export const exportJcad = 'jupytercad:exportJcad';
673742
}
674743

675744
namespace Private {

packages/schema/src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export interface IJupyterCadDoc extends YDocument<IJupyterCadDocChange> {
8080
metadata: JSONObject;
8181
outputs: JSONObject;
8282
readonly editable: boolean;
83+
readonly exportable: boolean;
8384

8485
objectExists(name: string): boolean;
8586
getObjectByName(name: string): IJCadObject | undefined;

packages/schema/src/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ export class JupyterCadDoc
446446
}
447447

448448
editable = true;
449+
exportable = false;
449450

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

549550
editable = false;
551+
exportable = true;
550552

551553
private _sourceObserver = (events: Y.YEvent<any>[]): void => {
552554
const changes: Array<{
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ServerApp": {
3+
"jpserver_extensions": {
4+
"jupytercad_core": true
5+
}
6+
}
7+
}

python/jupytercad_core/jupytercad_core/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@
99
warnings.warn("Importing 'jupytercad_core' outside a proper installation.")
1010
__version__ = "dev"
1111

12+
from .handlers import setup_handlers
13+
1214

1315
def _jupyter_labextension_paths():
1416
return [{"src": "labextension", "dest": "@jupytercad/jupytercad-core"}]
17+
18+
19+
def _load_jupyter_server_extension(server_app):
20+
"""Registers the API handler to receive HTTP requests from the frontend extension.
21+
22+
Parameters
23+
----------
24+
server_app: jupyterlab.labapp.LabApp
25+
JupyterLab application instance
26+
"""
27+
setup_handlers(server_app.web_app)
28+
server_app.log.info("Registered jupytercad server extension")
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import json
2+
from pathlib import Path
3+
4+
from jupyter_server.base.handlers import APIHandler
5+
from jupyter_server.utils import url_path_join, ApiPath, to_os_path
6+
import tornado
7+
8+
9+
class JCadExportHandler(APIHandler):
10+
@tornado.web.authenticated
11+
def post(self):
12+
body = self.get_json_body()
13+
14+
# Get filename removing the drive prefix
15+
file_name = body["path"].split(":")[1]
16+
export_name = body["newName"]
17+
18+
root_dir = Path(self.contents_manager.root_dir).resolve()
19+
file_name = Path(to_os_path(ApiPath(file_name), str(root_dir)))
20+
21+
with open(file_name, "r") as fobj:
22+
file_content = fobj.read()
23+
24+
jcad = dict(
25+
objects=[
26+
dict(
27+
name=Path(export_name).stem,
28+
visible=True,
29+
shape="Part::Any",
30+
parameters=dict(
31+
Content=file_content, Type=str(Path(Path(file_name).suffix[1:]))
32+
),
33+
)
34+
],
35+
metadata={},
36+
options={},
37+
outputs={},
38+
)
39+
40+
with open(Path(file_name).parents[0] / export_name, "w") as fobj:
41+
fobj.write(json.dumps(jcad, indent=2))
42+
43+
self.finish(json.dumps({"done": True}))
44+
45+
46+
def setup_handlers(web_app):
47+
host_pattern = ".*$"
48+
49+
base_url = web_app.settings["base_url"]
50+
route_pattern = url_path_join(base_url, "jupytercad", "export")
51+
handlers = [(route_pattern, JCadExportHandler)]
52+
web_app.add_handlers(host_pattern, handlers)

python/jupytercad_core/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ exclude = [".github", "binder"]
4747
[tool.hatch.build.targets.wheel.shared-data]
4848
"install.json" = "share/jupyter/labextensions/@jupytercad/jupytercad-core/install.json"
4949
"jupytercad_core/labextension" = "share/jupyter/labextensions/@jupytercad/jupytercad-core"
50+
"jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d"
5051

5152
[tool.hatch.build.hooks.version]
5253
path = "jupytercad_core/_version.py"

python/jupytercad_lab/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ const controlPanel: JupyterFrontEndPlugin<void> = {
104104
* Populates the application menus for the notebook.
105105
*/
106106
function populateMenus(mainMenu: IMainMenu, isEnabled: () => boolean): void {
107+
mainMenu.fileMenu.addItem({
108+
command: CommandIDs.exportJcad
109+
});
107110
// Add undo/redo hooks to the edit menu.
108111
mainMenu.editMenu.undoers.redo.add({
109112
id: CommandIDs.redo,

scripts/dev-install.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ def install_dev():
2323
execute(f"pip uninstall {py_package} -y")
2424
execute("jlpm clean:all", cwd=root_path / "python" / py_package)
2525
execute(f"pip install -e {python_package_prefix}/{py_package}")
26+
27+
if py_package == "jupytercad_core":
28+
execute("jupyter server extension enable jupytercad_core")
29+
2630
if py_package != "jupytercad_app":
2731
execute(
2832
f"jupyter labextension develop {python_package_prefix}/{py_package} --overwrite"

ui-tests/tests/ui.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ test.describe('UI Test', () => {
2424
});
2525
});
2626

27-
test.describe('File rendering test', () => {
27+
test.describe('File operations', () => {
2828
test.beforeAll(async ({ request }) => {
2929
const content = galata.newContentsHelper(request);
3030
await content.deleteDirectory('/examples');

0 commit comments

Comments
 (0)