Skip to content

Commit

Permalink
STEP files support (#232)
Browse files Browse the repository at this point in the history
* STEP files support

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Support step files in app

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update

* Add ui-tests

* Update Playwright Snapshots

* Remove logs

Co-authored-by: Duc Trung Le <[email protected]>

* Review

* Fixup

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Duc Trung Le <[email protected]>
  • Loading branch information
4 people committed Dec 13, 2023
1 parent 80e10d2 commit 4aa1881
Show file tree
Hide file tree
Showing 20 changed files with 13,533 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ __pycache__/
# Distribution / packaging
.Python
build/
build_dir/
develop-eggs/
dist/
downloads/
Expand Down
13,138 changes: 13,138 additions & 0 deletions examples/3M_CONNECTOR.STEP

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions packages/occ-worker/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { OCC } from '@jupytercad/opencascade';
import { IJCadContent, IJCadObject } from '@jupytercad/schema';

import { IDict, WorkerAction } from './types';
import { BrepFile, ShapesFactory } from './occapi';
import { ObjectFile, ShapesFactory } from './occapi';
import { OccParser } from './occparser';
import { IOperatorArg, IOperatorFuncOutput } from './types';

Expand Down Expand Up @@ -34,9 +34,12 @@ function buildModel(
if (shapeData) {
outputModel.push({ shapeData, jcObject: object });
}
} else if (parameters['Shape']) {
} else if (parameters['Shape'] && parameters['Type']) {
// Creating occ shape from brep file.
const shapeData = BrepFile({ content: parameters['Shape'] }, model);
const shapeData = ObjectFile(
{ content: parameters['Shape'], type: parameters['Type'] },
model
);
if (shapeData) {
outputModel.push({ shapeData, jcObject: object });
}
Expand Down
52 changes: 42 additions & 10 deletions packages/occ-worker/src/occapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,19 +328,17 @@ export function _Any(
arg: IAny,
content: IJCadContent
): OCC.TopoDS_Shape | undefined {
const { Shape, Placement } = arg;
const result = _loadBrep({ content: Shape });
const { Content, Type, Placement } = arg;
const result = _loadObjectFile({ content: Content, type: Type });
if (result) {
return setShapePlacement(result, Placement);
}
}

export function _loadBrep(arg: {
content: string;
}): OCC.TopoDS_Shape | undefined {
export function _loadBrepFile(content: string): OCC.TopoDS_Shape | undefined {
const oc = getOcc();
const fakeFileName = `${uuid()}.brep`;
oc.FS.createDataFile('/', fakeFileName, arg.content, true, true, true);
oc.FS.createDataFile('/', fakeFileName, content, true, true, true);
const shape = new oc.TopoDS_Shape();
const builder = new oc.BRep_Builder();
const progress = new oc.Message_ProgressRange_1();
Expand All @@ -349,6 +347,40 @@ export function _loadBrep(arg: {
return shape;
}

export function _loadStepFile(content: string): OCC.TopoDS_Shape | undefined {
const oc = getOcc();
const fakeFileName = `${uuid()}.STEP`;
oc.FS.createDataFile('/', fakeFileName, content, true, true, true);

const reader = new oc.STEPControl_Reader_1();

const readResult = reader.ReadFile(fakeFileName);

if (readResult === oc.IFSelect_ReturnStatus.IFSelect_RetDone) {
reader.TransferRoots(new oc.Message_ProgressRange_1());
const shape = reader.OneShape();
oc.FS.unlink('/' + fakeFileName);
return shape;
} else {
console.error('Something in OCCT went wrong trying to read');
return undefined;
}
}

export function _loadObjectFile(arg: {
content: string;
type: IAny['Type'];
}): OCC.TopoDS_Shape | undefined {
switch (arg.type.toLowerCase()) {
case 'brep':
return _loadBrepFile(arg.content);
case 'step':
return _loadStepFile(arg.content);
default:
throw `${arg.type} file not supported`;
}
}

const Any = operatorCache<IAny>('Part::Any', _Any);
const Box = operatorCache<IBox>('Part::Box', _Box);

Expand Down Expand Up @@ -376,10 +408,10 @@ const Intersection = operatorCache<IIntersection>(

const Extrude = operatorCache<IExtrusion>('Part::Extrusion', _Extrude);

export const BrepFile = operatorCache<{ content: string }>(
'BrepFile',
_loadBrep
);
export const ObjectFile = operatorCache<{
content: string;
type: IAny['Type'];
}>('ObjectFile', _loadObjectFile);

export const ShapesFactory: {
[key in Parts]: IAllOperatorFunc;
Expand Down
6 changes: 3 additions & 3 deletions packages/occ-worker/src/operatorcache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ const BOOLEAN_OPERATORS = [
] as const;

const MISC_OPERATORS = [
'BrepFile',
'ObjectFile',
'Sketcher::SketchObject',
'Part::Any'
] as const;

export function expand_operator(
name: Parts | 'BrepFile',
name: Parts | 'ObjectFile',
args: any,
content: IJCadContent
): IDict {
Expand Down Expand Up @@ -143,7 +143,7 @@ export function shape_meta_data(shape: OCC.TopoDS_Shape): IShapeMetadata {
};
}
export function operatorCache<T>(
name: Parts | 'BrepFile',
name: Parts | 'ObjectFile',
ops: (args: T, content: IJCadContent) => OCC.TopoDS_Shape | undefined
) {
return (
Expand Down
3 changes: 3 additions & 0 deletions packages/opencascade/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ mainBuild:
- symbol: Handle_Poly_Polygon3D
- symbol: Handle_Poly_PolygonOnTriangulation
- symbol: Handle_Poly_Triangulation
- symbol: IFSelect_ReturnStatus
- symbol: Message_ProgressRange
- symbol: NCollection_BaseList
- symbol: NCollection_BaseMap
Expand All @@ -69,6 +70,7 @@ mainBuild:
- symbol: Poly_Triangulation
- symbol: Standard_Transient
- symbol: StdPrs_ToolTriangulatedShape
- symbol: STEPControl_Reader
- symbol: TColgp_Array1OfDir
- symbol: TColgp_Array1OfPnt
- symbol: TColStd_Array1OfInteger
Expand All @@ -89,6 +91,7 @@ mainBuild:
- symbol: TopTools_IndexedDataMapOfShapeListOfShape
- symbol: TopTools_IndexedMapOfShape
- symbol: TopTools_ListOfShape
- symbol: XSControl_Reader
emccFlags:
- "-flto"
- "-fexceptions"
Expand Down
71 changes: 70 additions & 1 deletion packages/schema/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,10 @@ export class JupyterCadDoc
}

get objects(): Array<IJCadObject> {
return this._objects.map(
const objs = this._objects.map(
obj => JSONExt.deepCopy(obj.toJSON()) as IJCadObject
);
return objs;
}

get options(): JSONObject {
Expand Down Expand Up @@ -483,6 +484,74 @@ export class JupyterCadDoc
this
);
}

// TODO Change the IJupyterCadDoc interface to add an "editable" property allowing to make read-only docs?
export class JupyterCadStepDoc extends JupyterCadDoc {
constructor() {
super();

this._source = this.ydoc.getText('source');

this._source.observeDeep(this._sourceObserver);
}

get version(): string {
return '0.1.0';
}

get objectsChanged(): ISignal<IJupyterCadDoc, IJcadObjectDocChange> {
return this._objectChanged;
}

get objects(): Array<IJCadObject> {
const source = this._source.toJSON();

if (!source) {
return [];
}

return [
{
name: 'Step File', // TODO get file name?
visible: true,
shape: 'Part::Any',
parameters: {
Content: this._source.toJSON(),
Type: 'STEP'
}
}
];
}

static create(): JupyterCadStepDoc {
return new JupyterCadStepDoc();
}

private _sourceObserver = (events: Y.YEvent<any>[]): void => {
const changes: Array<{
name: string;
key: keyof IJCadObject;
newValue: IJCadObject;
}> = [];
events.forEach(event => {
event.keys.forEach((change, key) => {
changes.push({
name: 'Step File',
key: key as any,
newValue: JSONExt.deepCopy(event.target.toJSON())
});
});
});
this._objectChanged.emit({ objectChange: changes });
this._changed.emit({ objectChange: changes });
};

private _source: Y.Text;
private _objectChanged = new Signal<IJupyterCadDoc, IJcadObjectDocChange>(
this
);
}

export namespace JupyterCadModel {
export interface IOptions
extends DocumentRegistry.IModelOptions<IJupyterCadDoc> {
Expand Down
10 changes: 7 additions & 3 deletions packages/schema/src/schema/any.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
"type": "object",
"description": "Part::Any",
"title": "IAny",
"required": ["Shape"],
"required": ["Content", "Type"],
"additionalProperties": false,
"properties": {
"Shape": {
"Content": {
"type": "string",
"description": "The BREP string of the shape"
"description": "The string content of the object"
},
"Type": {
"type": "string",
"enum": ["brep", "step"]
},
"Placement": {
"$ref": "./placement.json"
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ path = "python/jupytercad/jupytercad/__init__.py"
[tool.jupyter-releaser]
skip = [ "check-python" ]


[tool.jupyter-releaser.options]
version-cmd = "python scripts/bump-version.py"
python_packages = [
Expand Down
8 changes: 6 additions & 2 deletions python/jupytercad_app/src/app/plugins/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ const browserWidget: JupyterFrontEndPlugin<void> = {
if (e.type === 'directory') {
return {};
}
if (name.endsWith('fcstd') || name.endsWith('jcad')) {
if (
name.endsWith('fcstd') ||
name.endsWith('jcad') ||
name.endsWith('step')
) {
return {};
}
return null;
Expand All @@ -43,7 +47,7 @@ const browserWidget: JupyterFrontEndPlugin<void> = {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = '.FCStd, .fcstd,.jcad, .JCAD';
input.accept = '.FCStd,.fcstd,.jcad,.JCAD,.step,.STEP';
input.onclick = () => void (input.value = '');
input.onchange = () => {
const files = input.files;
Expand Down
2 changes: 1 addition & 1 deletion python/jupytercad_app/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const distRoot = path.resolve(
);

// Ensure a clear build directory.
const buildDir = path.resolve(__dirname, 'build');
const buildDir = path.resolve(__dirname, 'build_dir');
if (fs.existsSync(buildDir)) {
fs.removeSync(buildDir);
}
Expand Down
37 changes: 37 additions & 0 deletions python/jupytercad_core/jupytercad_core/step_ydoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import json
from typing import Any, Callable
from functools import partial

from jupyter_ydoc.ybasedoc import YBaseDoc


class YSTEP(YBaseDoc):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._ysource = self._ydoc.get_text("source")

def version(self) -> str:
return "0.1.0"

def get(self) -> str:
"""
Returns the content of the document.
:return: Document's content.
:rtype: Any
"""
return json.dumps(self._ysource.to_json())

def set(self, value: str) -> None:
"""
Sets the content of the document.
:param value: The content of the document.
:type value: Any
"""
with self._ydoc.begin_transaction() as t:
self._ysource.extend(t, value)

def observe(self, callback: Callable[[str, Any], None]):
self.unobserve()
self._subscriptions[self._ysource] = self._ysource.observe(
partial(callback, "source")
)
1 change: 1 addition & 0 deletions python/jupytercad_core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ requires-python = ">=3.8"

[project.entry-points.jupyter_ydoc]
jcad = "jupytercad_core.jcad_ydoc:YJCad"
step = "jupytercad_core.step_ydoc:YSTEP"

[tool.hatch.version]
source = "nodejs"
Expand Down
4 changes: 3 additions & 1 deletion python/jupytercad_core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import jcadPlugin from './jcadplugin/plugins';
import stepPlugin from './stepplugin/plugins';
import {
annotationPlugin,
trackerPlugin,
Expand All @@ -11,5 +12,6 @@ export default [
trackerPlugin,
annotationPlugin,
workerRegistryPlugin,
jcadPlugin
jcadPlugin,
stepPlugin
];
2 changes: 1 addition & 1 deletion python/jupytercad_core/src/jcadplugin/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { fileIcon } from '@jupyterlab/ui-components';
import { JupyterCadWidgetFactory } from '../factory';
import { JupyterCadJcadModelFactory } from './modelfactory';

const FACTORY = 'Jupytercad Jcad Factory';
const FACTORY = 'JupyterCAD .jcad Viewer';
const PALETTE_CATEGORY = 'JupyterCAD';

namespace CommandIDs {
Expand Down
Loading

0 comments on commit 4aa1881

Please sign in to comment.