Skip to content

Commit 2096747

Browse files
martinRenoupre-commit-ci[bot]trungleduc
authored
Support for opening STL files (#246)
* STL support * Draft * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update opencascade build * Update ydoc --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Duc Trung Le <[email protected]>
1 parent e9cf29c commit 2096747

File tree

15 files changed

+295
-5
lines changed

15 files changed

+295
-5
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ jupytercad
77
packages/opencascade/build.yml
88
yarn.lock
99
*.jcad
10+
playwright.config.js

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
"clean:all": "lerna run clean:all",
4040
"eslint": "eslint . --ext .ts,.tsx --cache --fix",
4141
"eslint:check": "eslint . --ext .ts,.tsx",
42-
"prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
43-
"prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
42+
"prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.yml}\"",
43+
"prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md,.yml}\"",
4444
"lint:check": "jlpm run prettier:check && jlpm run eslint:check",
4545
"lint": "jlpm run prettier && jlpm run eslint",
4646
"test": "lerna run test",

packages/occ-worker/src/occapi/loadObjectFile.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { IAny } from '@jupytercad/schema';
33

44
import { _loadBrepFile } from './brepIO';
55
import { _loadStepFile } from './stepIO';
6+
import { _loadStlFile } from './stlIO';
67

78
export function _loadObjectFile(arg: {
89
content: string;
@@ -13,6 +14,8 @@ export function _loadObjectFile(arg: {
1314
return _loadBrepFile(arg.content);
1415
case 'step':
1516
return _loadStepFile(arg.content);
17+
case 'stl':
18+
return _loadStlFile(arg.content);
1619
default:
1720
throw `${arg.type} file not supported`;
1821
}

packages/schema/src/model.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,74 @@ export class JupyterCadStepDoc extends JupyterCadDoc {
575575
);
576576
}
577577

578+
export class JupyterCadStlDoc extends JupyterCadDoc {
579+
constructor() {
580+
super();
581+
582+
this._source = this.ydoc.getText('source');
583+
584+
this._source.observeDeep(this._sourceObserver);
585+
}
586+
587+
get version(): string {
588+
return '0.1.0';
589+
}
590+
591+
get objectsChanged(): ISignal<IJupyterCadDoc, IJcadObjectDocChange> {
592+
return this._objectChanged;
593+
}
594+
595+
get objects(): Array<IJCadObject> {
596+
const source = this._source.toJSON();
597+
598+
if (!source) {
599+
return [];
600+
}
601+
602+
return [
603+
{
604+
name: 'Stl File', // TODO get file name?
605+
visible: true,
606+
shape: 'Part::Any',
607+
parameters: {
608+
Content: this._source.toJSON(),
609+
Type: 'STL'
610+
}
611+
}
612+
];
613+
}
614+
615+
static create(): JupyterCadStlDoc {
616+
return new JupyterCadStlDoc();
617+
}
618+
619+
editable = false;
620+
621+
private _sourceObserver = (events: Y.YEvent<any>[]): void => {
622+
const changes: Array<{
623+
name: string;
624+
key: keyof IJCadObject;
625+
newValue: IJCadObject;
626+
}> = [];
627+
events.forEach(event => {
628+
event.keys.forEach((change, key) => {
629+
changes.push({
630+
name: 'Stl File',
631+
key: key as any,
632+
newValue: JSONExt.deepCopy(event.target.toJSON())
633+
});
634+
});
635+
});
636+
this._objectChanged.emit({ objectChange: changes });
637+
this._changed.emit({ objectChange: changes });
638+
};
639+
640+
private _source: Y.Text;
641+
private _objectChanged = new Signal<IJupyterCadDoc, IJcadObjectDocChange>(
642+
this
643+
);
644+
}
645+
578646
export namespace JupyterCadModel {
579647
export interface IOptions
580648
extends DocumentRegistry.IModelOptions<IJupyterCadDoc> {

packages/schema/src/schema/any.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212
"Type": {
1313
"type": "string",
14-
"enum": ["brep", "step"]
14+
"enum": ["brep", "step", "stl"]
1515
},
1616
"Placement": {
1717
"$ref": "./placement.json"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import json
2+
from typing import Any, Callable
3+
from functools import partial
4+
5+
from pycrdt import Text
6+
from jupyter_ydoc.ybasedoc import YBaseDoc
7+
8+
9+
class YSTL(YBaseDoc):
10+
def __init__(self, *args, **kwargs):
11+
super().__init__(*args, **kwargs)
12+
self._ydoc["source"] = self._ysource = Text()
13+
14+
def version(self) -> str:
15+
return "0.1.0"
16+
17+
def get(self) -> str:
18+
"""
19+
Returns the content of the document.
20+
:return: Document's content.
21+
:rtype: Any
22+
"""
23+
return json.dumps(self._ysource.to_py())
24+
25+
def set(self, value: str) -> None:
26+
"""
27+
Sets the content of the document.
28+
:param value: The content of the document.
29+
:type value: Any
30+
"""
31+
self._ysource[:] = value
32+
33+
def observe(self, callback: Callable[[str, Any], None]):
34+
self.unobserve()
35+
self._subscriptions[self._ysource] = self._ysource.observe(
36+
partial(callback, "source")
37+
)

python/jupytercad_core/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ requires-python = ">=3.8"
3232
[project.entry-points.jupyter_ydoc]
3333
jcad = "jupytercad_core.jcad_ydoc:YJCad"
3434
step = "jupytercad_core.step_ydoc:YSTEP"
35+
stl = "jupytercad_core.stl_ydoc:YSTL"
3536

3637
[tool.hatch.version]
3738
source = "nodejs"

python/jupytercad_core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import jcadPlugin from './jcadplugin/plugins';
22
import stepPlugin from './stepplugin/plugins';
3+
import stlPlugin from './stlplugin/plugins';
34
import {
45
annotationPlugin,
56
externalCommandRegistryPlugin,
@@ -16,6 +17,7 @@ export default [
1617
workerRegistryPlugin,
1718
jcadPlugin,
1819
stepPlugin,
20+
stlPlugin,
1921
formSchemaRegistryPlugin,
2022
externalCommandRegistryPlugin
2123
];
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { IJupyterCadDoc, JupyterCadModel } from '@jupytercad/schema';
2+
import { DocumentRegistry } from '@jupyterlab/docregistry';
3+
import { Contents } from '@jupyterlab/services';
4+
5+
/**
6+
* A Model factory to create new instances of JupyterCadModel.
7+
*/
8+
export class JupyterCadStlModelFactory
9+
implements DocumentRegistry.IModelFactory<JupyterCadModel>
10+
{
11+
/**
12+
* Whether the model is collaborative or not.
13+
*/
14+
readonly collaborative = true;
15+
16+
/**
17+
* The name of the model.
18+
*
19+
* @returns The name
20+
*/
21+
get name(): string {
22+
return 'jupytercad-stlmodel';
23+
}
24+
25+
/**
26+
* The content type of the file.
27+
*
28+
* @returns The content type
29+
*/
30+
get contentType(): Contents.ContentType {
31+
return 'stl';
32+
}
33+
34+
/**
35+
* The format of the file.
36+
*
37+
* @returns the file format
38+
*/
39+
get fileFormat(): Contents.FileFormat {
40+
return 'text';
41+
}
42+
43+
/**
44+
* Get whether the model factory has been disposed.
45+
*
46+
* @returns disposed status
47+
*/
48+
get isDisposed(): boolean {
49+
return this._disposed;
50+
}
51+
52+
/**
53+
* Dispose the model factory.
54+
*/
55+
dispose(): void {
56+
this._disposed = true;
57+
}
58+
59+
/**
60+
* Get the preferred language given the path on the file.
61+
*
62+
* @param path path of the file represented by this document model
63+
* @returns The preferred language
64+
*/
65+
preferredLanguage(path: string): string {
66+
return '';
67+
}
68+
69+
/**
70+
* Create a new instance of JupyterCadModel.
71+
*
72+
* @returns The model
73+
*/
74+
createNew(
75+
options: DocumentRegistry.IModelOptions<IJupyterCadDoc>
76+
): JupyterCadModel {
77+
const model = new JupyterCadModel({
78+
sharedModel: options.sharedModel,
79+
languagePreference: options.languagePreference
80+
});
81+
return model;
82+
}
83+
84+
private _disposed = false;
85+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {
2+
ICollaborativeDrive,
3+
SharedDocumentFactory
4+
} from '@jupyter/docprovider';
5+
import {
6+
IJCadWorkerRegistry,
7+
IJCadWorkerRegistryToken,
8+
IJupyterCadDocTracker,
9+
IJupyterCadWidget,
10+
JupyterCadStlDoc,
11+
IJCadExternalCommandRegistry,
12+
IJCadExternalCommandRegistryToken
13+
} from '@jupytercad/schema';
14+
import {
15+
JupyterFrontEnd,
16+
JupyterFrontEndPlugin
17+
} from '@jupyterlab/application';
18+
import { IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
19+
20+
import { JupyterCadStlModelFactory } from './modelfactory';
21+
import { JupyterCadWidgetFactory } from '../factory';
22+
23+
const FACTORY = 'JupyterCAD STL Viewer';
24+
25+
const activate = (
26+
app: JupyterFrontEnd,
27+
tracker: WidgetTracker<IJupyterCadWidget>,
28+
themeManager: IThemeManager,
29+
drive: ICollaborativeDrive,
30+
workerRegistry: IJCadWorkerRegistry,
31+
externalCommandRegistry: IJCadExternalCommandRegistry
32+
): void => {
33+
const widgetFactory = new JupyterCadWidgetFactory({
34+
name: FACTORY,
35+
modelName: 'jupytercad-stlmodel',
36+
fileTypes: ['stl'],
37+
defaultFor: ['stl'],
38+
tracker,
39+
commands: app.commands,
40+
workerRegistry,
41+
externalCommandRegistry
42+
});
43+
// Registering the widget factory
44+
app.docRegistry.addWidgetFactory(widgetFactory);
45+
46+
// Creating and registering the model factory for our custom DocumentModel
47+
const modelFactory = new JupyterCadStlModelFactory();
48+
app.docRegistry.addModelFactory(modelFactory);
49+
// register the filetype
50+
app.docRegistry.addFileType({
51+
name: 'stl',
52+
displayName: 'STL',
53+
mimeTypes: ['text/json'],
54+
extensions: ['.stl', '.STL'],
55+
fileFormat: 'text',
56+
contentType: 'stl'
57+
});
58+
59+
const stlSharedModelFactory: SharedDocumentFactory = () => {
60+
return new JupyterCadStlDoc();
61+
};
62+
drive.sharedModelFactory.registerDocumentFactory(
63+
'stl',
64+
stlSharedModelFactory
65+
);
66+
67+
widgetFactory.widgetCreated.connect((sender, widget) => {
68+
widget.context.pathChanged.connect(() => {
69+
tracker.save(widget);
70+
});
71+
themeManager.themeChanged.connect((_, changes) =>
72+
widget.context.model.themeChanged.emit(changes)
73+
);
74+
tracker.add(widget);
75+
app.shell.activateById('jupytercad::leftControlPanel');
76+
app.shell.activateById('jupytercad::rightControlPanel');
77+
});
78+
};
79+
80+
const stlPlugin: JupyterFrontEndPlugin<void> = {
81+
id: 'jupytercad:stlplugin',
82+
requires: [
83+
IJupyterCadDocTracker,
84+
IThemeManager,
85+
ICollaborativeDrive,
86+
IJCadWorkerRegistryToken,
87+
IJCadExternalCommandRegistryToken
88+
],
89+
autoStart: true,
90+
activate
91+
};
92+
93+
export default stlPlugin;

0 commit comments

Comments
 (0)