Skip to content

Commit

Permalink
use web worker to load opencascade WASM an parse breps
Browse files Browse the repository at this point in the history
  • Loading branch information
ghackenberg committed Oct 9, 2024
1 parent 993e4c3 commit 1b76dcc
Show file tree
Hide file tree
Showing 14 changed files with 217 additions and 41 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"packages/database",
"packages/broker",
"packages/backend",
"packages/worker",
"packages/frontend",
"packages/gateway"
],
Expand Down
41 changes: 2 additions & 39 deletions packages/freecad/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import { BlobReader, TextWriter, Uint8ArrayWriter, ZipReader } from '@zip.js/zip.js'
import initOpenCascade, { OpenCascadeInstance } from 'opencascade.js'
import { Color, Group, Mesh, MeshStandardMaterial, Object3D } from 'three'
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

import { BRep, parseBRep } from 'productboard-brep'

const GLTF = new GLTFLoader()

let OCCT: Promise<OpenCascadeInstance>

function getOCCT() {
if (!OCCT) {
OCCT = initOpenCascade()
}
return OCCT
}

export class FreeCADDocument {
public label: string
public objects: {[name: string]: FreeCADObject} = {}
Expand Down Expand Up @@ -69,7 +59,7 @@ function traverse(object: Object3D, material: MeshStandardMaterial) {
}
}

export async function parseFCStdModel(data: ReadableStream | BlobReader) {
export async function parseFCStdModel(data: ReadableStream | BlobReader, brep2Glb: (content: string) => Promise<Uint8Array>) {
const diffuse: {[name: string]: MeshStandardMaterial[]} = {}
const breps: {[name: string]: BRep} = {}
const gltfs: {[name: string]: GLTF} = {}
Expand Down Expand Up @@ -106,34 +96,7 @@ export async function parseFCStdModel(data: ReadableStream | BlobReader) {
const content = await entry.getData(writer)
//console.log('Parsing', entry.filename)
breps[entry.filename] = parseBRep(content)
const occt = await getOCCT()
// Parse shape
//console.log('Reading BRep', entry.filename)
const shape = new occt.TopoDS_Shape()
occt.FS.createDataFile('.', entry.filename, content, true, true, true)
const builder = new occt.BRep_Builder()
const readProgress = new occt.Message_ProgressRange_1()
occt.BRepTools.Read_2(shape, `./${entry.filename}`, builder, readProgress)
occt.FS.unlink(`./${entry.filename}`)
// Visualize shape
//console.log('Meshing BRep', entry.filename)
const storageformat = new occt.TCollection_ExtendedString_1()
const doc = new occt.TDocStd_Document(storageformat)
const shapeTool = occt.XCAFDoc_DocumentTool.ShapeTool(doc.Main()).get()
shapeTool.SetShape(shapeTool.NewShape(), shape)
new occt.BRepMesh_IncrementalMesh_2(shape, 0.1, false, 0.1, false)
// Export a GLB file (this will also perform the meshing)
//console.log('Writing GLB', entry.filename)
const glbFileName = new occt.TCollection_AsciiString_2(`./${entry.filename}.glb`)
const cafWriter = new occt.RWGltf_CafWriter(glbFileName, true)
const docHandle = new occt.Handle_TDocStd_Document_2(doc)
const fileInfo = new occt.TColStd_IndexedDataMapOfStringString_1()
const writeProgress = new occt.Message_ProgressRange_1()
cafWriter.Perform_2(docHandle, fileInfo, writeProgress)
// Read the GLB file from the virtual file system
//console.log('Readling GLB', entry.filename)
const glbFileData = occt.FS.readFile(`./${entry.filename}.glb`, { encoding: "binary" })
occt.FS.unlink(`./${entry.filename}.glb`)
const glbFileData = await brep2Glb(content)
gltfs[entry.filename] = await new Promise<GLTF>((resolve, reject) => {
GLTF.parse(glbFileData.buffer, undefined, resolve, reject)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useProduct, useVersion } from '../../hooks/entity'
import { useMembers, useVersions } from '../../hooks/list'
import { render } from '../../functions/render'
import { useAsyncHistory } from '../../hooks/history'
import { parseBRep } from '../../loaders/brep'
import { parseDAEModel } from '../../loaders/dae'
import { parseFBXModel } from '../../loaders/fbx'
import { parseGLTFModel } from '../../loaders/gltf'
Expand Down Expand Up @@ -131,7 +132,7 @@ export const ProductVersionSettingView = () => {
let exec = true
if (stream) {
if (file.name.endsWith('.FCStd')) {
parseFCStdModel(stream).then(group => exec && setGroup(group))
parseFCStdModel(stream, parseBRep).then(group => exec && setGroup(group))
}
}
return () => { exec = false }
Expand Down
35 changes: 35 additions & 0 deletions packages/frontend/src/scripts/loaders/brep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { worker } from "../worker"

export function parseBRep(content: string): Promise<Uint8Array> {
return new Promise<Uint8Array>((resolve, reject) => {
// Define handlers
function handleMessage(message: MessageEvent) {
worker.removeEventListener('message', handleMessage)
worker.removeEventListener('messageerror', handleMessageError)
worker.removeEventListener('error', handleError)
if (message.data instanceof Uint8Array) {
resolve(message.data)
} else {
reject('Return message data type unexpected: ' + message.data)
}
}
function handleMessageError(message: MessageEvent) {
worker.removeEventListener('message', handleMessage)
worker.removeEventListener('messageerror', handleMessageError)
worker.removeEventListener('error', handleError)
reject(message)
}
function handleError(error: ErrorEvent) {
worker.removeEventListener('message', handleMessage)
worker.removeEventListener('messageerror', handleMessageError)
worker.removeEventListener('error', handleError)
reject(error)
}
// Add handlers
worker.addEventListener('message', handleMessage)
worker.addEventListener('messageerror', handleMessageError)
worker.addEventListener('error', handleError)
// Post message
worker.postMessage(content)
})
}
3 changes: 2 additions & 1 deletion packages/frontend/src/scripts/loaders/fcstd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { BlobReader } from '@zip.js/zip.js'

import { parseFCStdModel } from 'productboard-freecad'

import { parseBRep } from './brep'
import { CacheAPI } from '../clients/cache'

export async function loadFCStdModel(path: string) {
const file = await CacheAPI.loadFile(path)
return parseFCStdModel(new BlobReader(new Blob([file])))
return parseFCStdModel(new BlobReader(new Blob([file])), parseBRep)
}
1 change: 1 addition & 0 deletions packages/frontend/src/scripts/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import './clients/mqtt'
import { PageHeaderBoot } from './components/snippets/PageHeaderBoot'
import { LoadingView } from './components/views/Loading'
import './plausible'
import './worker'

import AppIcon from '/src/images/app.png'

Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/scripts/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const worker = new Worker('/scripts/worker/main.js')
5 changes: 5 additions & 0 deletions packages/worker/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"plugins": [
"regex"
]
}
53 changes: 53 additions & 0 deletions packages/worker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "productboard-worker",
"version": "0.0.1",
"description": "Background worker",
"license": "UNLICENSED",
"private": "true",
"type": "module",
"author": {
"name": "Georg Hackenberg",
"email": "[email protected]"
},
"contributors": [
{
"name": "Georg Hackenberg",
"email": "[email protected]"
},
{
"name": "Christian Zehetner",
"email": "[email protected]"
},
{
"name": "Jürgen Humenberger",
"email": "[email protected]"
},
{
"name": "Dominik Frühwirth",
"email": "[email protected]"
}
],
"repository": {
"type": "git",
"url": "https://github.com/ghackenberg/caddrive.git",
"directory": "packages/worker"
},
"scripts": {
"clean": "rm -rf public",
"build": "webpack --config webpack.prod.js",
"lint": "eslint src/scripts",
"loc": "sloc src/scripts",
"dev": "webpack serve --config webpack.dev.js",
"start": "http-server -p 3005 -s"
},
"dependencies": {
"productboard-freecad": "^0.0.1"
},
"devDependencies": {
"ts-loader": "^9.4.1",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.8.1",
"webpack-merge": "^5.8.0"
}
}
51 changes: 51 additions & 0 deletions packages/worker/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import initOpenCascade from 'opencascade.js'

const OCCT = initOpenCascade()

OCCT.then(() => console.log('OpenCascade initialized!'))

addEventListener('message', async message => {
// Check message data type
if (typeof message.data != 'string') {
throw 'Call message data type unexpected: ' + typeof message.data
}
// Cast message data
const content = message.data as string

// Wait for OCCT to load
const occt = await OCCT

// Parse shape
//console.log('Reading BRep')
const shape = new occt.TopoDS_Shape()
occt.FS.createDataFile('.', 'part.brp', content, true, true, true)
const builder = new occt.BRep_Builder()
const readProgress = new occt.Message_ProgressRange_1()
occt.BRepTools.Read_2(shape, './part.brp', builder, readProgress)
occt.FS.unlink('./part.brp')

// Visualize shape
//console.log('Meshing BRep')
const storageformat = new occt.TCollection_ExtendedString_1()
const doc = new occt.TDocStd_Document(storageformat)
const shapeTool = occt.XCAFDoc_DocumentTool.ShapeTool(doc.Main()).get()
shapeTool.SetShape(shapeTool.NewShape(), shape)
new occt.BRepMesh_IncrementalMesh_2(shape, 0.1, false, 0.1, false)

// Export a GLB file (this will also perform the meshing)
//console.log('Writing GLB')
const glbFileName = new occt.TCollection_AsciiString_2('./part.glb')
const cafWriter = new occt.RWGltf_CafWriter(glbFileName, true)
const docHandle = new occt.Handle_TDocStd_Document_2(doc)
const fileInfo = new occt.TColStd_IndexedDataMapOfStringString_1()
const writeProgress = new occt.Message_ProgressRange_1()
cafWriter.Perform_2(docHandle, fileInfo, writeProgress)

// Read the GLB file from the virtual file system
//console.log('Readling GLB')
const glbFileData = occt.FS.readFile('./part.glb', { encoding: "binary" })
occt.FS.unlink('./part.glb')

// Returning GLB data
postMessage(glbFileData)
})
9 changes: 9 additions & 0 deletions packages/worker/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2017",
"rootDirs": ["src", "../brep/src", "../freecad/src"],
"outDir": "bin",
"lib": ["ES2017", "WebWorker"]
}
}
33 changes: 33 additions & 0 deletions packages/worker/webpack.common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { resolve } from 'path'

import 'webpack'

export default {
stats: 'minimal',
entry: './src/main.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},{
test: /\.(wasm)$/i,
type: 'asset/resource',
generator: {
filename: 'modules/[hash][ext][query]'
}
}
],
},
resolve: {
alias: {
'three': resolve('../../node_modules/three')
},
extensions: ['.ts', '.js'],
},
output: {
path: resolve('public'),
filename: 'scripts/worker/[name].js'
}
}
14 changes: 14 additions & 0 deletions packages/worker/webpack.dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { resolve } from 'path'

import { merge } from 'webpack-merge'

import common from './webpack.common.js'

export default merge(common, {
mode: 'development',
devServer: {
static: resolve('public'),
port: 3005
},
devtool: 'inline-source-map'
})
8 changes: 8 additions & 0 deletions packages/worker/webpack.prod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { merge } from 'webpack-merge'

import common from './webpack.common.js'

export default merge(common, {
mode: 'production',
devtool: 'source-map'
})

0 comments on commit 1b76dcc

Please sign in to comment.