diff --git a/web/app/cad/craft/e0/e0Plugin.js b/web/app/cad/craft/e0/e0Plugin.js new file mode 100644 index 00000000..6257a450 --- /dev/null +++ b/web/app/cad/craft/e0/e0Plugin.js @@ -0,0 +1,304 @@ +/** + * This is an internal alternative to native engine. It overrides basic 3d part design operations + */ + +let BOOLEAN_TYPES = { + UNION : 1, + SUBTRACT: 2, + INTERSECT: 3 +}; + +let CURVE_TYPES = { + SEGMENT: 1, + B_SPLINE: 2, + CIRCLE: 3, + ARC: 4 +}; + +const DEFLECTION = 2; +const TOLERANCE = 1e-3; + +export function activate(ctx) { + + loadWasm(ctx); + + ctx.services.operation.handlers.push(operationHandler); + function booleanBasedOperation(engineParams, params, impl) { + engineParams.deflection = DEFLECTION; + if (params.boolean && BOOLEAN_TYPES[params.boolean.type] > 0) { + engineParams.boolean = { + type: BOOLEAN_TYPES[params.boolean.type], + operands: params.boolean.operands.filter(managedByE0).map(m => m.brepShell.data.externals.ptr), + tolerance: TOLERANCE, + } + } + let data = callEngine(engineParams, impl); + let consumed = []; + if (params.boolean) { + data.consumed.forEach(ptr => { + let model = params.boolean.operands.find(m => managedByE0(m) && m.brepShell.data.externals.ptr === ptr); + if (model) { + consumed.push(model); + } + }); + } + return { + consumed, + created: data.created.map(shape => readShellData(shape)) + } + } + ctx.services.craftEngine = { + createBox: function(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys), + dx: params.width, + dy: params.height, + dz: params.depth + }, params, Module._SPI_box); + }, + createTorus: function(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys), + r1: params.radius, + r2: params.tube + }, params, Module._SPI_torus); + }, + createCone: function(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys, true), + r1: params.radius, + r2: params.frustum, + h: params.height + }, params, Module._SPI_cone); + }, + createCylinder: function(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys, true), + r: params.radius, + h: params.height, + }, params, Module._SPI_cylinder); + }, + createSphere: function(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys), + r: params.radius, + }, params, Module._SPI_sphere); + } + } +} + +function writeCsys(csys, swapToY) { + return { + origin: csys.origin.data(), + normal: (swapToY ? csys.y : csys.z).data(), + xDir: csys.x.data() + }; +} + +function operationHandler(id, request, services) { + switch (id) { + case 'CUT': + case 'EXTRUDE': { + + let isCut = id === 'CUT'; + let {request: engineReq, face} = createExtrudeCommand(request, services, isCut); + if (managedByE0(face.shell)) { + engineReq.boolean = { + type: isCut ? BOOLEAN_TYPES.SUBTRACT : BOOLEAN_TYPES.UNION, + operand: face.shell.brepShell.data.externals.ptr + } + } + + let data = callEngine(engineReq, Module._SPI_extrude); + + return singleShellRespone(face.shell, data); + } + case 'REVOLVE': { + let {request: engineReq, face} = createRevolveCommand(request, services); + if (managedByE0(face.shell)) { + engineReq.boolean = { + type: request.subtract ? BOOLEAN_TYPES.SUBTRACT : BOOLEAN_TYPES.UNION, + operand: face.shell.brepShell.data.externals.ptr + } + } + let data = callEngine(engineReq, Module._SPI_revolve); + return singleShellRespone(face.shell, data); + } + case 'FILLET': { + let edge = services.cadRegistry.findEdge(request.edges[0].edge); + + let engineReq = Object.assign({}, request, { + deflection: DEFLECTION, + solid: edge.shell.brepShell.data.externals.ptr, + edges: request.edges.map(e => Object.assign({}, e, {edge: services.cadRegistry.findEdge(e.edge).brepEdge.data.externals.ptr})) + }); + + let data = callEngine(engineReq, Module._SPI_fillet); + return singleShellRespone(edge.shell, data); + } + } +} + +function singleShellRespone(oldShell, newShellData) { + if (newShellData.error) { + throw 'operation failed'; + } + + let consumed = [oldShell]; + let created = readShellData(newShellData, consumed, oldShell.csys); + return { + consumed: consumed, + created: [created] + }; +} + +function readShellData(data, consumed, csys) { + let tpi = __CAD_APP.services.tpi; + let model = new tpi.scene.readShellEntityFromJson(data, consumed, csys); + model.brepShell.data.externals.engine = 'e0'; + return model; +} + +function managedByE0(mShell) { + let externals = mShell.brepShell && mShell.brepShell.data && mShell.brepShell.data.externals; + return externals && externals.engine === 'e0'; +} + +function readSketch(face, request, sketcher) { + let sketch = sketcher.readSketch(face.id); + if (!sketch) throw 'illegal state'; + + let tr = face.csys.outTransformation; + let paths = sketch.fetchContours().map(c => { + let path = []; + c.segments.forEach(s => { + if (s.isCurve()) { + if (s.constructor.name === 'Circle') { + const dir = face.csys.z.data(); + path.push({TYPE: CURVE_TYPES.CIRCLE, c: tr.apply(s.c).data(), dir, r: s.r}); + } else if (s.constructor.name === 'Arc') { + let a = s.inverted ? s.b : s.a; + let b = s.inverted ? s.a : s.b; + path.push({ + TYPE: CURVE_TYPES.ARC, + a: tr.apply(a).data(), + b: tr.apply(b).data(), + tangent: tr._apply(a.minus(s.c))._cross(face.csys.z)._normalize()._negate().data() + }); + } else { + let nurbs = s.toNurbs(face.csys).impl; + path.push(Object.assign({TYPE: CURVE_TYPES.B_SPLINE}, nurbs.serialize())); + } + } else { + let ab = [s.a, s.b]; + if (s.inverted) { + ab.reverse(); + } + ab = ab.map(v => tr.apply(v).data()); + path.push({TYPE: CURVE_TYPES.SEGMENT, a: ab[0], b: ab[1]}); + } + }); + return path; + }); + return paths; +} + +function createExtrudeCommand(request, {cadRegistry, sketcher}, invert) { + const face = cadRegistry.findFace(request.face); + const paths = readSketch(face, request, sketcher); + + let val = request.value; + if (invert) { + val *= -1; + } + return { + face, + request: { + vector: face.csys.z.multiply(val).data(), + sketch: paths, + tolerance: TOLERANCE, + deflection: DEFLECTION + } + }; +} + +function createRevolveCommand(request, {cadRegistry, sketcher}) { + const face = cadRegistry.findFace(request.face); + const paths = readSketch(face, request, sketcher); + + let pivot = cadRegistry.findSketchObject(request.axis).sketchPrimitive; + let tr = face.csys.outTransformation; + let vec = __CAD_APP.services.tpi.math.vec; + let axisOrigin = tr._apply3(pivot.a.data()); + let axisDir = vec._normalize(vec._sub(tr._apply3(pivot.b.data()), axisOrigin)) + + return { + face, + request: { + axisOrigin, + axisDir, + angle: request.angle / 180.0 * Math.PI, + sketch: paths, + tolerance: TOLERANCE, + deflection: DEFLECTION + } + }; +} + + +function toCString(str) { + let buffer = Module._malloc(str.length + 1); + writeAsciiToMemory(str, buffer); + return buffer; +} + +function callEngine(request, engineFunc) { + let toCStringRequest = toCString(JSON.stringify(request)); + engineFunc(toCStringRequest); + Module._free(toCStringRequest); + return __E0_ENGINE_EXCHANGE_VAL; +} + + +let __E0_ENGINE_EXCHANGE_VAL = null; +window.__E0_ENGINE_EXCHANGE = function(objStr) { + __E0_ENGINE_EXCHANGE_VAL = JSON.parse(objStr); + // let tpi = __CAD_APP.services.tpi; + // let sceneObject = new tpi.scene.UnmanagedSceneSolid(data, 'SOLID'); + // tpi.addOnScene(sceneObject); + // __DEBUG__.AddTessDump(obj); +}; + +function instantiateEngine(importObject, callback) { + const url = '/wasm/e0/main.wasm'; + WebAssembly.instantiateStreaming(fetch(url), importObject).then(results => { + callback(results.instance); + }); +} + +function loadWasm(ctx) { + ctx.services.lifecycle.startAsyncInitializingJob('e0:loader'); + + window.Module = { + // locateFile: function(file) { + // return SERVER_PATH + file; + // }, + onRuntimeInitialized: function() { + ctx.services.lifecycle.finishAsyncInitializingJob('e0:loader'); + }, + instantiateWasm: function (importObject, fncReceiveInstance) { + instantiateEngine(importObject, fncReceiveInstance); + return {}; + } + }; + + let mainScript = document.createElement('script'); + mainScript.setAttribute('src', '/wasm/e0/main.js'); + mainScript.setAttribute('async', 'async'); + document.head.appendChild(mainScript); +} + + + + + diff --git a/web/app/cad/init/lifecyclePlugin.js b/web/app/cad/init/lifecyclePlugin.js index 8086a8cf..64798c08 100644 --- a/web/app/cad/init/lifecyclePlugin.js +++ b/web/app/cad/init/lifecyclePlugin.js @@ -1,17 +1,27 @@ import {state} from '../../../../modules/lstream'; -import context from '../../../../modules/context'; export function activate({streams, services}) { + const asyncInitializingJobs = new Set(); const startTime = performance.now(); streams.lifecycle = { appReady: state(false), projectLoaded: state(false) }; services.lifecycle = { + startAsyncInitializingJob : job => { + if (!streams.lifecycle.projectLoaded.value) { + asyncInitializingJobs.add(job); + } + }, + finishAsyncInitializingJob : job => { + asyncInitializingJobs.delete(job); + services.lifecycle.loadProjectRequest(); + }, loadProjectRequest: () => { if (streams.lifecycle.appReady.value && !streams.lifecycle.projectLoaded.value && - services.extension.allExtensionsReady()) { + services.extension.allExtensionsReady() && + asyncInitializingJobs.size === 0) { services.extension.activateAllExtensions(); services.project.load(); diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index a7755dc5..fb564c4c 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -22,6 +22,7 @@ import * as SketcherPlugin from '../sketch/sketcherPlugin'; import * as ExportPlugin from '../exportPlugin'; import * as TpiPlugin from '../tpi/tpiPlugin'; import * as ViewSyncPlugin from '../scene/viewSyncPlugin'; +import * as E0Plugin from '../craft/e0/e0Plugin'; import PartModellerPlugins from '../part/partModelerPlugins'; @@ -50,7 +51,8 @@ export default function startApplication(callback) { CraftUiPlugin, CadRegistryPlugin, ExportPlugin, - TpiPlugin + TpiPlugin, + E0Plugin ]; let plugins = [