Skip to content

Commit c7f4cad

Browse files
authored
Improve post-processing system (#350)
* Add GLTF support * Update external worker interface
1 parent e35f4b3 commit c7f4cad

File tree

9 files changed

+162
-42
lines changed

9 files changed

+162
-42
lines changed

packages/base/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"devDependencies": {
7171
"@apidevtools/json-schema-ref-parser": "^9.0.9",
7272
"@types/node": "^18.15.11",
73+
"@types/three": "^0.135.0",
7374
"rimraf": "^3.0.2",
7475
"typescript": "^5"
7576
},

packages/base/src/3dview/mainview.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
IJupyterCadClientState,
77
IJupyterCadDoc,
88
IJupyterCadModel,
9+
IPostOperatorInput,
910
IPostResult,
1011
ISelection
1112
} from '@jupytercad/schema';
@@ -19,6 +20,7 @@ import * as React from 'react';
1920
import * as THREE from 'three';
2021
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
2122
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
23+
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
2224
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';
2325

2426
import { FloatingAnnotation } from '../annotation';
@@ -719,11 +721,43 @@ export class MainView extends React.Component<IProps, IStates> {
719721

720722
private _requestRender(
721723
sender: MainViewModel,
722-
renderData: { shapes: any; postShapes: any }
724+
renderData: {
725+
shapes: any;
726+
postShapes?: IDict<IPostResult> | null;
727+
postResult?: IDict<IPostOperatorInput>;
728+
}
723729
) {
724-
const { shapes, postShapes } = renderData;
730+
const { shapes, postShapes, postResult } = renderData;
725731
if (shapes !== null && shapes !== undefined) {
726732
this._shapeToMesh(renderData.shapes);
733+
const options = {
734+
binary: true,
735+
onlyVisible: false
736+
};
737+
738+
if (postResult && this._meshGroup) {
739+
const exporter = new GLTFExporter();
740+
Object.values(postResult).forEach(pos => {
741+
const objName = pos.jcObject.parameters?.['Object'];
742+
if (!objName) {
743+
return;
744+
}
745+
const threeShape = this._meshGroup!.getObjectByName(
746+
`${objName}-group`
747+
);
748+
if (!threeShape) {
749+
return;
750+
}
751+
exporter.parse(
752+
threeShape,
753+
exported => {
754+
pos.postShape = exported as any;
755+
},
756+
options
757+
);
758+
});
759+
this._mainViewModel.sendRawGeomeryToWorker(postResult);
760+
}
727761
}
728762
if (postShapes !== null && postShapes !== undefined) {
729763
Object.entries(postShapes).forEach(([objName, postResult]) => {

packages/base/src/3dview/mainviewmodel.ts

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { IWorkerMessage } from '@jupytercad/occ-worker';
22
import {
33
IAnnotation,
4+
IDict,
45
IDisplayShape,
56
IJcadObjectDocChange,
67
IJCadWorker,
78
IJCadWorkerRegistry,
89
IJupyterCadDoc,
910
IJupyterCadModel,
1011
IMainMessage,
12+
IPostOperatorInput,
13+
IPostResult,
14+
JCadWorkerSupportedFormat,
1115
MainAction,
1216
WorkerAction
1317
} from '@jupytercad/schema';
@@ -31,7 +35,14 @@ export class MainViewModel implements IDisposable {
3135
get id(): string {
3236
return this._id;
3337
}
34-
get renderSignal(): ISignal<this, { shapes: any; postShapes: any }> {
38+
get renderSignal(): ISignal<
39+
this,
40+
{
41+
shapes: any;
42+
postShapes?: IDict<IPostResult> | null;
43+
postResult?: IDict<IPostOperatorInput>;
44+
}
45+
> {
3546
return this._renderSignal;
3647
}
3748
get jcadModel() {
@@ -76,21 +87,37 @@ export class MainViewModel implements IDisposable {
7687
switch (msg.action) {
7788
case MainAction.DISPLAY_SHAPE: {
7889
const { result, postResult } = msg.payload;
90+
91+
const rawPostResult: IDict<IPostOperatorInput> = {};
92+
const threejsPostResult: IDict<IPostOperatorInput> = {};
93+
94+
Object.entries(postResult).forEach(([key, val]) => {
95+
const format = val.jcObject?.shapeMetadata?.shapeFormat;
96+
if (format === JCadWorkerSupportedFormat.BREP) {
97+
rawPostResult[key] = val;
98+
} else if (format === JCadWorkerSupportedFormat.GLTF) {
99+
threejsPostResult[key] = val;
100+
}
101+
});
102+
79103
this._saveMeta(result);
80104

81105
if (this._firstRender) {
82-
const postShapes = this._jcadModel.sharedModel.outputs;
83-
this._renderSignal.emit({ shapes: result, postShapes });
106+
const postShapes = this._jcadModel.sharedModel
107+
.outputs as any as IDict<IPostResult>;
108+
this._renderSignal.emit({
109+
shapes: result,
110+
postShapes,
111+
postResult: threejsPostResult
112+
});
84113
this._firstRender = false;
85114
} else {
86-
this._renderSignal.emit({ shapes: result, postShapes: null });
87-
this._postWorkerId.forEach((wk, id) => {
88-
wk.postMessage({
89-
id,
90-
action: WorkerAction.POSTPROCESS,
91-
payload: postResult
92-
});
115+
this._renderSignal.emit({
116+
shapes: result,
117+
postShapes: null,
118+
postResult: threejsPostResult
93119
});
120+
this.sendRawGeomeryToWorker(rawPostResult);
94121
}
95122

96123
break;
@@ -111,10 +138,35 @@ export class MainViewModel implements IDisposable {
111138
}
112139
};
113140

141+
sendRawGeomeryToWorker(postResult: IDict<IPostOperatorInput>): void {
142+
Object.values(postResult).forEach(res => {
143+
this._postWorkerId.forEach((wk, id) => {
144+
const shape = res.jcObject.shape;
145+
if (!shape) {
146+
return;
147+
}
148+
149+
const { shapeFormat, workerId } = res.jcObject?.shapeMetadata ?? {};
150+
const worker = this._workerRegistry.getWorker(workerId ?? '');
151+
if (wk !== worker) {
152+
return;
153+
}
154+
155+
if (wk.shapeFormat === shapeFormat) {
156+
wk.postMessage({
157+
id,
158+
action: WorkerAction.POSTPROCESS,
159+
payload: res
160+
});
161+
}
162+
});
163+
});
164+
}
165+
114166
postProcessWorkerHandler = (msg: IMainMessage): void => {
115167
switch (msg.action) {
116168
case MainAction.DISPLAY_POST: {
117-
const postShapes: any = {};
169+
const postShapes: IDict<IPostResult> = {};
118170
msg.payload.forEach(element => {
119171
const { jcObject, postResult } = element;
120172
this._jcadModel.sharedModel.setOutput(jcObject.name, postResult);
@@ -168,9 +220,14 @@ export class MainViewModel implements IDisposable {
168220
private _postWorkerId: Map<string, IJCadWorker> = new Map();
169221
private _firstRender = true;
170222
private _id: string;
171-
private _renderSignal = new Signal<this, { shapes: any; postShapes: any }>(
172-
this
173-
);
223+
private _renderSignal = new Signal<
224+
this,
225+
{
226+
shapes: any;
227+
postShapes?: IDict<IPostResult> | null;
228+
postResult?: IDict<IPostOperatorInput>;
229+
}
230+
>(this);
174231
private _isDisposed = false;
175232
}
176233

packages/occ-worker/src/actions.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import {
33
IJCadContent,
44
IJCadObject,
55
IPostOperatorInput,
6+
JCadWorkerSupportedFormat,
67
WorkerAction
78
} from '@jupytercad/schema';
89

9-
import { ObjectFile, getShapesFactory } from './occapi';
10-
10+
import { getShapesFactory, ObjectFile } from './occapi';
1111
import { OccParser } from './occparser';
1212
import { IOperatorArg, IOperatorFuncOutput } from './types';
1313

@@ -21,7 +21,7 @@ function buildModel(
2121
const { objects } = model;
2222

2323
objects.forEach(object => {
24-
const { shape, parameters } = object;
24+
const { shape, parameters, shapeMetadata } = object;
2525
if (!shape || !parameters) {
2626
return;
2727
}
@@ -33,11 +33,28 @@ function buildModel(
3333
// Creating occ shape from brep file.
3434
const type = parameters['Type'] ?? 'brep';
3535
shapeData = ObjectFile({ content: parameters['Shape'], type }, model);
36-
} else if (shape.startsWith('Post::')) {
37-
shapeData = shapeFactory['Post::Operator']?.(
38-
parameters as IOperatorArg,
39-
model
40-
);
36+
} else if (shape.startsWith('Post::') && shapeMetadata) {
37+
const shapeFormat = (shapeMetadata.shapeFormat ??
38+
JCadWorkerSupportedFormat.BREP) as JCadWorkerSupportedFormat;
39+
40+
switch (shapeFormat) {
41+
case JCadWorkerSupportedFormat.BREP: {
42+
shapeData = shapeFactory['Post::Operator']?.(
43+
parameters as IOperatorArg,
44+
model
45+
);
46+
break;
47+
}
48+
case JCadWorkerSupportedFormat.GLTF: {
49+
shapeData = {
50+
postShape: ''
51+
};
52+
break;
53+
}
54+
55+
default:
56+
break;
57+
}
4158
}
4259
if (shapeData) {
4360
outputModel.push({ shapeData, jcObject: object });
@@ -57,10 +74,11 @@ function loadFile(payload: { content: IJCadContent }): IDict | null {
5774
if (item.jcObject.shape?.startsWith('Post::')) {
5875
postResult[item.jcObject.name] = {
5976
jcObject: item.jcObject,
60-
occBrep: item.shapeData.occBrep
77+
postShape: item.shapeData.postShape
6178
};
6279
}
6380
});
81+
6482
return { result, postResult };
6583
}
6684

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { getShapesFactory } from './common';
77
export function _PostOperator(
88
arg: IPostOperator,
99
content: IJCadContent
10-
): { occBrep: string } {
10+
): { postShape: string } {
1111
const baseObject = content.objects.filter(obj => obj.name === arg.Object);
1212
if (baseObject.length === 0) {
13-
return { occBrep: '' };
13+
return { postShape: '' };
1414
}
1515
const shapesFactory = getShapesFactory();
1616
const baseShape = baseObject[0].shape;
@@ -20,9 +20,9 @@ export function _PostOperator(
2020
content
2121
);
2222
if (base?.occShape) {
23-
const occBrep = _writeBrep(base.occShape);
24-
return { occBrep };
23+
const postShape = _writeBrep(base.occShape);
24+
return { postShape };
2525
}
2626
}
27-
return { occBrep: '' };
27+
return { postShape: '' };
2828
}

packages/occ-worker/src/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@ import { OCC } from '@jupytercad/opencascade';
22
import {
33
IAny,
44
IBox,
5+
IChamfer,
56
ICone,
67
ICut,
78
ICylinder,
89
IExtrusion,
10+
IFillet,
911
IFuse,
1012
IIntersection,
1113
IJCadContent,
14+
IPostOperator,
1215
IShapeMetadata,
1316
ISketchObject,
1417
ISphere,
1518
ITorus,
16-
IPostOperator,
17-
WorkerAction,
1819
IWorkerMessageBase,
19-
IChamfer,
20-
IFillet
20+
WorkerAction
2121
} from '@jupytercad/schema';
2222

2323
export interface IDict<T = any> {
@@ -45,7 +45,7 @@ export type IWorkerMessage = ILoadFile | IRegister;
4545
export interface IOperatorFuncOutput {
4646
occShape?: OCC.TopoDS_Shape;
4747
metadata?: IShapeMetadata | undefined;
48-
occBrep?: string;
48+
postShape?: string | ArrayBuffer;
4949
}
5050

5151
type IOperatorFunc<T> = (

packages/schema/src/interfaces.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export interface IParsedShape {
215215

216216
export interface IPostOperatorInput {
217217
jcObject: IJCadObject;
218-
occBrep?: string;
218+
postShape?: string | ArrayBuffer;
219219
}
220220

221221
/**
@@ -278,8 +278,13 @@ export type IMessageHandler =
278278
| ((msg: IMainMessageBase) => void)
279279
| ((msg: IMainMessageBase) => Promise<void>);
280280

281+
export enum JCadWorkerSupportedFormat {
282+
BREP = 'BREP',
283+
GLTF = 'GLTF'
284+
}
281285
export interface IJCadWorker {
282286
ready: Promise<void>;
287+
shapeFormat?: JCadWorkerSupportedFormat;
283288
postMessage(msg: IWorkerMessageBase): void;
284289
register(options: { messageHandler: IMessageHandler; thisArg?: any }): string;
285290
unregister(id: string): void;

packages/schema/src/schema/jcad.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,14 @@
5252
"shapeMetadata": {
5353
"title": "IShapeMetadata",
5454
"type": "object",
55-
"required": ["mass", "centerOfMass", "matrixOfInertia"],
5655
"additionalProperties": false,
5756
"properties": {
57+
"shapeFormat": {
58+
"type": "string"
59+
},
60+
"workerId": {
61+
"type": "string"
62+
},
5863
"mass": {
5964
"type": "number"
6065
},

0 commit comments

Comments
 (0)