diff --git a/modules/bus/index.js b/modules/bus/index.js new file mode 100644 index 00000000..79324440 --- /dev/null +++ b/modules/bus/index.js @@ -0,0 +1,72 @@ +export default class Bus { + + constructor() { + this.listeners = {}; + this.state = {}; + this.recordFor = new Set(); + this.lock = new Set(); + } + + subscribe(key, callback) { + let listenerList = this.listeners[key]; + if (listenerList === undefined) { + listenerList = []; + this.listeners[key] = listenerList; + } + listenerList.push(callback); + + if (this.recordFor.has(key)) { + callback(this.state[key]); + } + return callback; + }; + + unSubscribe(key, callback) { + const listenerList = this.listeners[key]; + for (let i = 0; i < listenerList.length; i++) { + if (listenerList[i] === callback) { + listenerList.splice(i, 1); + return; + } + } + }; + + dispatch(key, data) { + if (this.lock.has(key)) { + console.warn('recursive dispatch'); + return + } + this.lock.add(key); + try { + let listenerList = this.listeners[key]; + if (listenerList !== undefined) { + for (let i = 0; i < listenerList.length; i++) { + const callback = listenerList[i]; + try { + callback(data); + } catch(e) { + console.error(e); + } + } + } + } finally { + this.lock.delete(key); + if (this.recordFor.has(key)) { + this.state[key] = data; + } + } + }; + + enableState(forEvent, initValue) { + this.recordFor.add(forEvent); + this.state[forEvent] = initValue; + } + + disableState(forEvent) { + this.recordFor.delete(forEvent); + } +} + + + + diff --git a/modules/bus/store.js b/modules/bus/store.js new file mode 100644 index 00000000..a55a4ab3 --- /dev/null +++ b/modules/bus/store.js @@ -0,0 +1,61 @@ + +export class Store { + + constructor() { + this.state = {}; + this.listeners = {}; + this.locked = false; + } + + subscribe(key, callback) { + let listenerList = this.listeners[key]; + if (listenerList === undefined) { + listenerList = []; + this.listeners[key] = listenerList; + } + listenerList.push(callback); + return callback; + }; + + unSubscribe(key, callback) { + const listenerList = this.listeners[key]; + for (let i = 0; i < listenerList.length; i++) { + if (listenerList[i] === callback) { + listenerList.splice(i, 1); + return; + } + } + }; + + dispatch(key, newValue, oldValue) { + if (this.locked === true) { + throw 'concurrent state modification'; + } + this.locked = true; + try { + let listenerList = this.listeners[key]; + if (listenerList !== undefined) { + for (let i = 0; i < listenerList.length; i++) { + const callback = listenerList[i]; + try { + callback(newValue, oldValue, this); + } catch(e) { + console.error(e); + } + } + } + } finally { + this.locked = false; + } + }; + + set(key, value) { + let oldValue = this.state[key]; + this.state[key] = value; + this.dispatch(key, value, oldValue); + } + + get(key) { + return this.state[key]; + } +} \ No newline at end of file diff --git a/modules/gems/iterables.js b/modules/gems/iterables.js new file mode 100644 index 00000000..5bf72b1a --- /dev/null +++ b/modules/gems/iterables.js @@ -0,0 +1,29 @@ + +export function findDiff(arr1, arr2) { + + let both = []; + let firstOnly = []; + let secondOnly = []; + + for (let e1 of arr1) { + for (let e2 of arr2) { + if (e1 === e2) { + both.push(e1); + } + } + } + + for (let e1 of arr1) { + if (both.indexOf(e1) === -1) { + firstOnly.push(e1); + } + } + + for (let e2 of arr2) { + if (both.indexOf(e2) === -1) { + secondOnly.push(e2); + } + } + + return [both, firstOnly, secondOnly] +} \ No newline at end of file diff --git a/web/app/utils/mask.js b/modules/gems/mask.js similarity index 100% rename from web/app/utils/mask.js rename to modules/gems/mask.js diff --git a/modules/scene/materails.js b/modules/scene/materials.js similarity index 53% rename from modules/scene/materails.js rename to modules/scene/materials.js index e25716d9..32f4b866 100644 --- a/modules/scene/materails.js +++ b/modules/scene/materials.js @@ -1,4 +1,5 @@ -import {MeshPhongMaterial, FaceColors, DoubleSide} from 'three'; +import DPR from 'dpr'; +import {MeshPhongMaterial, LineBasicMaterial, FaceColors, DoubleSide} from 'three'; export function createTransparentPhongMaterial(color, opacity) { return new MeshPhongMaterial({ @@ -13,4 +14,9 @@ export function createTransparentPhongMaterial(color, opacity) { }); } - +export function createLineMaterial(color, linewidth) { + return new LineBasicMaterial({ + color, + linewidth: linewidth / DPR + }); +} \ No newline at end of file diff --git a/modules/scene/objectData.js b/modules/scene/objectData.js new file mode 100644 index 00000000..c39098fe --- /dev/null +++ b/modules/scene/objectData.js @@ -0,0 +1,17 @@ + +export function setAttribute(obj, key, value) { + getData(obj)[key] = value; +} + +export function getAttribute(obj, key) { + return getData(obj)[key]; +} + +export function getData(obj) { + let data = obj.__TCAD_CUSTOM_DATA; + if (data === undefined) { + data = {}; + obj.__TCAD_CUSTOM_DATA = data; + } + return data; +} \ No newline at end of file diff --git a/web/app/3d/actions/action-helpers.js b/web/app/3d/actions/action-helpers.js index 515ec898..eec3fe14 100644 --- a/web/app/3d/actions/action-helpers.js +++ b/web/app/3d/actions/action-helpers.js @@ -1,17 +1,17 @@ export function checkForSelectedFaces(amount) { return (state, app) => { - state.enabled = app.viewer.selectionMgr.selection.length >= amount; + state.enabled = app.getFaceSelection().length >= amount; if (!state.enabled) { - state.hint = amount == 1 ? 'requires a face to be selected' : 'requires ' + amount + ' faces to be selected'; + state.hint = amount === 1 ? 'requires a face to be selected' : 'requires ' + amount + ' faces to be selected'; } } } export function checkForSelectedSolids(amount) { return (state, app) => { - state.enabled = app.viewer.selectionMgr.selection.length >= amount; + state.enabled = app.getFaceSelection().length >= amount; if (!state.enabled) { - state.hint = amount == 1 ? 'requires a solid to be selected' : 'requires ' + amount + ' solids to be selected'; + state.hint = amount === 1 ? 'requires a solid to be selected' : 'requires ' + amount + ' solids to be selected'; } } } \ No newline at end of file diff --git a/web/app/3d/actions/actions.js b/web/app/3d/actions/actions.js index e9e320f3..98ed5aa7 100644 --- a/web/app/3d/actions/actions.js +++ b/web/app/3d/actions/actions.js @@ -37,7 +37,7 @@ ActionManager.prototype.notify = function(event) { if (actions != undefined) { for (let action of actions) { this.updateAction(action); - this.app.bus.notify('action.update.' + action.id, action.state); + this.app.bus.dispatch('action.update.' + action.id, action.state); } } }; diff --git a/web/app/3d/cad-utils.js b/web/app/3d/cad-utils.js index b59be2b6..b9a6cad4 100644 --- a/web/app/3d/cad-utils.js +++ b/web/app/3d/cad-utils.js @@ -6,7 +6,6 @@ import * as math from '../math/math' import {Matrix3, AXIS, ORIGIN} from '../math/l3space' import Counters from './counters' import {MeshSceneSolid} from './scene/wrappers/meshSceneObject' -import DPR from '../utils/dpr' export const FACE_COLOR = 0xB0C4DE; diff --git a/web/app/3d/craft/brep/wizards/preview-wizard.js b/web/app/3d/craft/brep/wizards/preview-wizard.js index 9c67e3b6..e8ef2fa2 100644 --- a/web/app/3d/craft/brep/wizards/preview-wizard.js +++ b/web/app/3d/craft/brep/wizards/preview-wizard.js @@ -43,7 +43,7 @@ export class PreviewWizard extends Wizard { } dispose() { - this.app.bus.unsubscribe('refreshSketch', this.onSketchUpdate); + this.app.bus.unSubscribe('refreshSketch', this.onSketchUpdate); this.destroyPreviewObject(); this.app.viewer.workGroup.remove(this.previewGroup); this.app.viewer.render(); diff --git a/web/app/3d/craft/brep/wizards/wizard.js b/web/app/3d/craft/brep/wizards/wizard.js index 26ee964e..9a4bca10 100644 --- a/web/app/3d/craft/brep/wizards/wizard.js +++ b/web/app/3d/craft/brep/wizards/wizard.js @@ -122,31 +122,31 @@ export class Wizard { } createFormField(name, label, type, params, initValue) { - if (type == 'number') { + if (type === 'number') { const number = tk.config(new tk.Number(label, initValue, params.step, params.round), params); number.input.on('t-change', () => this.onUIChange(name)); return Field.fromInput(number, Field.TEXT_TO_NUMBER_COERCION); - } else if (type == 'choice') { + } else if (type === 'choice') { const ops = params.options; const radio = new tk.InlineRadio(ops, ops, ops.indexOf(initValue)); radio.root.find('input[type=radio]').on('change', () => { this.onUIChange(name); }); return new Field(radio, () => radio.getValue(), (v) => radio.setValue(v)); - } else if (type == 'face') { - return selectionWidget(name, label, initValue, this.app.viewer.selectionMgr, (selection) => selection.id); - } else if (type == 'sketch.segment') { - return selectionWidget(name, label, initValue, this.app.viewer.sketchSelectionMgr, (selection) => selection.__TCAD_SketchObject.id); + } else if (type === 'face') { + return selectionWidget(name, label, initValue, this.app.context.bus, 'selection:face',(selection) => selection.id); + } else if (type === 'sketch.segment') { + return selectionWidget(name, label, initValue, this.app.context.bus, 'selection:sketchObject', (selection) => selection.__TCAD_SketchObject.id); } } } -function selectionWidget(name, label, initValue, selectionManager, toId) { +function selectionWidget(name, label, initValue, bus, selectionKey, toId) { const obj = new tk.Text(label, initValue); obj.input.on('change', () => this.onUIChange(name)); return Field.fromInput(obj, undefined, (objId) => { if (objId === CURRENT_SELECTION) { - let selection = selectionManager.selection[0]; + let selection = bus.state[selectionKey][0]; return selection ? toId(selection) : ''; } else { return objId; diff --git a/web/app/3d/craft/craft.js b/web/app/3d/craft/craft.js index 1651d407..157cafa9 100644 --- a/web/app/3d/craft/craft.js +++ b/web/app/3d/craft/craft.js @@ -12,8 +12,8 @@ export function Craft(app) { if (this._historyPointer === value) return; this._historyPointer = value; this.reset(this.history.slice(0, this._historyPointer)); - this.app.bus.notify('craft'); - this.app.bus.notify('historyPointer'); + this.app.bus.dispatch('craft'); + this.app.bus.dispatch('historyPointer'); this.app.viewer.render(); } }); @@ -30,7 +30,7 @@ Craft.prototype.remove = function(modificationIndex) { if (this.historyPointer >= history.length) { this.finishHistoryEditing(); } else { - this.app.bus.notify('historyShrink'); + this.app.bus.dispatch('historyShrink'); } }; @@ -38,8 +38,8 @@ Craft.prototype.loadHistory = function(history) { this.history = history; this._historyPointer = history.length; this.reset(history); - this.app.bus.notify('craft'); - this.app.bus.notify('historyPointer'); + this.app.bus.dispatch('craft'); + this.app.bus.dispatch('historyPointer'); this.app.viewer.render(); }; @@ -87,7 +87,7 @@ Craft.prototype.modifyInternal = function(request) { this.app.viewer.workGroup.add(solid.cadGroup); } - this.app.bus.notify('solid-list', { + this.app.bus.dispatch('solid-list', { solids: this.solids, needRefresh: result.created }); @@ -103,7 +103,7 @@ Craft.prototype.modify = function(request, overriding) { } this.history[this._historyPointer] = request; this._historyPointer ++; - this.app.bus.notify('craft'); - this.app.bus.notify('historyPointer'); + this.app.bus.dispatch('craft'); + this.app.bus.dispatch('historyPointer'); this.app.viewer.render(); }; \ No newline at end of file diff --git a/web/app/3d/craft/mesh/wizards/plane.js b/web/app/3d/craft/mesh/wizards/plane.js index 1cd7b624..31416e93 100644 --- a/web/app/3d/craft/mesh/wizards/plane.js +++ b/web/app/3d/craft/mesh/wizards/plane.js @@ -16,7 +16,7 @@ export function PlaneWizard(app, initParams) { relativeToFaceId: '' }; this.selectionListener = () => { - const face = this.app.viewer.selectionMgr.selection[0]; + const face = this.getFirstSelectedFace(); if (face) { this.ui.relativeToFace.input.val(face.id); this.synch(); diff --git a/web/app/3d/craft/mesh/wizards/revolve.js b/web/app/3d/craft/mesh/wizards/revolve.js index 3ad6410c..89f81e8b 100644 --- a/web/app/3d/craft/mesh/wizards/revolve.js +++ b/web/app/3d/craft/mesh/wizards/revolve.js @@ -145,7 +145,7 @@ RevolveWizard.prototype.createRequest = function(done) { }; RevolveWizard.prototype.dispose = function() { - this.app.bus.unsubscribe('selection-sketch-object', this.selectionListener); + this.app.bus.unSubscribe('selection-sketch-object', this.selectionListener); OpWizard.prototype.dispose.call(this); }; diff --git a/web/app/3d/craft/mesh/wizards/wizard-commons.js b/web/app/3d/craft/mesh/wizards/wizard-commons.js index ea5ae0fa..2649917d 100644 --- a/web/app/3d/craft/mesh/wizards/wizard-commons.js +++ b/web/app/3d/craft/mesh/wizards/wizard-commons.js @@ -1,4 +1,4 @@ -import DPR from '../../../../utils/dpr' +import DPR from 'dpr' import * as tk from '../../../../ui/toolkit' const IMAGINE_MATERIAL = new THREE.LineBasicMaterial({ diff --git a/web/app/3d/debug.js b/web/app/3d/debug.js index b12d17d1..cc12948c 100644 --- a/web/app/3d/debug.js +++ b/web/app/3d/debug.js @@ -1,7 +1,7 @@ import {checkForSelectedFaces} from './actions/action-helpers' import {nurbsToThreeGeom, triangulateToThree} from './scene/wrappers/brepSceneObject' import {createSolidMaterial} from './scene/wrappers/sceneObject' -import DPR from '../utils/dpr' +import DPR from 'dpr' import Vector from 'math/vector'; import {NurbsCurve} from "../brep/geom/impl/nurbs"; import * as ui from '../ui/ui'; @@ -242,7 +242,7 @@ const DebugActions = { listens: ['selection'], update: checkForSelectedFaces(1), invoke: (app) => { - var s = app.viewer.selectionMgr.selection[0]; + var s = app.getFirstSelectedFace(); console.log(JSON.stringify({ polygons: s.csgGroup.polygons, basis: s._basis @@ -257,7 +257,7 @@ const DebugActions = { listens: ['selection'], update: checkForSelectedFaces(1), invoke: (app) => { - console.log(app.viewer.selectionMgr.selection[0].id); + console.log(app.getFirstSelectedFace().id); } }, @@ -268,7 +268,7 @@ const DebugActions = { listens: ['selection'], update: checkForSelectedFaces(1), invoke: (app) => { - const faceId = app.viewer.selectionMgr.selection[0].id; + const faceId = app.getFirstSelectedFace().id; const sketch = JSON.parse(localStorage.getItem(app.faceStorageKey(faceId))); const layers = sketch.layers.filter(l => l.name != '__bounds__'); const data = []; diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index 78844769..e3a79c34 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -1,6 +1,6 @@ import '../../../modules/scene/utils/vectorThreeEnhancement' import '../utils/three-loader' -import {Bus} from '../ui/toolkit' +import Bus from 'bus' import {Viewer} from './scene/viewer' import {UI} from './ui/ctrl' import TabSwitcher from './ui/tab-switcher' @@ -31,6 +31,7 @@ import {Circle} from "./craft/sketch/sketch-model"; import {Plane} from "../brep/geom/impl/plane"; import {enclose} from "../brep/brep-enclose"; // import {createSphere, rayMarchOntoCanvas, sdfIntersection, sdfSolid, sdfSubtract, sdfTransform, sdfUnion} from "../hds/sdf"; +import Plugins from './plugins'; function App() { this.id = this.processHints(); @@ -38,7 +39,11 @@ function App() { this.actionManager = new ActionManager(this); this.inputManager = new InputManager(this); this.state = this.createState(); - this.viewer = new Viewer(this.bus, document.getElementById('viewer-container')); + this.context = this.createPluginContext(); + this.initPlugins(); + this.createViewer(); + this.viewer = this.context.services.viewer; + this.viewer.workGroup = this.context.services.cadScene.workGroup; this.actionManager.registerActions(AllActions); this.tabSwitcher = new TabSwitcher($('#tab-switcher'), $('#view-3d')); this.controlBar = new ControlBar(this, $('#control-bar')); @@ -67,7 +72,7 @@ function App() { var sketchFace = app.findFace(sketchFaceId); if (sketchFace != null) { app.refreshSketchOnFace(sketchFace); - app.bus.notify('refreshSketch'); + app.bus.dispatch('refreshSketch'); app.viewer.render(); } } @@ -82,6 +87,32 @@ function App() { }); } +App.prototype.createPluginContext = function() { + return { + bus: this.bus, + services: {} + }; +}; + +App.prototype.initPlugins = function() { + for (let plugin of Plugins) { + plugin.activate(this.context); + } +}; + +App.prototype.createViewer = function() { + this.context.bus.dispatch('dom:viewerContainer', document.getElementById('viewer-container')); +}; + +App.prototype.getFaceSelection = function() { + let selection = this.context.bus.state['selection:face']; + return selection; +}; + +App.prototype.getFirstSelectedFace = function() { + return this.getSelection()[0]; +}; + App.prototype.addShellOnScene = function(shell, skin) { const sceneSolid = new BREPSceneSolid(shell, undefined, skin); this.viewer.workGroup.add(sceneSolid.cadGroup); @@ -320,7 +351,7 @@ App.prototype.lookAtSolid = function(solidId) { App.prototype.createState = function() { const state = {}; - this.bus.defineObservable(state, 'showSketches', true); + // this.bus.defineObservable(state, 'showSketches', true); return state; }; @@ -604,7 +635,7 @@ App.prototype.cut = function() { App.prototype.refreshSketches = function() { this._refreshSketches(); - this.bus.notify('refreshSketch'); + this.bus.dispatch('refreshSketch'); this.viewer.render(); }; diff --git a/web/app/3d/plugins.js b/web/app/3d/plugins.js new file mode 100644 index 00000000..efd8537f --- /dev/null +++ b/web/app/3d/plugins.js @@ -0,0 +1,6 @@ +import * as ScenePlugin from './scene/scenePlugin'; +import * as SelectionMarkerPlugin from './scene/selectionMarker/selectionMarkerPlugin'; + +export default [ + ScenePlugin, SelectionMarkerPlugin +] \ No newline at end of file diff --git a/web/app/3d/scene/cadScene.js b/web/app/3d/scene/cadScene.js new file mode 100644 index 00000000..2bab2a35 --- /dev/null +++ b/web/app/3d/scene/cadScene.js @@ -0,0 +1,66 @@ +import {AXIS} from '../../math/l3space' +import {createArrow} from 'scene/objects/auxiliary'; +import Vector from 'math/vector'; +import {OnTopOfAll} from 'scene/materialMixins'; +import {moveObject3D, setBasisToObject3D} from 'scene/objects/transform'; + +import * as SceneGraph from 'scene/sceneGraph'; + +export default class CadScene { + + constructor(rootGroup) { + this.workGroup = SceneGraph.createGroup(); + this.auxGroup = SceneGraph.createGroup(); + SceneGraph.addToGroup(rootGroup, this.workGroup); + SceneGraph.addToGroup(rootGroup, this.auxGroup); + + this.setUpAxises(); + this.setUpBasisGroup(); + } + + setUpAxises() { + let arrowLength = 1500; + let createAxisArrow = createArrow.bind(null, arrowLength, 40, 16); + let addAxis = (axis, color) => { + let arrow = createAxisArrow(axis, color, 0.2); + moveObject3D(arrow, axis.scale(-arrowLength * 0.5)); + SceneGraph.addToGroup(this.auxGroup, arrow); + }; + + addAxis(AXIS.X, 0xFF0000); + addAxis(AXIS.Y, 0x00FF00); + addAxis(AXIS.Z, 0x0000FF); + } + + setUpBasisGroup() { + let length = 200; + let arrowLength = length * 0.2; + let arrowHead = arrowLength * 0.4; + + let _createArrow = createArrow.bind(null, length, arrowLength, arrowHead); + + function createBasisArrow(axis, color) { + return _createArrow(axis, color, 0.4, [OnTopOfAll]); + } + + this.basisGroup = SceneGraph.createGroup(); + let xAxis = createBasisArrow(new Vector(1, 0, 0), 0xFF0000); + let yAxis = createBasisArrow(new Vector(0, 1, 0), 0x00FF00); + SceneGraph.addToGroup(this.basisGroup, xAxis); + SceneGraph.addToGroup(this.basisGroup, yAxis); + } + + updateBasis(basis, depth) { + setBasisToObject3D(this.basisGroup, basis, depth); + } + + showBasis() { + this.workGroup.add(this.basisGroup); + } + + hideBasis() { + if (this.basisGroup.parent !== null) { + this.basisGroup.parent.remove(this.basisGroup); + } + } +} \ No newline at end of file diff --git a/web/app/3d/scene/controls/controlsManager.js b/web/app/3d/scene/controls/controlsManager.js new file mode 100644 index 00000000..e69de29b diff --git a/web/app/3d/scene/controls/pickControl.js b/web/app/3d/scene/controls/pickControl.js new file mode 100644 index 00000000..99556507 --- /dev/null +++ b/web/app/3d/scene/controls/pickControl.js @@ -0,0 +1,118 @@ +import * as mask from 'gems/mask' + +export const PICK_KIND = { + FACE: mask.type(1), + SKETCH: mask.type(2), + EDGE: mask.type(3), + VERTEX: mask.type(4) +}; + + +export default class PickControl { + constructor(context) { + this.context = context; + let {bus} = context; + let domElement = context.services.viewer.sceneSetup.domElement(); + bus.enableState('selection:solid', []); + bus.enableState('selection:face', []); + bus.enableState('selection:edge', []); + bus.enableState('selection:sketchObject', []); + + this.mouseState = { + startX: 0, + startY: 0 + }; + + domElement.addEventListener('mousedown', this.mousedown, false); + domElement.addEventListener('mouseup', this.mouseup, false); + } + + mousedown = e => { + this.mouseState.startX = e.offsetX; + this.mouseState.startY = e.offsetY; + }; + + mouseup = e => { + let dx = Math.abs(this.mouseState.startX - e.offsetX); + let dy = Math.abs(this.mouseState.startY - e.offsetY); + let TOL = 1; + if (dx < TOL && dy < TOL) { + if (e.button !== 0) { + this.handleSolidPick(e); + } else { + this.handlePick(e); + } + } + }; + + selected(key, object) { + let selection = this.context.bus.state[key]; + return selection !== undefined && selection.indexOf(object) !== -1; + } + + handlePick(event) { + this.raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (object, kind) => { + if (kind === PICK_KIND.FACE) { + if (!this.selected('selection:face', object)) { + this.context.bus.dispatch('selection:face', [object]); + return false; + } + } else if (kind === PICK_KIND.SKETCH) { + if (!this.selected('selection:sketchObject', object)) { + this.context.bus.dispatch('selection:sketchObject', [object]); + return false; + } + } else if (kind === PICK_KIND.EDGE) { + if (!this.selected('selection:edge', object)) { + this.context.bus.dispatch('selection:edge', [object]); + return false; + } + } + return true; + }); + } + + handleSolidPick(e) { + this.raycastObjects(e, PICK_KIND.FACE, (sketchFace) => { + this.context.bus.dispatch('selection:solid', sketchFace.solid); + this.context.services.viewer.render(); + return false; + }); + } + + raycastObjects(event, kind, visitor) { + let pickResults = this.context.services.viewer.raycast(event, this.context.services.cadScene.workGroup); + const pickers = [ + (pickResult) => { + if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line && + pickResult.object.__TCAD_SketchObject !== undefined) { + return !visitor(pickResult.object, PICK_KIND.SKETCH); + } + return false; + }, + (pickResult) => { + if (mask.is(kind, PICK_KIND.EDGE) && pickResult.object.__TCAD_EDGE !== undefined) { + return !visitor(pickResult.object, PICK_KIND.EDGE); + } + return false; + }, + (pickResult) => { + if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face && pickResult.face.__TCAD_SceneFace !== undefined) { + const sketchFace = pickResult.face.__TCAD_SceneFace; + return !visitor(sketchFace, PICK_KIND.FACE); + } + return false; + }, + ]; + for (let i = 0; i < pickResults.length; i++) { + const pickResult = pickResults[i]; + for (let picker of pickers) { + if (picker(pickResult)) { + return; + } + } + } + } + + +} diff --git a/web/app/3d/scene/pickControl.js b/web/app/3d/scene/pickControl.js deleted file mode 100644 index 97fc699d..00000000 --- a/web/app/3d/scene/pickControl.js +++ /dev/null @@ -1,40 +0,0 @@ - - -export default class PickControl { - constructor(bus) { - this.bus = bus; - } -} - -export function initPickControl(domElement, onPick) { - let mouseState = { - startX: 0, - startY: 0 - }; - - //fix for FireFox - function fixOffsetAPI(event) { - if (event.offsetX === undefined) { - event.offsetX = event.layerX; - event.offsetY = event.layerY; - } - } - - domElement.addEventListener('mousedown', - function (e) { - fixOffsetAPI(e); - mouseState.startX = e.offsetX; - mouseState.startY = e.offsetY; - }, false); - - domElement.addEventListener('mouseup', - function (e) { - fixOffsetAPI(e); - let dx = Math.abs(mouseState.startX - e.offsetX); - let dy = Math.abs(mouseState.startY - e.offsetY); - let TOL = 1; - if (dx < TOL && dy < TOL) { - onPick(e); - } - }, false); -} \ No newline at end of file diff --git a/web/app/3d/scene/scenePlugin.js b/web/app/3d/scene/scenePlugin.js new file mode 100644 index 00000000..3f1864f1 --- /dev/null +++ b/web/app/3d/scene/scenePlugin.js @@ -0,0 +1,20 @@ +import Viewer from './viewer'; +import CadScene from "./cadScene"; +import PickControl from "./controls/pickControl"; + +export function activate(context) { + context.bus.subscribe('dom:viewerContainer', (container) => { + initScene(context, container); + }); +} + +function initScene(context, container) { + let viewer = new Viewer(container); + context.services.viewer = viewer; + + context.services.cadScene = new CadScene(viewer.sceneSetup.rootGroup); + + let pickControl = new PickControl(context); + + context.bus.subscribe('scene:update', () => viewer.render()); +} diff --git a/web/app/3d/scene/selectionMarker/abstractSelectionMarker.js b/web/app/3d/scene/selectionMarker/abstractSelectionMarker.js new file mode 100644 index 00000000..d2131f4e --- /dev/null +++ b/web/app/3d/scene/selectionMarker/abstractSelectionMarker.js @@ -0,0 +1,53 @@ +import {findDiff} from 'gems/iterables'; + +export class AbstractSelectionMarker { + + constructor(bus, event) { + this.bus = bus; + this.selection = []; + this.bus.subscribe(event, this.update); + } + + update = selection => { + if (!selection) { + if (this.selection.length !== 0) { + for (let obj of this.selection) { + this.unMark(obj); + } + this.selection = []; + } + this.bus.dispatch('scene:update'); + return; + } + + let [, toMark, toWithdraw] = findDiff(selection, this.selection); + for (let obj of toMark) { + this.selection.push(obj); + this.mark(obj); + } + + for (let obj of toWithdraw) { + this.selection.splice(this.selection.indexOf(obj), 1); + this.unMark(obj); + } + this.bus.dispatch('scene:update'); + }; + + mark(obj) { + throw 'abstract'; + } + + unMark(obj) { + throw 'abstract'; + } +} + +export function setFacesColor(faces, color) { + for (let face of faces) { + if (color === null) { + face.color.set(new THREE.Color()); + } else { + face.color.set( color ); + } + } +} diff --git a/web/app/3d/scene/selectionMarker/edgeSelectionMarker.js b/web/app/3d/scene/selectionMarker/edgeSelectionMarker.js new file mode 100644 index 00000000..59b77a17 --- /dev/null +++ b/web/app/3d/scene/selectionMarker/edgeSelectionMarker.js @@ -0,0 +1,9 @@ +import {AbstractSelectionMarker} from "./abstractSelectionMarker"; +import {LineMarker} from "./lineMarker"; + +export class EdgeSelectionMarker extends LineMarker { + + constructor (bus, selectionMaterial) { + super(bus, 'selection:edge', selectionMaterial); + } +} \ No newline at end of file diff --git a/web/app/3d/scene/selectionMarker/lineMarker.js b/web/app/3d/scene/selectionMarker/lineMarker.js new file mode 100644 index 00000000..6e3a963d --- /dev/null +++ b/web/app/3d/scene/selectionMarker/lineMarker.js @@ -0,0 +1,20 @@ +import {AbstractSelectionMarker} from "./abstractSelectionMarker"; +import {setAttribute, getAttribute} from 'scene/objectData'; + +export class LineMarker extends AbstractSelectionMarker { + + constructor(bus, event, selectionMaterial) { + super(bus, event); + this.selectionMaterial = selectionMaterial; + } + + mark(obj) { + setAttribute(obj, 'selection:defaultMaterial', obj.material); + obj.material = this.selectionMaterial; + } + + unMark(obj) { + obj.material = getAttribute(obj, 'selection:defaultMaterial'); + obj.material = this.selectionMaterial; + } +} \ No newline at end of file diff --git a/web/app/3d/scene/selectionMarker/selectionMarker.js b/web/app/3d/scene/selectionMarker/selectionMarker.js new file mode 100644 index 00000000..af9ad111 --- /dev/null +++ b/web/app/3d/scene/selectionMarker/selectionMarker.js @@ -0,0 +1,47 @@ +import * as stitching from '../../../brep/stitching' +import {AbstractSelectionMarker, setFacesColor} from "./abstractSelectionMarker"; + +export class SelectionMarker extends AbstractSelectionMarker { + + constructor(bus, selectionColor, readOnlyColor, defaultColor) { + super(bus, 'selection:face'); + this.selectionColor = selectionColor; + this.defaultColor = defaultColor; + this.readOnlyColor = readOnlyColor; + } + + mark(sceneFace) { + this.setColor(sceneFace, this.selectionColor, this.readOnlyColor); + } + + unMark(sceneFace) { + this.setColor(sceneFace, this.defaultColor, this.defaultColor); + } + + setColor(sceneFace, color, groupColor) { + const group = this.findGroup(sceneFace); + if (group) { + for (let i = 0; i < group.length; i++) { + let face = group[i]; + setFacesColor(face.meshFaces, groupColor); + face.solid.mesh.geometry.colorsNeedUpdate = true; + } + } else { + setFacesColor(sceneFace.meshFaces, color); + sceneFace.solid.mesh.geometry.colorsNeedUpdate = true; + } + } + + findGroup(sceneFace) { + if (sceneFace.curvedSurfaces) { + return sceneFace.curvedSurfaces; + } + if (sceneFace.brepFace) { + const stitchedFace = sceneFace.brepFace.data[stitching.FACE_CHUNK]; + if (stitchedFace) { + return stitchedFace.faces.map(f => f.data['scene.face']); + } + } + return undefined; + } +} diff --git a/web/app/3d/scene/selectionMarker/selectionMarkerPlugin.js b/web/app/3d/scene/selectionMarker/selectionMarkerPlugin.js new file mode 100644 index 00000000..ddcbe598 --- /dev/null +++ b/web/app/3d/scene/selectionMarker/selectionMarkerPlugin.js @@ -0,0 +1,13 @@ +import DPR from 'dpr'; +import {SelectionMarker} from './selectionMarker'; +import {SketchSelectionMarker} from './sketchSelectionMarker'; +import {EdgeSelectionMarker} from './edgeSelectionMarker'; +import {createLineMaterial} from 'scene/materials'; + +export function activate(context) { + let {bus} = context; + new SelectionMarker(bus, 0xFAFAD2, 0xFF0000, null); + new SketchSelectionMarker(bus, createLineMaterial(0xFF0000, 6 / DPR)); + new EdgeSelectionMarker(bus, createLineMaterial(0xFA8072, 12 / DPR)); +} + diff --git a/web/app/3d/scene/selectionMarker/sketchSelectionMarker.js b/web/app/3d/scene/selectionMarker/sketchSelectionMarker.js new file mode 100644 index 00000000..9345364b --- /dev/null +++ b/web/app/3d/scene/selectionMarker/sketchSelectionMarker.js @@ -0,0 +1,11 @@ +import {AbstractSelectionMarker} from "./abstractSelectionMarker"; +import {setAttribute} from 'scene/objectData'; +import {getAttribute} from "../../../../../modules/scene/objectData"; +import {LineMarker} from "./lineMarker"; + +export class SketchSelectionMarker extends LineMarker { + + constructor(bus, selectionMaterial) { + super(bus, 'selection:sketchObject', selectionMaterial); + } +} \ No newline at end of file diff --git a/web/app/3d/scene/viewer.js b/web/app/3d/scene/viewer.js index 7885edb8..ce02462c 100644 --- a/web/app/3d/scene/viewer.js +++ b/web/app/3d/scene/viewer.js @@ -1,175 +1,26 @@ -import {AXIS} from '../../math/l3space' -import DPR from 'dpr' -import * as mask from '../../utils/mask'; -import {EdgeSelectionManager, SelectionManager, SketchSelectionManager} from '../selection' -import {createArrow} from 'scene/objects/auxiliary'; -import Vector from 'math/vector'; -import {OnTopOfAll} from 'scene/materialMixins'; import SceneSetup from 'scene/sceneSetup'; -import * as SceneGraph from 'scene/sceneGraph'; -import {moveObject3D, setBasisToObject3D} from 'scene/objects/transform'; -import {initPickControl} from "./pickControl"; -export class Viewer { +export default class Viewer { - constructor(bus, container) { - this.bus = bus; + constructor(container) { this.sceneSetup = new SceneSetup(container); - initPickControl(this.sceneSetup.domElement(), this.onPick); - - this.workGroup = SceneGraph.createGroup(); - this.auxGroup = SceneGraph.createGroup(); - SceneGraph.addToGroup(this.sceneSetup.rootGroup, this.workGroup); - SceneGraph.addToGroup(this.sceneSetup.rootGroup, this.auxGroup); - - this.setUpAxises(); - this.setUpBasisGroup(); - this.setUpSelectionManager(); - - this.render(); } render() { this.sceneSetup.render(); } - setUpAxises() { - let arrowLength = 1500; - let createAxisArrow = createArrow.bind(null, arrowLength, 40, 16); - let addAxis = (axis, color) => { - let arrow = createAxisArrow(axis, color, 0.2); - moveObject3D(arrow, axis.scale(-arrowLength * 0.5)); - SceneGraph.addToGroup(this.auxGroup, arrow); - }; - - addAxis(AXIS.X, 0xFF0000); - addAxis(AXIS.Y, 0x00FF00); - addAxis(AXIS.Z, 0x0000FF); - } - - setUpSelectionManager() { - this.selectionMgr = new SelectionManager(this, 0xFAFAD2, 0xFF0000, null); - this.sketchSelectionMgr = new SketchSelectionManager(this, new THREE.LineBasicMaterial({ - color: 0xFF0000, - linewidth: 6 / DPR - })); - this.edgeSelectionMgr = new EdgeSelectionManager(this, new THREE.LineBasicMaterial({ - color: 0xFA8072, - linewidth: 12 / DPR - })); - } - - setUpBasisGroup() { - let length = 200; - let arrowLength = length * 0.2; - let arrowHead = arrowLength * 0.4; - - let _createArrow = createArrow.bind(null, length, arrowLength, arrowHead); - - function createBasisArrow(axis, color) { - return _createArrow(axis, color, 0.4, [OnTopOfAll]); - } - - this.basisGroup = SceneGraph.createGroup(); - let xAxis = createBasisArrow(new Vector(1, 0, 0), 0xFF0000); - let yAxis = createBasisArrow(new Vector(0, 1, 0), 0x00FF00); - SceneGraph.addToGroup(this.basisGroup, xAxis); - SceneGraph.addToGroup(this.basisGroup, yAxis); - } - - updateBasis(basis, depth) { - setBasisToObject3D(this.basisGroup, basis, depth); - } - - showBasis() { - this.workGroup.add(this.basisGroup); - } - - hideBasis() { - if (this.basisGroup.parent !== null) { - this.basisGroup.parent.remove(this.basisGroup); - } - } - lookAt(obj) { this.sceneSetup.lookAt(obj); - this.render(); } - - onPick = e => { - if (e.button !== 0) { - this.handleSolidPick(e); - } else { - this.handlePick(e); - } - }; - handlePick(event) { - this.raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (object, kind) => { - if (kind === PICK_KIND.FACE) { - if (this.selectionMgr.pick(object)) { - return false; - } - } else if (kind === PICK_KIND.SKETCH) { - if (this.sketchSelectionMgr.pick(object)) { - return false; - } - } else if (kind === PICK_KIND.EDGE) { - if (this.edgeSelectionMgr.pick(object)) { - return false; - } - } - return true; - }); + raycast(event, group) { + return this.sceneSetup.raycast(event, group); } - - handleSolidPick(e) { - this.raycastObjects(event, PICK_KIND.FACE, (sketchFace) => { - this.selectionMgr.clear(); - this.bus.notify("solid-pick", sketchFace.solid); - this.render(); - return false; - }); - } - - raycastObjects(event, kind, visitor) { - let pickResults = this.sceneSetup.raycast(event, this.workGroup); - const pickers = [ - (pickResult) => { - if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line && - pickResult.object.__TCAD_SketchObject !== undefined) { - return !visitor(pickResult.object, PICK_KIND.SKETCH); - } - return false; - }, - (pickResult) => { - if (mask.is(kind, PICK_KIND.EDGE) && pickResult.object.__TCAD_EDGE !== undefined) { - return !visitor(pickResult.object, PICK_KIND.EDGE); - } - return false; - }, - (pickResult) => { - if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face && pickResult.face.__TCAD_SceneFace !== undefined) { - const sketchFace = pickResult.face.__TCAD_SceneFace; - return !visitor(sketchFace, PICK_KIND.FACE); - } - return false; - }, - ]; - for (let i = 0; i < pickResults.length; i++) { - const pickResult = pickResults[i]; - for (let picker of pickers) { - if (picker(pickResult)) { - return; - } - } - } + + setCameraMode() { + } + } -export const PICK_KIND = { - FACE: mask.type(1), - SKETCH: mask.type(2), - EDGE: mask.type(3), - VERTEX: mask.type(4) -}; diff --git a/web/app/3d/selection.js b/web/app/3d/selection.js deleted file mode 100644 index f78f1c82..00000000 --- a/web/app/3d/selection.js +++ /dev/null @@ -1,193 +0,0 @@ -import DPR from '../utils/dpr' -import * as stitching from '../brep/stitching' - -class AbstractSelectionManager { - - constructor(viewer) { - this.viewer = viewer; - this.selection = []; - this.viewer.bus.subscribe('craft', () => this.deselectAll()); - } - - contains(face) { - return this.selection.indexOf(face) != -1; - } - - pick(object) { - if (!this.contains(object)) { - this.select(object); - return true; - } - return false; - } - - select() { - throw "AbstractFunctionCall"; - } - - deselectAll() { - throw "AbstractFunctionCall"; - } -} - -export class SketchSelectionManager extends AbstractSelectionManager { - - constructor (viewer, selectionMaterial) { - super(viewer); - this.selectionMaterial = selectionMaterial; - this.defaultMaterials = []; - } - - select(line) { - this._clearSilent(); - this.defaultMaterials.push(line.material); - this.selection.push(line); - line.material = this.selectionMaterial; - this.notify(); - this.viewer.render(); - } - - deselectAll() { - this.clear(); - } - - clear() { - this._clearSilent(); - this.notify(); - this.viewer.render(); - } - - _clearSilent() { - for (let i = 0; i < this.selection.length; i++) { - this.selection[i].material = this.defaultMaterials[i]; - } - this.defaultMaterials.length = 0; - this.selection.length = 0; - } - - notify() { - this.viewer.bus.notify('selection-sketch-object'); - } -} - -export class EdgeSelectionManager extends AbstractSelectionManager { - - constructor (viewer, selectionMaterial) { - super(viewer); - this.selectionMaterial = selectionMaterial; - this.defaultMaterials = []; - } - - select(line) { - this._clearSilent(); - const edge = line.__TCAD_EDGE; - const stitchedCurve = edge.data[stitching.EDGE_CHUNK]; - if (stitchedCurve) { - for (let edgeChunk of stitchedCurve.edges) { - this.mark(edgeChunk.data['scene.edge']); - } - } else { - this.mark(line); - } - this.notify(); - this.viewer.render(); - } - - mark(line) { - this.defaultMaterials.push(line.material); - this.selection.push(line); - line.material = this.selectionMaterial; - } - - deselectAll() { - this.clear(); - } - - clear() { - this._clearSilent(); - this.notify(); - this.viewer.render(); - } - - _clearSilent() { - for (let i = 0; i < this.selection.length; i++) { - this.selection[i].material = this.defaultMaterials[i]; - } - this.defaultMaterials.length = 0; - this.selection.length = 0; - } - - notify() { - //this.viewer.bus.notify('selection-edge'); - } -} - - -export class SelectionManager extends AbstractSelectionManager { - - constructor(viewer, selectionColor, readOnlyColor, defaultColor) { - super(viewer); - this.selectionColor = selectionColor; - this.defaultColor = defaultColor; - this.readOnlyColor = readOnlyColor; - this.planeSelection = []; - } - - select(sceneFace) { - this.clear(); - const group = this.findGroup(sceneFace); - if (group) { - for (var i = 0; i < group.length; i++) { - var face = group[i]; - this.selection.push(face); - setFacesColor(face.meshFaces, this.readOnlyColor); - } - } else { - this.selection.push(sceneFace); - this.viewer.updateBasis(sceneFace.basis(), sceneFace.depth()); - this.viewer.showBasis(); - setFacesColor(sceneFace.meshFaces, this.selectionColor); - } - sceneFace.solid.mesh.geometry.colorsNeedUpdate = true; - this.viewer.bus.notify('selection', sceneFace); - this.viewer.render(); - } - - findGroup(sceneFace) { - if (sceneFace.curvedSurfaces) { - return sceneFace.curvedSurfaces; - } - if (sceneFace.brepFace) { - const stitchedFace = sceneFace.brepFace.data[stitching.FACE_CHUNK]; - if (stitchedFace) { - return stitchedFace.faces.map(f => f.data['scene.face']); - } - } - return undefined; - } - - deselectAll() { - this.clear(); - this.viewer.bus.notify('selection', null); - this.viewer.render(); - } - - clear() { - for (let selectee of this.selection) { - setFacesColor(selectee.meshFaces, this.defaultColor); - selectee.solid.mesh.geometry.colorsNeedUpdate = true; - } - this.viewer.hideBasis(); - this.selection.length = 0; - } -} - -function setFacesColor(faces, color) { - for (let face of faces) { - if (color == null) { - face.color.set(new THREE.Color()); - } else { - face.color.set( color ); - } - } -} diff --git a/web/app/3d/ui/ctrl.js b/web/app/3d/ui/ctrl.js index 09550116..04da9d6b 100644 --- a/web/app/3d/ui/ctrl.js +++ b/web/app/3d/ui/ctrl.js @@ -153,7 +153,7 @@ UI.prototype.getInfoForOp = function(op) { }; UI.prototype.initOperation = function(op) { - var selection = this.app.viewer.selectionMgr.selection; + var selection = this.app.getFaceSelection(); return this.createWizard(op, false, undefined, selection[0]); }; @@ -161,7 +161,7 @@ UI.prototype.createWizardForOperation = function(op) { var initParams = op.params; var face = op.face !== undefined ? this.app.findFace(op.face) : null; if (face != null) { - this.app.viewer.selectionMgr.select(face); + this.app.context.bus.dispatch('selection:face', face); } return this.createWizard(op.type, true, initParams, face); }; diff --git a/web/app/sketcher/tools/manager.js b/web/app/sketcher/tools/manager.js index 74857140..19a1096a 100644 --- a/web/app/sketcher/tools/manager.js +++ b/web/app/sketcher/tools/manager.js @@ -66,7 +66,7 @@ export class ToolManager { switchTool(tool) { this.tool = tool; - this.viewer.bus.notify("tool-change"); + this.viewer.bus.dispatch("tool-change"); } releaseControl() { diff --git a/web/app/sketcher/tools/tool.js b/web/app/sketcher/tools/tool.js index 2a77959a..0549bf0a 100644 --- a/web/app/sketcher/tools/tool.js +++ b/web/app/sketcher/tools/tool.js @@ -26,11 +26,11 @@ export class Tool { keyup(e) {}; sendMessage(text) { - this.viewer.bus.notify('tool-message', text); + this.viewer.bus.dispatch('tool-message', text); }; sendHint(hint) { - this.viewer.bus.notify('tool-hint', hint); + this.viewer.bus.dispatch('tool-hint', hint); }; sendSpecifyPointHint() { diff --git a/web/app/sketcher/viewer2d.js b/web/app/sketcher/viewer2d.js index 772499fc..1b0c37a6 100644 --- a/web/app/sketcher/viewer2d.js +++ b/web/app/sketcher/viewer2d.js @@ -377,7 +377,7 @@ Viewer.prototype.getActiveLayer = function() { Viewer.prototype.setActiveLayer = function(layer) { if (!layer.readOnly) { this._activeLayer = layer; - this.bus.notify("activeLayer"); + this.bus.dispatch("activeLayer"); } }; diff --git a/web/app/ui/toolkit.js b/web/app/ui/toolkit.js index 5eb749a1..d6ef177c 100644 --- a/web/app/ui/toolkit.js +++ b/web/app/ui/toolkit.js @@ -297,7 +297,7 @@ Bus.prototype.unsubscribe = function(event, callback) { } }; -Bus.prototype.notify = function(event, data, sender) { +Bus.prototype.dispatch = function(event, data, sender) { var listenerList = this.listeners[event]; if (listenerList !== undefined) { for (var i = 0; i < listenerList.length; i++) { @@ -326,7 +326,7 @@ Bus.prototype.defineObservable = function(scope, name, initValue, eventName) { get: function() { return observable.value;}, set: function(value) { observable.value = value; - bus.notify(eventName, value); + bus.dispatch(eventName, value); } }); }; diff --git a/web/app/utils/dpr.js b/web/app/utils/dpr.js deleted file mode 100644 index 20f1cf26..00000000 --- a/web/app/utils/dpr.js +++ /dev/null @@ -1 +0,0 @@ -export default (window.devicePixelRatio) ? window.devicePixelRatio : 1;