jsketcher/web/app/cad/craft/e0/e0Plugin.js

324 lines
9 KiB
JavaScript

/**
* 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 shellsToPointers(shells) {
return shells.filter(managedByE0).map(m => m.brepShell.data.externals.ptr);
}
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))
}
}
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)]
}
}
}
}
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]);
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 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);
}