UI modularization / decouple 3D rendering

This commit is contained in:
Val Erastov 2018-01-05 01:41:15 -08:00
parent e8be3fe473
commit 046a10fe16
37 changed files with 631 additions and 436 deletions

72
modules/bus/index.js Normal file
View file

@ -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);
}
}

61
modules/bus/store.js Normal file
View file

@ -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];
}
}

29
modules/gems/iterables.js Normal file
View file

@ -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]
}

View file

@ -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) { export function createTransparentPhongMaterial(color, opacity) {
return new MeshPhongMaterial({ return new MeshPhongMaterial({
@ -13,4 +14,9 @@ export function createTransparentPhongMaterial(color, opacity) {
}); });
} }
export function createLineMaterial(color, linewidth) {
return new LineBasicMaterial({
color,
linewidth: linewidth / DPR
});
}

View file

@ -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;
}

View file

@ -1,17 +1,17 @@
export function checkForSelectedFaces(amount) { export function checkForSelectedFaces(amount) {
return (state, app) => { return (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount; state.enabled = app.getFaceSelection().length >= amount;
if (!state.enabled) { 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) { export function checkForSelectedSolids(amount) {
return (state, app) => { return (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount; state.enabled = app.getFaceSelection().length >= amount;
if (!state.enabled) { 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';
} }
} }
} }

View file

@ -37,7 +37,7 @@ ActionManager.prototype.notify = function(event) {
if (actions != undefined) { if (actions != undefined) {
for (let action of actions) { for (let action of actions) {
this.updateAction(action); this.updateAction(action);
this.app.bus.notify('action.update.' + action.id, action.state); this.app.bus.dispatch('action.update.' + action.id, action.state);
} }
} }
}; };

View file

@ -6,7 +6,6 @@ import * as math from '../math/math'
import {Matrix3, AXIS, ORIGIN} from '../math/l3space' import {Matrix3, AXIS, ORIGIN} from '../math/l3space'
import Counters from './counters' import Counters from './counters'
import {MeshSceneSolid} from './scene/wrappers/meshSceneObject' import {MeshSceneSolid} from './scene/wrappers/meshSceneObject'
import DPR from '../utils/dpr'
export const FACE_COLOR = 0xB0C4DE; export const FACE_COLOR = 0xB0C4DE;

View file

@ -43,7 +43,7 @@ export class PreviewWizard extends Wizard {
} }
dispose() { dispose() {
this.app.bus.unsubscribe('refreshSketch', this.onSketchUpdate); this.app.bus.unSubscribe('refreshSketch', this.onSketchUpdate);
this.destroyPreviewObject(); this.destroyPreviewObject();
this.app.viewer.workGroup.remove(this.previewGroup); this.app.viewer.workGroup.remove(this.previewGroup);
this.app.viewer.render(); this.app.viewer.render();

View file

@ -122,31 +122,31 @@ export class Wizard {
} }
createFormField(name, label, type, params, initValue) { 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); const number = tk.config(new tk.Number(label, initValue, params.step, params.round), params);
number.input.on('t-change', () => this.onUIChange(name)); number.input.on('t-change', () => this.onUIChange(name));
return Field.fromInput(number, Field.TEXT_TO_NUMBER_COERCION); return Field.fromInput(number, Field.TEXT_TO_NUMBER_COERCION);
} else if (type == 'choice') { } else if (type === 'choice') {
const ops = params.options; const ops = params.options;
const radio = new tk.InlineRadio(ops, ops, ops.indexOf(initValue)); const radio = new tk.InlineRadio(ops, ops, ops.indexOf(initValue));
radio.root.find('input[type=radio]').on('change', () => { radio.root.find('input[type=radio]').on('change', () => {
this.onUIChange(name); this.onUIChange(name);
}); });
return new Field(radio, () => radio.getValue(), (v) => radio.setValue(v)); return new Field(radio, () => radio.getValue(), (v) => radio.setValue(v));
} else if (type == 'face') { } else if (type === 'face') {
return selectionWidget(name, label, initValue, this.app.viewer.selectionMgr, (selection) => selection.id); return selectionWidget(name, label, initValue, this.app.context.bus, 'selection:face',(selection) => selection.id);
} else if (type == 'sketch.segment') { } else if (type === 'sketch.segment') {
return selectionWidget(name, label, initValue, this.app.viewer.sketchSelectionMgr, (selection) => selection.__TCAD_SketchObject.id); 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); const obj = new tk.Text(label, initValue);
obj.input.on('change', () => this.onUIChange(name)); obj.input.on('change', () => this.onUIChange(name));
return Field.fromInput(obj, undefined, (objId) => { return Field.fromInput(obj, undefined, (objId) => {
if (objId === CURRENT_SELECTION) { if (objId === CURRENT_SELECTION) {
let selection = selectionManager.selection[0]; let selection = bus.state[selectionKey][0];
return selection ? toId(selection) : ''; return selection ? toId(selection) : '';
} else { } else {
return objId; return objId;

View file

@ -12,8 +12,8 @@ export function Craft(app) {
if (this._historyPointer === value) return; if (this._historyPointer === value) return;
this._historyPointer = value; this._historyPointer = value;
this.reset(this.history.slice(0, this._historyPointer)); this.reset(this.history.slice(0, this._historyPointer));
this.app.bus.notify('craft'); this.app.bus.dispatch('craft');
this.app.bus.notify('historyPointer'); this.app.bus.dispatch('historyPointer');
this.app.viewer.render(); this.app.viewer.render();
} }
}); });
@ -30,7 +30,7 @@ Craft.prototype.remove = function(modificationIndex) {
if (this.historyPointer >= history.length) { if (this.historyPointer >= history.length) {
this.finishHistoryEditing(); this.finishHistoryEditing();
} else { } else {
this.app.bus.notify('historyShrink'); this.app.bus.dispatch('historyShrink');
} }
}; };
@ -38,8 +38,8 @@ Craft.prototype.loadHistory = function(history) {
this.history = history; this.history = history;
this._historyPointer = history.length; this._historyPointer = history.length;
this.reset(history); this.reset(history);
this.app.bus.notify('craft'); this.app.bus.dispatch('craft');
this.app.bus.notify('historyPointer'); this.app.bus.dispatch('historyPointer');
this.app.viewer.render(); this.app.viewer.render();
}; };
@ -87,7 +87,7 @@ Craft.prototype.modifyInternal = function(request) {
this.app.viewer.workGroup.add(solid.cadGroup); this.app.viewer.workGroup.add(solid.cadGroup);
} }
this.app.bus.notify('solid-list', { this.app.bus.dispatch('solid-list', {
solids: this.solids, solids: this.solids,
needRefresh: result.created needRefresh: result.created
}); });
@ -103,7 +103,7 @@ Craft.prototype.modify = function(request, overriding) {
} }
this.history[this._historyPointer] = request; this.history[this._historyPointer] = request;
this._historyPointer ++; this._historyPointer ++;
this.app.bus.notify('craft'); this.app.bus.dispatch('craft');
this.app.bus.notify('historyPointer'); this.app.bus.dispatch('historyPointer');
this.app.viewer.render(); this.app.viewer.render();
}; };

View file

@ -16,7 +16,7 @@ export function PlaneWizard(app, initParams) {
relativeToFaceId: '' relativeToFaceId: ''
}; };
this.selectionListener = () => { this.selectionListener = () => {
const face = this.app.viewer.selectionMgr.selection[0]; const face = this.getFirstSelectedFace();
if (face) { if (face) {
this.ui.relativeToFace.input.val(face.id); this.ui.relativeToFace.input.val(face.id);
this.synch(); this.synch();

View file

@ -145,7 +145,7 @@ RevolveWizard.prototype.createRequest = function(done) {
}; };
RevolveWizard.prototype.dispose = function() { 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); OpWizard.prototype.dispose.call(this);
}; };

View file

@ -1,4 +1,4 @@
import DPR from '../../../../utils/dpr' import DPR from 'dpr'
import * as tk from '../../../../ui/toolkit' import * as tk from '../../../../ui/toolkit'
const IMAGINE_MATERIAL = new THREE.LineBasicMaterial({ const IMAGINE_MATERIAL = new THREE.LineBasicMaterial({

View file

@ -1,7 +1,7 @@
import {checkForSelectedFaces} from './actions/action-helpers' import {checkForSelectedFaces} from './actions/action-helpers'
import {nurbsToThreeGeom, triangulateToThree} from './scene/wrappers/brepSceneObject' import {nurbsToThreeGeom, triangulateToThree} from './scene/wrappers/brepSceneObject'
import {createSolidMaterial} from './scene/wrappers/sceneObject' import {createSolidMaterial} from './scene/wrappers/sceneObject'
import DPR from '../utils/dpr' import DPR from 'dpr'
import Vector from 'math/vector'; import Vector from 'math/vector';
import {NurbsCurve} from "../brep/geom/impl/nurbs"; import {NurbsCurve} from "../brep/geom/impl/nurbs";
import * as ui from '../ui/ui'; import * as ui from '../ui/ui';
@ -242,7 +242,7 @@ const DebugActions = {
listens: ['selection'], listens: ['selection'],
update: checkForSelectedFaces(1), update: checkForSelectedFaces(1),
invoke: (app) => { invoke: (app) => {
var s = app.viewer.selectionMgr.selection[0]; var s = app.getFirstSelectedFace();
console.log(JSON.stringify({ console.log(JSON.stringify({
polygons: s.csgGroup.polygons, polygons: s.csgGroup.polygons,
basis: s._basis basis: s._basis
@ -257,7 +257,7 @@ const DebugActions = {
listens: ['selection'], listens: ['selection'],
update: checkForSelectedFaces(1), update: checkForSelectedFaces(1),
invoke: (app) => { invoke: (app) => {
console.log(app.viewer.selectionMgr.selection[0].id); console.log(app.getFirstSelectedFace().id);
} }
}, },
@ -268,7 +268,7 @@ const DebugActions = {
listens: ['selection'], listens: ['selection'],
update: checkForSelectedFaces(1), update: checkForSelectedFaces(1),
invoke: (app) => { 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 sketch = JSON.parse(localStorage.getItem(app.faceStorageKey(faceId)));
const layers = sketch.layers.filter(l => l.name != '__bounds__'); const layers = sketch.layers.filter(l => l.name != '__bounds__');
const data = []; const data = [];

View file

@ -1,6 +1,6 @@
import '../../../modules/scene/utils/vectorThreeEnhancement' import '../../../modules/scene/utils/vectorThreeEnhancement'
import '../utils/three-loader' import '../utils/three-loader'
import {Bus} from '../ui/toolkit' import Bus from 'bus'
import {Viewer} from './scene/viewer' import {Viewer} from './scene/viewer'
import {UI} from './ui/ctrl' import {UI} from './ui/ctrl'
import TabSwitcher from './ui/tab-switcher' 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 {Plane} from "../brep/geom/impl/plane";
import {enclose} from "../brep/brep-enclose"; import {enclose} from "../brep/brep-enclose";
// import {createSphere, rayMarchOntoCanvas, sdfIntersection, sdfSolid, sdfSubtract, sdfTransform, sdfUnion} from "../hds/sdf"; // import {createSphere, rayMarchOntoCanvas, sdfIntersection, sdfSolid, sdfSubtract, sdfTransform, sdfUnion} from "../hds/sdf";
import Plugins from './plugins';
function App() { function App() {
this.id = this.processHints(); this.id = this.processHints();
@ -38,7 +39,11 @@ function App() {
this.actionManager = new ActionManager(this); this.actionManager = new ActionManager(this);
this.inputManager = new InputManager(this); this.inputManager = new InputManager(this);
this.state = this.createState(); 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.actionManager.registerActions(AllActions);
this.tabSwitcher = new TabSwitcher($('#tab-switcher'), $('#view-3d')); this.tabSwitcher = new TabSwitcher($('#tab-switcher'), $('#view-3d'));
this.controlBar = new ControlBar(this, $('#control-bar')); this.controlBar = new ControlBar(this, $('#control-bar'));
@ -67,7 +72,7 @@ function App() {
var sketchFace = app.findFace(sketchFaceId); var sketchFace = app.findFace(sketchFaceId);
if (sketchFace != null) { if (sketchFace != null) {
app.refreshSketchOnFace(sketchFace); app.refreshSketchOnFace(sketchFace);
app.bus.notify('refreshSketch'); app.bus.dispatch('refreshSketch');
app.viewer.render(); 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) { App.prototype.addShellOnScene = function(shell, skin) {
const sceneSolid = new BREPSceneSolid(shell, undefined, skin); const sceneSolid = new BREPSceneSolid(shell, undefined, skin);
this.viewer.workGroup.add(sceneSolid.cadGroup); this.viewer.workGroup.add(sceneSolid.cadGroup);
@ -320,7 +351,7 @@ App.prototype.lookAtSolid = function(solidId) {
App.prototype.createState = function() { App.prototype.createState = function() {
const state = {}; const state = {};
this.bus.defineObservable(state, 'showSketches', true); // this.bus.defineObservable(state, 'showSketches', true);
return state; return state;
}; };
@ -604,7 +635,7 @@ App.prototype.cut = function() {
App.prototype.refreshSketches = function() { App.prototype.refreshSketches = function() {
this._refreshSketches(); this._refreshSketches();
this.bus.notify('refreshSketch'); this.bus.dispatch('refreshSketch');
this.viewer.render(); this.viewer.render();
}; };

6
web/app/3d/plugins.js Normal file
View file

@ -0,0 +1,6 @@
import * as ScenePlugin from './scene/scenePlugin';
import * as SelectionMarkerPlugin from './scene/selectionMarker/selectionMarkerPlugin';
export default [
ScenePlugin, SelectionMarkerPlugin
]

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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 );
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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 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) { constructor(container) {
this.bus = bus;
this.sceneSetup = new SceneSetup(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() { render() {
this.sceneSetup.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) { lookAt(obj) {
this.sceneSetup.lookAt(obj); this.sceneSetup.lookAt(obj);
this.render();
} }
onPick = e => { raycast(event, group) {
if (e.button !== 0) { return this.sceneSetup.raycast(event, group);
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;
});
} }
handleSolidPick(e) { setCameraMode() {
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;
}
}
}
}
} }
export const PICK_KIND = {
FACE: mask.type(1),
SKETCH: mask.type(2),
EDGE: mask.type(3),
VERTEX: mask.type(4)
};

View file

@ -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 );
}
}
}

View file

@ -153,7 +153,7 @@ UI.prototype.getInfoForOp = function(op) {
}; };
UI.prototype.initOperation = 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]); return this.createWizard(op, false, undefined, selection[0]);
}; };
@ -161,7 +161,7 @@ UI.prototype.createWizardForOperation = function(op) {
var initParams = op.params; var initParams = op.params;
var face = op.face !== undefined ? this.app.findFace(op.face) : null; var face = op.face !== undefined ? this.app.findFace(op.face) : null;
if (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); return this.createWizard(op.type, true, initParams, face);
}; };

View file

@ -66,7 +66,7 @@ export class ToolManager {
switchTool(tool) { switchTool(tool) {
this.tool = tool; this.tool = tool;
this.viewer.bus.notify("tool-change"); this.viewer.bus.dispatch("tool-change");
} }
releaseControl() { releaseControl() {

View file

@ -26,11 +26,11 @@ export class Tool {
keyup(e) {}; keyup(e) {};
sendMessage(text) { sendMessage(text) {
this.viewer.bus.notify('tool-message', text); this.viewer.bus.dispatch('tool-message', text);
}; };
sendHint(hint) { sendHint(hint) {
this.viewer.bus.notify('tool-hint', hint); this.viewer.bus.dispatch('tool-hint', hint);
}; };
sendSpecifyPointHint() { sendSpecifyPointHint() {

View file

@ -377,7 +377,7 @@ Viewer.prototype.getActiveLayer = function() {
Viewer.prototype.setActiveLayer = function(layer) { Viewer.prototype.setActiveLayer = function(layer) {
if (!layer.readOnly) { if (!layer.readOnly) {
this._activeLayer = layer; this._activeLayer = layer;
this.bus.notify("activeLayer"); this.bus.dispatch("activeLayer");
} }
}; };

View file

@ -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]; var listenerList = this.listeners[event];
if (listenerList !== undefined) { if (listenerList !== undefined) {
for (var i = 0; i < listenerList.length; i++) { 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;}, get: function() { return observable.value;},
set: function(value) { set: function(value) {
observable.value = value; observable.value = value;
bus.notify(eventName, value); bus.dispatch(eventName, value);
} }
}); });
}; };

View file

@ -1 +0,0 @@
export default (window.devicePixelRatio) ? window.devicePixelRatio : 1;