diff --git a/modules/gems/capitalize.js b/modules/gems/capitalize.js index 9ef79f3d..ccce2121 100644 --- a/modules/gems/capitalize.js +++ b/modules/gems/capitalize.js @@ -2,3 +2,8 @@ export default function capitalize(str) { if (!str) return; return str.charAt(0).toUpperCase() + str.slice(1); } + +export function decapitalize(str) { + if (!str) return; + return str.charAt(0).toLowerCase() + str.slice(1); +} diff --git a/web/app/brep/io/brepIO.js b/web/app/brep/io/brepIO.js index 892757bf..0c49cba7 100644 --- a/web/app/brep/io/brepIO.js +++ b/web/app/brep/io/brepIO.js @@ -23,6 +23,7 @@ export function readBrep(data) { format: 'verbose', data: normalizeTesselationData(faceData.tess, inverted, faceData.surface.normal) }; + bb._face.data.productionInfo = faceData.productionInfo; if (faceData.ref !== undefined) { bb._face.data.externals = { ref: faceData.ref diff --git a/web/app/cad/actions/actionSystemPlugin.js b/web/app/cad/actions/actionSystemPlugin.js index dbe148b6..f5ea8b41 100644 --- a/web/app/cad/actions/actionSystemPlugin.js +++ b/web/app/cad/actions/actionSystemPlugin.js @@ -1,5 +1,6 @@ import {enableAnonymousActionHint} from './anonHint'; import * as stream from 'lstream'; +import {DEBUG_FLAGS} from '../debugFlags'; export function activate(context) { @@ -23,6 +24,9 @@ export function activate(context) { return; } if (state.enabled) { + if (DEBUG_FLAGS.ACTION_RUN) { + console.log("RUNNING ACTION: " + id); + } runner(context, data); } else { showAnonymousActionHint(id); diff --git a/web/app/cad/craft/e0/common.js b/web/app/cad/craft/e0/common.js index d440af6e..bdcfc1f4 100644 --- a/web/app/cad/craft/e0/common.js +++ b/web/app/cad/craft/e0/common.js @@ -72,6 +72,7 @@ export function readSketchContour(contour, face) { ab = ab.map(v => tr.apply(v).data()); path.push({TYPE: CURVE_TYPES.SEGMENT, a: ab[0], b: ab[1]}); } + path[path.length - 1].id = s.id; }); return path; } diff --git a/web/app/cad/debugFlags.js b/web/app/cad/debugFlags.js new file mode 100644 index 00000000..fd72a42e --- /dev/null +++ b/web/app/cad/debugFlags.js @@ -0,0 +1,5 @@ +export const DEBUG_FLAGS = { + + ACTION_RUN: false + +}; \ No newline at end of file diff --git a/web/app/cad/debugPlugin.js b/web/app/cad/debugPlugin.js index f2eecf03..7a8663d6 100644 --- a/web/app/cad/debugPlugin.js +++ b/web/app/cad/debugPlugin.js @@ -10,6 +10,7 @@ import {toLoops} from '../brep/io/brepLoopsFormat'; import {contributeComponent} from './dom/components/ContributedComponents'; import BrepDebuggerWindow, {BREP_DEBUG_WINDOW_VISIBLE} from '../brep/debug/debugger/BrepDebuggerWindow'; import curveTess from '../brep/geom/impl/curve/curve-tess'; +import {DEBUG_FLAGS} from './debugFlags'; export function activate({bus, services, streams}) { @@ -17,7 +18,9 @@ export function activate({bus, services, streams}) { addDebugSelectors(services); services.action.registerActions(DebugActions); services.menu.registerMenus([DebugMenuConfig]); - + services.debug = { + FLAGS: DEBUG_FLAGS + }; streams.ui.controlBars.left.update(actions => [...actions, 'menu.debug']); bus.enableState(BREP_DEBUG_WINDOW_VISIBLE, false); diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index e72a0b92..eae4d271 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -71,6 +71,7 @@ export default function startApplication(callback) { ]; let allPlugins = [...preUIPlugins, ...plugins]; + context.services.plugin = createPluginService(allPlugins, context); defineStreams(allPlugins, context); @@ -98,3 +99,16 @@ export function activatePlugins(plugins, context) { } } +function createPluginService(plugins, context) { + function disposePlugins() { + for (let plugin of plugins) { + if (plugin.dispose) { + plugin.dispose(context); + } + } + } + + return { + disposePlugins + }; +} \ No newline at end of file diff --git a/web/app/cad/model/mface.js b/web/app/cad/model/mface.js index 2caa7ca2..52078f84 100644 --- a/web/app/cad/model/mface.js +++ b/web/app/cad/model/mface.js @@ -72,6 +72,10 @@ export class MFace extends MObject { } } + get defaultSketchId() { + return this.id; + } + setSketch(sketch) { if (!this.isPlaneBased) { diff --git a/web/app/cad/projectPlugin.js b/web/app/cad/projectPlugin.js index cad9061d..2334701c 100644 --- a/web/app/cad/projectPlugin.js +++ b/web/app/cad/projectPlugin.js @@ -9,9 +9,9 @@ export function activate(context) { const {streams, services} = context; - const [id, params] = parseHintsFromLocation(); + const [id, hints] = parseHintsFromLocation(); - processParams(params, context); + processParams(hints, context); const sketchNamespace = id + '.sketch.'; const sketchStorageNamespace = STORAGE_PREFIX + sketchNamespace; @@ -40,20 +40,33 @@ export function activate(context) { let dataStr = services.storage.get(services.project.projectStorageKey()); if (dataStr) { let data = JSON.parse(dataStr); - if (data.history) { - services.craft.reset(data.history); - } - if (data.expressions) { - services.expressions.load(data.expressions); - } + loadData(data); } } catch (e) { console.error(e); } } - + + + function loadData(data) { + if (data.history) { + services.craft.reset(data.history); + } + if (data.expressions) { + services.expressions.load(data.expressions); + } + } + + function empty() { + loadData({ + history: [], + expressions: "" + }); + } + services.project = { - id, sketchStorageKey, projectStorageKey, sketchStorageNamespace, getSketchURL, save, load + id, sketchStorageKey, projectStorageKey, sketchStorageNamespace, getSketchURL, save, load, empty, + hints } } diff --git a/web/app/cad/scene/scenePlugin.js b/web/app/cad/scene/scenePlugin.js index 5572b0b9..f5795cc0 100644 --- a/web/app/cad/scene/scenePlugin.js +++ b/web/app/cad/scene/scenePlugin.js @@ -24,3 +24,7 @@ export function activate({streams, services}) { // services.viewer.setCameraMode(CAMERA_MODE.ORTHOGRAPHIC); } + +export function dispose(ctx) { + ctx.services.viewer.dispose(); +} diff --git a/web/app/cad/scene/viewer.js b/web/app/cad/scene/viewer.js index cc4cbd57..ac10e3b4 100644 --- a/web/app/cad/scene/viewer.js +++ b/web/app/cad/scene/viewer.js @@ -58,6 +58,10 @@ export default class Viewer { this.setCameraMode(CAMERA_MODE.PERSPECTIVE); } } + + dispose() { + this.sceneSetup.renderer.dispose(); + } } export const CAMERA_MODE = { diff --git a/web/app/cad/sketch/sketchObjectGlobalId.js b/web/app/cad/sketch/sketchObjectGlobalId.js new file mode 100644 index 00000000..6cd64523 --- /dev/null +++ b/web/app/cad/sketch/sketchObjectGlobalId.js @@ -0,0 +1,3 @@ +export default function globalSketchId(sketchId, id) { + return sketchId + "/" + id; +} \ No newline at end of file diff --git a/web/app/cad/sketch/sketchReader.js b/web/app/cad/sketch/sketchReader.js index 43e6f569..99fa907e 100644 --- a/web/app/cad/sketch/sketchReader.js +++ b/web/app/cad/sketch/sketchReader.js @@ -6,6 +6,7 @@ import * as math from '../../math/math' import {HashTable} from '../../utils/hashmap' import {Constraints} from '../../sketcher/parametric'; import Joints from '../../../../modules/gems/joints'; +import sketchObjectGlobalId from './sketchObjectGlobalId'; class SketchGeom { @@ -43,9 +44,7 @@ class SketchGeom { } export function ReadSketch(sketch, sketchId, readConstructionSegments) { - function getID(obj) { - return sketchId + "/" + obj.id; - } + const getID = obj => sketchObjectGlobalId(sketchId, obj.id); const out = new SketchGeom(); let coiJoints = new Joints(); diff --git a/web/app/cad/sketch/sketcherPlugin.js b/web/app/cad/sketch/sketcherPlugin.js index d96f5af5..508c9c35 100644 --- a/web/app/cad/sketch/sketcherPlugin.js +++ b/web/app/cad/sketch/sketcherPlugin.js @@ -23,8 +23,10 @@ export function activate(ctx) { if (evt.key.indexOf(prefix) < 0) return; let sketchFaceId = evt.key.substring(prefix.length); let sketchFace = services.cadRegistry.findFace(sketchFaceId); - updateSketchForFace(sketchFace); - services.viewer.requestRender(); + if (sketchFace) { + updateSketchForFace(sketchFace); + services.viewer.requestRender(); + } }; services.storage.addListener(onSketchUpdate); @@ -70,7 +72,7 @@ export function activate(ctx) { } function updateSketchForFace(mFace) { - let sketch = readSketch(mFace.id); + let sketch = readSketch(mFace.defaultSketchId); mFace.setSketch(sketch); streams.sketcher.update.next(mFace); } diff --git a/web/app/cad/tpi/tpi.js b/web/app/cad/tpi/tpi.js index c850fc2e..4e7c5b61 100644 --- a/web/app/cad/tpi/tpi.js +++ b/web/app/cad/tpi/tpi.js @@ -38,5 +38,6 @@ export default { }, math: { vec - } + }, + THREE: THREE } \ No newline at end of file diff --git a/web/app/sketcher/viewer2d.js b/web/app/sketcher/viewer2d.js index f903a085..03d5fd1b 100644 --- a/web/app/sketcher/viewer2d.js +++ b/web/app/sketcher/viewer2d.js @@ -85,6 +85,10 @@ Viewer.prototype.dispose = function() { this.toolManager.dispose(); }; +Viewer.prototype.isDisposed = function() { + return this.canvas === null; +}; + Viewer.prototype.setTransformation = function(a, b, c, d, e, f, zoom) { this.transformation = [a, b, c, d, e, f]; this.scale = zoom; @@ -190,9 +194,11 @@ Viewer.prototype._createServiceLayers = function() { }; Viewer.prototype.refresh = function() { - var viewer = this; - window.requestAnimationFrame( function() { - viewer.repaint(); + const viewer = this; + window.requestAnimationFrame(function() { + if (!viewer.isDisposed()) { + viewer.repaint(); + } }); }; diff --git a/web/test/cases/craft.js b/web/test/cases/craft.js deleted file mode 100644 index a7af792d..00000000 --- a/web/test/cases/craft.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as test from '../test'; - -export default { - - testPlanes: env => { - test.modeller(env.testTPI(tpi => { - - tpi.services.action.run('PLANE'); - - console.dir(tpi); - - env.done(); - })); - }, - -}; diff --git a/web/test/cases/craftExtrude.js b/web/test/cases/craftExtrude.js new file mode 100644 index 00000000..9b6cc1da --- /dev/null +++ b/web/test/cases/craftExtrude.js @@ -0,0 +1,40 @@ +import {assertEmpty, assertEquals, assertFaceIsPlane, assertTrue} from '../utils/asserts'; +import sketchObjectGlobalId from '../../app/cad/sketch/sketchObjectGlobalId'; + +export const TEST_MODE = 'modellerUI'; + +export function testExtrudeFromSketch2(env, ui) { + // globalSketchId(sketchId, seg1.id) +} + +export function testExtrudeFromSketch(env, ui) { + ui.openWizard('PLANE'); + ui.wizardOK(); + ui.selectFaces([0, 0, -10], [0, 0, 10]); + let sketchedFace = ui.context.services.selection.face.single; + let sketcherUI = ui.openSketcher(); + let seg1 = sketcherUI.addSegment(-100, -100, 100, -100); + let seg2 = sketcherUI.addSegment(100, -100, 100, 100); + let seg3 = sketcherUI.addSegment(100, 100, -100, 100); + let seg4 = sketcherUI.addSegment(-100, 100, -100, -100); + + ui.commitSketch(); + + ui.selectFaces([0, 0, -10], [0, 0, 10]); + + + ui.openWizard('EXTRUDE'); + ui.wizardContext.updateParam('value', 200); + ui.wizardOK(); + + let [leftFace] = ui.rayCastFaces([-200, 0, 100], [200, 0, 100]); + + let sketchId = sketchedFace.defaultSketchId; + + assertTrue(leftFace.brepFace.data.productionInfo.originatedFromPrimitive, + sketchObjectGlobalId(sketchId, seg3.id)); + + assertTrue(leftFace.brepFace.data.productionInfo.role, "sweep"); + + env.done(); +} diff --git a/web/test/cases/craftPlane.js b/web/test/cases/craftPlane.js new file mode 100644 index 00000000..1d83a577 --- /dev/null +++ b/web/test/cases/craftPlane.js @@ -0,0 +1,81 @@ +import {assertEmpty, assertEquals, assertFaceIsPlane, assertTrue} from '../utils/asserts'; + +export const TEST_MODE = 'modellerUI'; + +export function testCreatePlaneAtOriginDefaultXY(env, ui) { + ui.openWizard('PLANE'); + ui.wizardOK(); + assertFaceIsPlane(ui.rayCastFaces([0, 0, -10], [0, 0, 10])[0]); + env.done(); +} + +export function testCreatePlaneAtOriginXZ(env, ui) { + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('orientation', 'XZ'); + ui.wizardOK(); + assertFaceIsPlane(ui.rayCastFaces([0, -10, 0], [0, 10, 0])[0]); + env.done(); +} + +export function testCreatePlaneAtZY(env, ui) { + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('orientation', 'ZY'); + ui.wizardOK(); + assertFaceIsPlane(ui.rayCastFaces([-10, 0, 0], [10, 0, 0])[0]); + env.done(); +} + +export function testCreatePlaneAtOriginXYOffset(env, ui) { + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('depth', 100); + ui.wizardOK(); + assertEmpty(ui.rayCastFaces([0, 0, -10], [0, 0, 10])); + assertFaceIsPlane(ui.rayCastFaces([0, 0, 90], [0, 0, 110])[0]); + env.done(); +} + +export function testCreatePlaneAtOriginXZOffset(env, ui) { + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('orientation', 'XZ'); + ui.wizardContext.updateParam('depth', 100); + ui.wizardOK(); + assertEmpty(ui.rayCastFaces([0, -10, 0], [0, 10, 0])); + assertFaceIsPlane(ui.rayCastFaces([0, 90, 0], [0, 110, 0])[0]); + env.done(); +} + +export function testCreatePlaneAtOriginZYOffset(env, ui) { + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('orientation', 'ZY'); + ui.wizardContext.updateParam('depth', 100); + ui.wizardOK(); + assertEmpty(ui.rayCastFaces([-10, 0, 0], [10, 0, 0])); + assertFaceIsPlane(ui.rayCastFaces([90, 0, 0], [110, 0, 0])[0]); + env.done(); +} + +export function testCreatePlaneParallelToOther(env, ui) { + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('orientation', 'ZY'); + ui.wizardContext.updateParam('depth', 100); + ui.wizardOK(); + + assertEmpty(ui.rayCastFaces([-10, 0, 0], [10, 0, 0])); + let captured = ui.rayCastFaces([90, 0, 0], [210, 0, 0]); + assertTrue(captured.length === 1); + + let baseFace = captured[0]; + + ui.openWizard('PLANE'); + ui.wizardContext.updateParam('parallelTo', baseFace.id); + ui.wizardContext.updateParam('depth', 100); + ui.wizardOK(); + + captured = ui.rayCastFaces([90, 0, 0], [210, 0, 0]); + assertTrue(captured.length === 2); + assertTrue(captured[0].id === baseFace.id); + assertTrue(captured[1].id !== baseFace.id); + + env.done(); +} + diff --git a/web/test/menu.js b/web/test/menu.js index 7c7a8952..7532d255 100644 --- a/web/test/menu.js +++ b/web/test/menu.js @@ -11,7 +11,7 @@ export class Menu { .on('click', (e) => this.popup.hide()) .on('contextmenu', (e) => { const target = $(e.target).closest('.right-click-menu'); - if (target.length == 0) return true; + if (target.length === 0) return true; return this.onShowMenu(e, target); }); } diff --git a/web/test/modes.js b/web/test/modes.js new file mode 100644 index 00000000..42f9f636 --- /dev/null +++ b/web/test/modes.js @@ -0,0 +1,10 @@ +import * as test from './test'; +import modellerUISubject from './utils/subjects/modeller/modellerUISubject'; + +export const modellerUI = func => env => { + test.emptyModeller(env.test(win => { + let subject = modellerUISubject(win.__CAD_APP); + func(env, subject); + })); +}; + diff --git a/web/test/runner.less b/web/test/runner.less index 355d5422..8e519782 100644 --- a/web/test/runner.less +++ b/web/test/runner.less @@ -4,17 +4,19 @@ body { } #sandbox { + position: fixed; border: 3px plum solid; box-sizing: border-box; - width: 100%; - height: 60%; + right: 0; + bottom: 0; + height: 40%; + max-height: 500px; + width: 40%; + max-width: 500px; background-color: #233930; } #test-list { - width: 100%; - height: ~"calc(40% - 22px)"; - overflow-y: scroll; } #sandbox iframe { diff --git a/web/test/suites.js b/web/test/suites.js index 92479e34..5b3c618b 100644 --- a/web/test/suites.js +++ b/web/test/suites.js @@ -1,3 +1,6 @@ +import * as test from './test'; +import * as modes from './modes'; + export default { SketcherObjects: [ TestCase('segment'), @@ -20,7 +23,8 @@ export default { ], Craft: [ - TestCase('craft'), + TestCase('craftPlane'), + TestCase('craftExtrude'), ], BREP: [ @@ -36,11 +40,22 @@ export default { }; function TestCase(name) { - let tests = require('./cases/' + name).default; - tests = Object.keys(tests).filter(key => key.startsWith('test')).map(key => ({ - name: key, - func: tests[key] - })); + let testModule = require('./cases/' + name); + let tests; + function registerTests(testsHolder, helperWrapper) { + tests = testsHolder; + tests = Object.keys(tests).filter(key => key.startsWith('test')).map(key => ({ + name: key, + func: helperWrapper(tests[key]) + })); + + } + let mode = modes[testModule.TEST_MODE]; + if (mode) { + registerTests(testModule, mode); + } else { + registerTests(testModule.default, func => env => func(env)); + } return { name, tests } diff --git a/web/test/test.js b/web/test/test.js index c904a7da..506c3c18 100644 --- a/web/test/test.js +++ b/web/test/test.js @@ -52,18 +52,18 @@ export class TestEnv { } } - testTPI(testBlock) { - return this.test(function(win, app) { - testBlock(app.TPI); - }); - } - assertTrue(stmt, msg) { if (!stmt) { this.fail('assertTrue fails.', msg); } } - + + assertEmpty(array, msg) { + if (array.length !== 0) { + this.fail('assertEmpty fails. Array length = ' + array.length, msg); + } + } + assertFalse(stmt, msg) { if (stmt) { this.fail('assertFalse fails.', msg); @@ -95,7 +95,7 @@ export class TestEnv { assertData(expected, actual) { const expectedJSON = JSON.stringify(expected).replace(/\s/g, ''); const actualJSON = JSON.stringify(actual).replace(/\s/g, ''); - if (actualJSON != expectedJSON) { + if (actualJSON !== expectedJSON) { console.log('EXPECTED:'); console.log(this.prettyJSON(expected)); console.log('ACTUAL:'); @@ -141,7 +141,7 @@ function checkSimilarity(data1, data2) { const info1 = info(data1); const info2 = info(data2); console.log(info1 + " : " + info2); - return info1 == info2; + return info1 === info2; } @@ -174,8 +174,22 @@ export function sketch(callback) { load('/sketcher.html#' + TEST_PROJECT, callback, SKETCHER_API); } +let ModellerWin = null; export function modeller(callback) { - load('/index.html#' + TEST_PROJECT, callback, MODELLER_API); + if (ModellerWin != null) { + ModellerWin.__CAD_APP.services.project.empty(); + callback(ModellerWin, MODELLER_API(ModellerWin)); + return; + } + load('/index.html#' + TEST_PROJECT, (win, api) => { + ModellerWin = win; + let ctx = win.__CAD_APP; + ctx.streams.lifecycle.projectLoaded.attach(loaded => { + if (loaded) { + callback(win, api); + } + }) + }, MODELLER_API); } export function emptyModeller(callback) { diff --git a/web/test/utils/asserts.js b/web/test/utils/asserts.js new file mode 100644 index 00000000..61968732 --- /dev/null +++ b/web/test/utils/asserts.js @@ -0,0 +1,49 @@ +import {FailError} from '../test'; + +export function fail(msg, optionalMsg) { + throw new FailError(msg + (optionalMsg === undefined ? '' : ' ' + optionalMsg)); +} + +export function assertTrue(stmt, msg) { + if (!stmt) { + fail('assertTrue fails.', msg); + } +} + +export function assertEmpty(array, msg) { + if (array.length !== 0) { + fail('assertEmpty fails. Array length = ' + array.length, msg); + } +} + +export function assertFalse(stmt, msg) { + if (stmt) { + fail('assertFalse fails.', msg); + } +} + +export function assertEquals(expected, actual, msg) { + if (expected !== actual) { + fail('assertEquals: Expected: ' + expected + ' but was ' + actual, msg); + } +} + +export function assertFloatEquals(expected, actual, msg) { + if (Math.abs(expected - actual) >= 1E-6) { + fail('assertFloatEquals: Expected: ' + expected + ' but was ' + actual, msg); + } +} + +export function assertPointXY2DEquals(expectedX, expectedY, actual, msg) { + if (actual.x !== expectedX || actual.y !== expectedY) { + fail('assertPoint2DEquals: Expected: (' + expectedX + ', ' + expectedY + ') but was (' + actual.x + ', ' + actual.y + ')', msg); + } +} + +export function assertPoint2DEquals(expected, actial, msg) { + this.assertPointXY2DEquals(expected.x, expected.y, actial, msg); +} + +export function assertFaceIsPlane(face) { + assertTrue(face.shell.surfacePrototype !== undefined); +} \ No newline at end of file diff --git a/web/test/utils/sketcher-utils.js b/web/test/utils/sketcher-utils.js index 16a200d4..0295cf46 100644 --- a/web/test/utils/sketcher-utils.js +++ b/web/test/utils/sketcher-utils.js @@ -46,6 +46,14 @@ export function addSegment(app, aX, aY, bX, bY) { return segment; } +export function addSegmentInModel(app, aX, aY, bX, bY) { + + [aX, aY] = modelToScreen(app.viewer, aX, aY); + [bX, bY] = modelToScreen(app.viewer, bX, bY); + + return addSegment(app, aX, aY, bX, bY); +} + export function polyLine(app) { app.actions['addMultiSegment'].action(); const tool = app.viewer.toolManager.tool; @@ -79,4 +87,13 @@ export class TestSegment { add(app) { return addSegment(app, this.a.x, this.a.y, this.b.x, this.b.y); } -} \ No newline at end of file +} + +function modelToScreen(viewer, x, y) { + + let modelToScreenMx = viewer.screenToModelMatrix.invert(); + [x, y] = modelToScreenMx.apply3([x, y, 0]); + x /= viewer.retinaPxielRatio; + y = (viewer.canvas.height - y) / viewer.retinaPxielRatio; + return [x, y]; +} diff --git a/web/test/utils/subjects/modeller/modellerUISubject.js b/web/test/utils/subjects/modeller/modellerUISubject.js new file mode 100644 index 00000000..c5e92196 --- /dev/null +++ b/web/test/utils/subjects/modeller/modellerUISubject.js @@ -0,0 +1,95 @@ +import {TestMouseEvent} from '../../mouse-event'; +import {getAttribute} from '../../../../../modules/scene/objectData'; +import {FACE} from '../../../../app/cad/scene/entites'; +import {createSubjectFromInPlaceSketcher} from './sketcherUISubject'; + +export default ctx => { + + function openWizard(operationId) { + ctx.services.action.run(operationId); + } + + function wizardOK() { + ctx.services.wizard.applyWorkingRequest(); + } + + function sceneMouseEvent(type, x, y) { + let domEl = ctx.services.viewer.sceneSetup.domElement(); + let xMid = Math.round(domEl.offsetWidth / 2 + x); + let yMid = Math.round(domEl.offsetHeight / 2 + y); + domEl.dispatchEvent(mouseEvent(type, xMid, yMid)); + } + + function clickOnScene(x, y) { + sceneMouseEvent('mousedown', x, y); + sceneMouseEvent('mouseup', x, y); + } + + function rayCast(from3, to3) { + const THREE = ctx.services.tpi.THREE; + let raycaster = new THREE.Raycaster(); + let from = new THREE.Vector3().fromArray(from3); + let to = new THREE.Vector3().fromArray(to3); + let dir = to.sub(from); + let dist = dir.length(); + raycaster.set(from, dir.normalize()); + return raycaster.intersectObjects( ctx.services.cadScene.workGroup.children, true ).filter(h => h.distance <= dist); + } + + function rayCastFaces(from, to) { + let models = rayCast(from, to).map(h => { + if (h.face) { + let faceV = getAttribute(h.face, FACE); + if (faceV && faceV.model) { + return faceV.model; + } + } + }); + let out = []; + models.forEach(m => { + if (!!m && !out.includes(m)) { + out.push(m); + } + }); + return out; + } + + function selectFaces(from, to) { + rayCastFaces(from, to).forEach(face => ctx.services.pickControl.pick(face)); + } + + function getWizardContext() { + return ctx.streams.wizard.wizardContext.value + } + + function openSketcher() { + ctx.services.action.run('EditFace'); + return createSubjectFromInPlaceSketcher(ctx); + } + + function commitSketch() { + ctx.services.action.run('sketchSaveAndExit'); + } + + return { + context: ctx, + openWizard, wizardOK, sceneMouseEvent, clickOnScene, + rayCastFaces, selectFaces, openSketcher, commitSketch, + get wizardContext() { return getWizardContext()} + }; +} + + +function mouseEvent(type, x, y) { + return new MouseEvent(type, { + bubbles: true, + screenX: x, + screenY: y, + clientX: x, + clientY: y, + pageX: x, + pageY: y, + offsetX: x, + offsetY: y, + }); +} \ No newline at end of file diff --git a/web/test/utils/subjects/modeller/sketcherUISubject.js b/web/test/utils/subjects/modeller/sketcherUISubject.js new file mode 100644 index 00000000..ee089c19 --- /dev/null +++ b/web/test/utils/subjects/modeller/sketcherUISubject.js @@ -0,0 +1,26 @@ +import * as sketcher_utils from '../../../utils/sketcher-utils' +import {decapitalize} from '../../../../../modules/gems/capitalize'; + +export function createSubjectFromInPlaceSketcher(ctx) { + + + let actions = {}; + for (const actionId of Object.keys(ctx.streams.action.state)) { + if (actionId.startsWith('sketch')) { + let oldId = decapitalize(actionId.substring(6)); + actions[oldId] = { + action: () => ctx.services.action.run(actionId) + } + } + } + + const oldStyleSketcherApp = { + viewer: ctx.services.sketcher.inPlaceEditor.viewer, + actions + }; + + return { + addSegment: sketcher_utils.addSegmentInModel.bind(this, oldStyleSketcherApp) + } + +} \ No newline at end of file