mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-13 20:02:41 +01:00
204 lines
5.6 KiB
JavaScript
204 lines
5.6 KiB
JavaScript
import * as mask from 'gems/mask'
|
|
import {getAttribute, setAttribute} from 'scene/objectData';
|
|
import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL} from '../entites';
|
|
import {state} from 'lstream';
|
|
import {distinctState} from '../../../../../modules/lstream';
|
|
|
|
export const PICK_KIND = {
|
|
FACE: mask.type(1),
|
|
SKETCH: mask.type(2),
|
|
EDGE: mask.type(3)
|
|
};
|
|
|
|
const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];
|
|
|
|
const DEFAULT_SELECTION_MODE = Object.freeze({
|
|
shell: false,
|
|
vertex: false,
|
|
face: true,
|
|
edge: true,
|
|
sketchObject: true,
|
|
datum: true
|
|
});
|
|
|
|
export function activate(context) {
|
|
const {services, streams} = context;
|
|
initStateAndServices(context);
|
|
let domElement = services.viewer.sceneSetup.domElement();
|
|
|
|
domElement.addEventListener('mousedown', mousedown, false);
|
|
domElement.addEventListener('mouseup', mouseup, false);
|
|
|
|
let mouseState = {
|
|
startX: 0,
|
|
startY: 0
|
|
};
|
|
|
|
function mousedown(e) {
|
|
mouseState.startX = e.offsetX;
|
|
mouseState.startY = e.offsetY;
|
|
}
|
|
|
|
function mouseup(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) {
|
|
if (e.button !== 0) {
|
|
handleSolidPick(e);
|
|
} else {
|
|
handlePick(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function selected(selection, object) {
|
|
return selection.value.indexOf(object) !== -1;
|
|
}
|
|
|
|
function handlePick(event) {
|
|
raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (view, kind) => {
|
|
let selectionMode = streams.pickControl.selectionMode.value;
|
|
let modelId = view.model.id;
|
|
if (kind === PICK_KIND.FACE) {
|
|
if (selectionMode.shell) {
|
|
if (dispatchSelection(streams.selection.shell, view.model.shell.id, event)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (dispatchSelection(streams.selection.face, modelId, event)) {
|
|
services.cadScene.showGlobalCsys(view.model.csys);
|
|
return false;
|
|
}
|
|
}
|
|
} else if (kind === PICK_KIND.SKETCH) {
|
|
if (dispatchSelection(streams.selection.sketchObject, modelId, event)) {
|
|
return false;
|
|
}
|
|
} else if (kind === PICK_KIND.EDGE) {
|
|
if (dispatchSelection(streams.selection.edge, modelId, event)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function dispatchSelection(selection, selectee, event) {
|
|
if (selected(selection, selectee)) {
|
|
return false;
|
|
}
|
|
let multiMode = event.shiftKey;
|
|
selection.update(value => multiMode ? [...value, selectee] : [selectee]);
|
|
return true;
|
|
}
|
|
|
|
function handleSolidPick(e) {
|
|
raycastObjects(e, PICK_KIND.FACE, (sketchFace) => {
|
|
streams.selection.solid.next([sketchFace.solid]);
|
|
services.viewer.render();
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function raycastObjects(event, kind, visitor) {
|
|
let pickResults = services.viewer.raycast(event, services.cadScene.workGroup.children);
|
|
const pickers = [
|
|
(pickResult) => {
|
|
if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line) {
|
|
let sketchObjectV = getAttribute(pickResult.object, SKETCH_OBJECT);
|
|
if (sketchObjectV) {
|
|
return !visitor(sketchObjectV, PICK_KIND.SKETCH);
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
(pickResult) => {
|
|
if (mask.is(kind, PICK_KIND.EDGE)) {
|
|
let edgeV = getAttribute(pickResult.object, EDGE);
|
|
if (edgeV) {
|
|
return !visitor(edgeV, PICK_KIND.EDGE);
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
(pickResult) => {
|
|
if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face) {
|
|
let faceV = getAttribute(pickResult.face, FACE);
|
|
if (faceV) {
|
|
return !visitor(faceV, 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 function defineStreams({streams}) {
|
|
streams.selection = {
|
|
};
|
|
SELECTABLE_ENTITIES.forEach(entity => {
|
|
streams.selection[entity] = state([]);
|
|
});
|
|
streams.pickControl = {
|
|
selectionMode: distinctState(DEFAULT_SELECTION_MODE)
|
|
}
|
|
}
|
|
|
|
function initStateAndServices({streams, services}) {
|
|
|
|
streams.pickControl.selectionMode.pairwise().attach(([prev, curr]) => {
|
|
SELECTABLE_ENTITIES.forEach(entity => {
|
|
if (prev[entity] !== curr[entity]) {
|
|
streams.selection[entity].next([]);
|
|
}
|
|
});
|
|
});
|
|
|
|
function setSelectionMode(selectionMode) {
|
|
streams.pickControl.selectionMode.next({
|
|
...DEFAULT_SELECTION_MODE, ...selectionMode
|
|
});
|
|
}
|
|
|
|
function switchToDefaultSelectionMode() {
|
|
streams.pickControl.selectionMode.next(DEFAULT_SELECTION_MODE);
|
|
}
|
|
|
|
services.pickControl = {
|
|
setSelectionMode, switchToDefaultSelectionMode
|
|
};
|
|
|
|
services.selection = {};
|
|
|
|
SELECTABLE_ENTITIES.forEach(entity => {
|
|
let entitySelectApi = {
|
|
objects: [],
|
|
single: undefined
|
|
};
|
|
services.selection[entity] = entitySelectApi;
|
|
let selectionState = streams.selection[entity];
|
|
selectionState.attach(selection => {
|
|
entitySelectApi.objects = selection.map(id => services.cadRegistry.findEntity(entity, id));
|
|
entitySelectApi.single = entitySelectApi.objects[0];
|
|
});
|
|
entitySelectApi.select = selection => selectionState.value = selection;
|
|
});
|
|
|
|
streams.craft.models.attach(() => {
|
|
withdrawAll(streams.selection)
|
|
});
|
|
}
|
|
|
|
export function withdrawAll(selectionStreams) {
|
|
Object.values(selectionStreams).forEach(stream => stream.next([]))
|
|
}
|