From 9af681b21f33d0a319ab15aee03ee0495922234c Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Tue, 15 Jan 2019 19:18:28 -0800 Subject: [PATCH] separate loft preview from actual craft / engine layer refactoring --- web/app/cad/craft/e0/common.js | 95 +++++++ web/app/cad/craft/e0/craftMethods.js | 147 +++++++++++ web/app/cad/craft/e0/e0Plugin.js | 302 +---------------------- web/app/cad/craft/e0/interact.js | 23 ++ web/app/cad/craft/e0/operationHandler.js | 87 +++++++ web/app/cad/craft/loft/loftPreviewer.js | 2 +- 6 files changed, 356 insertions(+), 300 deletions(-) create mode 100644 web/app/cad/craft/e0/common.js create mode 100644 web/app/cad/craft/e0/craftMethods.js create mode 100644 web/app/cad/craft/e0/interact.js create mode 100644 web/app/cad/craft/e0/operationHandler.js diff --git a/web/app/cad/craft/e0/common.js b/web/app/cad/craft/e0/common.js new file mode 100644 index 00000000..d440af6e --- /dev/null +++ b/web/app/cad/craft/e0/common.js @@ -0,0 +1,95 @@ +export const BOOLEAN_TYPES = { + UNION : 1, + SUBTRACT: 2, + INTERSECT: 3 +}; + +export const CURVE_TYPES = { + SEGMENT: 1, + B_SPLINE: 2, + CIRCLE: 3, + ARC: 4 +}; + +export const DEFLECTION = 2; +export const E0_TOLERANCE = 1e-3; + +export 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] + }; +} + +export 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; +} + +export function managedByE0(mShell) { + let externals = mShell.brepShell && mShell.brepShell.data && mShell.brepShell.data.externals; + return externals && externals.engine === 'e0'; +} + +export function readSketchContour(contour, face) { + let tr = face.csys.outTransformation; + let path = []; + contour.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; + let tangent = tr._apply(s.c.minus(a))._cross(face.csys.z)._normalize(); + if (s.inverted) { + tangent._negate(); + } + path.push({ + TYPE: CURVE_TYPES.ARC, + a: tr.apply(a).data(), + b: tr.apply(b).data(), + tangent: tangent.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; +} + +export function readSketch(face, request, sketcher) { + let sketch = sketcher.readSketch(face.id); + if (!sketch) throw 'illegal state'; + return sketch.fetchContours().map(c => readSketchContour(c, face)); +} + +export function shellsToPointers(shells) { + return shells.filter(managedByE0).map(m => m.brepShell.data.externals.ptr); +} + +export function writeCsys(csys, swapToY) { + return { + origin: csys.origin.data(), + normal: (swapToY ? csys.y : csys.z).data(), + xDir: csys.x.data() + }; +} diff --git a/web/app/cad/craft/e0/craftMethods.js b/web/app/cad/craft/e0/craftMethods.js new file mode 100644 index 00000000..73b02608 --- /dev/null +++ b/web/app/cad/craft/e0/craftMethods.js @@ -0,0 +1,147 @@ +import { + BOOLEAN_TYPES, DEFLECTION, E0_TOLERANCE, managedByE0, readShellData, readSketch, readSketchContour, shellsToPointers, + singleShellRespone, + writeCsys +} from './common'; +import {callEngine} from './interact'; +import {resolveExtrudeVector} from '../cutExtrude/cutExtrude'; + +export function boolean({type, operandsA, operandsB}) { + let engineParams = { + type: BOOLEAN_TYPES[type], + operandsA: shellsToPointers(operandsA), + operandsB: shellsToPointers(operandsB), + tolerance: E0_TOLERANCE, + deflection: DEFLECTION, + }; + let data = callEngine(engineParams, Module._SPI_boolean); + let consumed = [...operandsA, ...operandsB]; + return { + consumed, + created: [readShellData(data.result, consumed, operandsA[0].csys)] + } +} + +export function createBox(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys), + dx: params.width, + dy: params.height, + dz: params.depth + }, params, Module._SPI_box); +} + +export function createTorus(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys), + r1: params.radius, + r2: params.tube + }, params, Module._SPI_torus); +} + +export function createCone(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys, true), + r1: params.radius, + r2: params.frustum, + h: params.height + }, params, Module._SPI_cone); +} + +export function createCylinder(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys, true), + r: params.radius, + h: params.height, + }, params, Module._SPI_cylinder); +} + +export function createSphere(params) { + return booleanBasedOperation({ + csys: writeCsys(params.csys), + r: params.radius, + }, params, Module._SPI_sphere); +} + +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: shellsToPointers(params.boolean.operands), + tolerance: E0_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, consumed, params.csys)) + } +} + +function cutExtrude(isCut, request) { + + function createExtrudeCommand(request, {cadRegistry, sketcher}, invert) { + const face = cadRegistry.findFace(request.face); + const paths = readSketch(face, request, sketcher); + + return { + face, + request: { + vector: resolveExtrudeVector(cadRegistry, face, request, !invert).data(), + sketch: paths, + tolerance: E0_TOLERANCE, + deflection: DEFLECTION + } + }; + } + + 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); +} + +export function cut(params) { + return cutExtrude(true, params); +} + +export function extrude(params) { + return cutExtrude(false, params); +} + +const mapLoftParams = params => ({ + sections: params.sections.map(sec => readSketchContour(sec.contour, sec.face)), + tolerance: E0_TOLERANCE, + deflection: DEFLECTION +}); + +export function loftPreview(params) { + return callEngine(mapLoftParams(params), Module._SPI_loftPreview); +} + +export function loft(params) { + let result = callEngine(mapLoftParams(params), Module._SPI_loftPreview); + throw 'unsupported'; + // let consumed = [...operandsA, ...operandsB]; + // return { + // consumed, + // created: [readShellData(data.result, consumed, operandsA[0].csys)] + // } +} diff --git a/web/app/cad/craft/e0/e0Plugin.js b/web/app/cad/craft/e0/e0Plugin.js index f83e86de..bba07171 100644 --- a/web/app/cad/craft/e0/e0Plugin.js +++ b/web/app/cad/craft/e0/e0Plugin.js @@ -1,315 +1,19 @@ /** * This is an internal alternative to native engine. It overrides basic 3d part design operations */ -import {resolveExtrudeVector} from '../cutExtrude/cutExtrude'; - -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; +import * as craftMethods from './craftMethods'; +import operationHandler from './operationHandler'; 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: shellsToPointers(params.boolean.operands), - 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, consumed, params.csys)) - } - } 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); - }, - boolean: function({type, operandsA, operandsB}) { - let engineParams = { - type: BOOLEAN_TYPES[type], - operandsA: shellsToPointers(operandsA), - operandsB: shellsToPointers(operandsB), - tolerance: TOLERANCE, - deflection: DEFLECTION, - }; - let data = callEngine(engineParams, Module._SPI_boolean); - let consumed = [...operandsA, ...operandsB]; - return { - consumed, - created: [readShellData(data.result, consumed, operandsA[0].csys)] - } - }, - loft: function(params) { - let engineParams = { - sections: params.sections.map(sec => readSketchContour(sec.contour, sec.face)), - preview: params.preview, - tolerance: TOLERANCE, - deflection: DEFLECTION, - }; - let data = callEngine(engineParams, Module._SPI_loft); - if (params.preview) { - return data; - } - throw 'unsupported'; - // let consumed = [...operandsA, ...operandsB]; - // return { - // consumed, - // created: [readShellData(data.result, consumed, operandsA[0].csys)] - // } - } + ...craftMethods } } -function shellsToPointers(shells) { - return shells.filter(managedByE0).map(m => m.brepShell.data.externals.ptr); -} - -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); - let data = callEngine(engineReq, Module._SPI_revolve); - return singleShellRespone(face.shell, data); - } - case 'FILLET': { - let edge = services.cadRegistry.findEdge(request.edges[0]); - let engineReq = { - deflection: DEFLECTION, - solid: edge.shell.brepShell.data.externals.ptr, - edges: request.edges.map(e => ({ - edge: services.cadRegistry.findEdge(e).brepEdge.data.externals.ptr, - thickness: request.thickness - })) - }; - - 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 readSketchContour(contour, face) { - let tr = face.csys.outTransformation; - let path = []; - contour.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; - let tangent = tr._apply(s.c.minus(a))._cross(face.csys.z)._normalize(); - if (s.inverted) { - tangent._negate(); - } - path.push({ - TYPE: CURVE_TYPES.ARC, - a: tr.apply(a).data(), - b: tr.apply(b).data(), - tangent: tangent.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; -} - -function readSketch(face, request, sketcher) { - let sketch = sketcher.readSketch(face.id); - if (!sketch) throw 'illegal state'; - return sketch.fetchContours().map(c => readSketchContour(c, face)); -} - -function createExtrudeCommand(request, {cadRegistry, sketcher}, invert) { - const face = cadRegistry.findFace(request.face); - const paths = readSketch(face, request, sketcher); - - return { - face, - request: { - vector: resolveExtrudeVector(cadRegistry, face, request, !invert).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)) - - let res = { - face, - request: { - axisOrigin, - axisDir, - angle: request.angle / 180.0 * Math.PI, - sketch: paths, - tolerance: TOLERANCE, - deflection: DEFLECTION - } - }; - if (managedByE0(face.shell) && request.boolean && BOOLEAN_TYPES[request.boolean] > 0) { - res.request.boolean = { - type: BOOLEAN_TYPES[request.boolean], - operand: face.shell.brepShell.data.externals.ptr - } - } - return res; -} - - -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 => { diff --git a/web/app/cad/craft/e0/interact.js b/web/app/cad/craft/e0/interact.js new file mode 100644 index 00000000..059ea675 --- /dev/null +++ b/web/app/cad/craft/e0/interact.js @@ -0,0 +1,23 @@ + +export function toCString(str) { + let buffer = Module._malloc(str.length + 1); + writeAsciiToMemory(str, buffer); + return buffer; +} + +export 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); +}; diff --git a/web/app/cad/craft/e0/operationHandler.js b/web/app/cad/craft/e0/operationHandler.js new file mode 100644 index 00000000..75e3fd8f --- /dev/null +++ b/web/app/cad/craft/e0/operationHandler.js @@ -0,0 +1,87 @@ +import {BOOLEAN_TYPES, DEFLECTION, E0_TOLERANCE, managedByE0, readSketch, singleShellRespone} from './common'; +import {callEngine} from './interact'; +import {resolveExtrudeVector} from '../cutExtrude/cutExtrude'; + +export default 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); + let data = callEngine(engineReq, Module._SPI_revolve); + return singleShellRespone(face.shell, data); + } + case 'FILLET': { + let edge = services.cadRegistry.findEdge(request.edges[0]); + let engineReq = { + deflection: DEFLECTION, + solid: edge.shell.brepShell.data.externals.ptr, + edges: request.edges.map(e => ({ + edge: services.cadRegistry.findEdge(e).brepEdge.data.externals.ptr, + thickness: request.thickness + })) + }; + + let data = callEngine(engineReq, Module._SPI_fillet); + return singleShellRespone(edge.shell, data); + } + } +} + +function createExtrudeCommand(request, {cadRegistry, sketcher}, invert) { + const face = cadRegistry.findFace(request.face); + const paths = readSketch(face, request, sketcher); + + return { + face, + request: { + vector: resolveExtrudeVector(cadRegistry, face, request, !invert).data(), + sketch: paths, + tolerance: E0_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)) + + let res = { + face, + request: { + axisOrigin, + axisDir, + angle: request.angle / 180.0 * Math.PI, + sketch: paths, + tolerance: E0_TOLERANCE, + deflection: DEFLECTION + } + }; + if (managedByE0(face.shell) && request.boolean && BOOLEAN_TYPES[request.boolean] > 0) { + res.request.boolean = { + type: BOOLEAN_TYPES[request.boolean], + operand: face.shell.brepShell.data.externals.ptr + } + } + return res; +} diff --git a/web/app/cad/craft/loft/loftPreviewer.js b/web/app/cad/craft/loft/loftPreviewer.js index 7652d303..0a04317b 100644 --- a/web/app/cad/craft/loft/loftPreviewer.js +++ b/web/app/cad/craft/loft/loftPreviewer.js @@ -2,7 +2,7 @@ import {createSmoothMeshGeometryFromData} from '../../../../../modules/scene/geo export function loftPreviewGeomProvider(params, services) { - const tessInfo = services.craftEngine.loft({ + const tessInfo = services.craftEngine.loftPreview({ sections: params.sections.map(services.cadRegistry.findLoop), preview: true });