awv3
Version:
⚡ AWV3 embedded CAD
295 lines (274 loc) • 11.5 kB
JavaScript
import * as THREE from 'three';
import Object3 from '../../../three/object3';
import Ccfuturef from '../ccfuturef';
import Ccref from '../ccref';
import { addCommand, restrictionCommands } from '../command/highlevel';
import { Assign, IdToReal, Variable, Return, Sequence } from '../command/lowlevel';
import { incidence } from '../constraint/type';
import {
drawPointBy_S,
drawLineBy_S_E,
drawArcBy_S_E_C,
drawArcBy_S_E_M,
drawArcBy_S_T_E,
drawCircleBy_C_E,
getTangent,
} from '../geomutils';
import { updateGeomObjectContainer } from '../preview';
import BaseHandler from './base';
const Mode = Object.freeze({ point: 'point', line: 'line', arccenter: 'arccenter', arcmiddle: 'arcmiddle', arctangent: 'arctangent', circle: 'circle' });
export default class DrawHandler extends BaseHandler {
constructor(sketcher, name) {
super(sketcher, name);
this.endPoint = null; // ccref of the last point of the polyline
this.endTangent = null; // normalized THREE.Vector3() tangent of the last edge
this.positions = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
this.preview = new THREE.Group();
this.sketcher.pool.add(this.preview);
this.queue = new PromiseQueue();
this.startPointRestrictions = {};
this.restrictions = {};
this.startPoint = null; // ccref of the first point of the polyline
this.state = 0; // number of element filled in this.positions
this.statesCount = 2; // number of states needed to create an edge
}
destroy() {
this.preview.destroy();
this.sketcher.setCursorCoordinates(undefined);
super.destroy();
}
filterObjectsWithInteraction(object) {
return object.id === this.sketch.id;
}
[Object3.Events.Interaction.Clicked](object, hitObject) {
const ray = hitObject.ray.clone().applyMatrix4(new THREE.Matrix4().getInverse(this.sketch.matrixWorld));
const position = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1)));
return this.queue.run(() => this.afterEdgeAdded(this.click(position)));
}
async afterEdgeAdded(geomParams) {
if (!geomParams) return;
const object = new Ccfuturef(Variable(), geomParams);
const objectStartPoint = object.isCircle() ? object.center : object.startPoint;
const manualConstrCommand = this.endPoint && !this.sketcher.autoconstraintIncremental
? addCommand(this.sketch, {
class: incidence.type,
entities: [this.endPoint, objectStartPoint],
value: {},
})
: null;
const command = Sequence(
Assign(object, addCommand(this.sketch, geomParams)),
...this.sketcher.autoconstraintCommands(object),
...restrictionCommands(this.sketch, objectStartPoint, this.startPointRestrictions),
...restrictionCommands(this.sketch, object, this.restrictions),
manualConstrCommand,
Return(IdToReal(object)),
);
const p = this.sketcher.run(command);
this.sketcher.incrementalSolveConstraints();
if (!object.isLine() && !object.isArc()) {
this.endPolyline();
return p;
}
const ccref = new Ccref(this.sketcher, (await p));
this.startPointRestrictions = {};
this.restrictions = {};
this.startPoint = this.startPoint || ccref.startPoint;
this.endPoint = ccref.endPoint || this.endPoint;
this.endTangent = getTangent(ccref.geomParams, this.endPoint.pos);
this.click(this.endPoint.pos);
}
cancel() {
if (this.state !== 0) this.endPolyline();
else super.cancel();
}
endPolyline() {
this.endPoint = null;
this.endTangent = null;
this.startPointRestrictions = {};
this.restrictions = {};
this.startPoint = null;
this.state = 0;
this.onMouseMove();
}
async closePolyline() {
if (!this.startPoint || !this.endPoint) return;
const geomParams = { start: this.startPoint.pos, end: this.endPoint.pos };
const object = new Ccfuturef(Variable(), geomParams);
const command = Sequence(
Assign(object, addCommand(this.sketch, geomParams)),
addCommand(this.sketch, { class: incidence.type, entities: [this.endPoint, object.startPoint], value: {} }),
addCommand(this.sketch, { class: incidence.type, entities: [object.endPoint, this.startPoint], value: {} }),
);
await this.sketcher.run(command);
this.endPolyline();
}
click(position, doSnapping = true) {
const geomParams = this.move(position, true, doSnapping);
if (!this.handleClick(this.positions[this.state])) return;
if (++this.state === this.statesCount) {
this.state = 0;
return geomParams;
} else if (this.state === 1) {
this.startPointRestrictions = this.restrictions;
this.restrictions = {};
}
}
getPreviewGeomParams() {
if (this.state === 0) return drawPointBy_S(this.positions, this.restrictions);
switch (this.name) {
case Mode.point:
return drawPointBy_S(this.positions, this.restrictions);
case Mode.line:
return drawLineBy_S_E(this.positions, this.restrictions);
case Mode.arccenter:
return drawArcBy_S_E_C(this.positions, this.restrictions);
case Mode.arcmiddle:
return drawArcBy_S_E_M(this.positions, this.restrictions);
case Mode.arctangent:
return drawArcBy_S_T_E([this.positions[0], this.endTangent, this.positions[1]], this.restrictions);
case Mode.circle:
return drawCircleBy_C_E(this.positions, this.restrictions);
}
}
move(position, force, doSnapping = true) {
if (!force && this.queue.isBusy()) return;
if (doSnapping) position.copy(this.doSnapping(position));
this.sketcher.setCursorCoordinates(position);
for (let i = this.state; i < this.positions.length; ++i)
this.positions[i].copy(position);
const previewParams = this.getPreviewGeomParams();
if (this.state === 0) this.positions[0].copy(previewParams.start);
updateGeomObjectContainer(this.preview, {
coordinateSystem: this.sketch.graphics.matrix,
scale: this.sketcher.graphicScale,
...previewParams,
});
return previewParams;
}
onMouseMove() {
this.move(this.getRecentMousePosition());
this.sketcher.refresh();
}
switchMode(mode) {
if (this.state > 1) this.state = 1;
this.statesCount = mode === Mode.point ? 1 : (mode === Mode.arccenter || mode === Mode.arcmiddle) ? 3 : 2;
// handler switching logic in handler/index.js expects mode to be stored in this.name
this.name = mode;
}
consoleComplete(cmd) {
cmd = cmd.trim();
const tokens = cmd === '' ? [] : cmd.split(/\s+/);
const completions = super.consoleComplete(cmd);
if (completions.indexOf(cmd) !== -1) return completions;
return [cmd].concat(
this.name.startsWith('point')
? [`${cmd} ax`, `${cmd} ay`]
: this.name.startsWith('line')
? [`${cmd} l`, `${cmd} a`, `${cmd} x`, `${cmd} y`]
: this.name.startsWith('arc') ? [`${cmd} a`, `${cmd} r`, `${cmd} cw`, `${cmd} ccw`] : [],
completions,
);
}
consoleExecute(cmd) {
if (super.consoleExecute(cmd)) return; // switch handlers
if (cmd === 'cl') return this.closePolyline();
else return this.afterEdgeAdded(this.click(this.getRecentMousePosition(), false));
}
parseRestrictions(cmd) {
let tokens = cmd.trim().split(/\s+/);
this.restrictions = {};
while (tokens.length !== 0) {
let name = tokens.shift();
let value = Number.NaN;
switch (name) {
case 'x':
name = 'xoffset';
value = Number(tokens.shift());
break;
case 'y':
name = 'yoffset';
value = Number(tokens.shift());
break;
case 'ax':
name = 'xabsolute';
value = Number(tokens.shift());
break;
case 'ay':
name = 'yabsolute';
value = Number(tokens.shift());
break;
case 'l':
name = 'length';
value = Number(tokens.shift());
break;
case 'a':
name = 'angle';
value = Math.PI / 180 * Number(tokens.shift());
break;
case 'r':
name = 'radius';
value = Number(tokens.shift());
break;
case 'cw':
name = 'clockwise';
value = true;
break;
case 'ccw':
name = 'clockwise';
value = false;
break;
}
if (!isFinite(value)) continue;
this.restrictions[name] = value;
}
// update priview
this.onMouseMove();
}
doSnapping(position) {
if (this.preview.geomType === 'CC_Point') return this.createSnapper().snapPoint(position).position;
if (this.preview.geomType === 'CC_Line') return this.createSnapper().snapPoint(position).position;
if (this.preview.geomType === 'CC_Arc') return this.createSnapper().snapPoint(position).position;
return position;
}
handleClick(position) {
if (!this.endTangent) {
let variants = [];
// for all lines/arcs on the sketch
for (let object of this.sketch.children) {
if (!object.isLine() && !object.isArc()) continue;
// for their endpoint matching position
for (let end of ['startPoint', 'endPoint']) {
if (!object[end].pos.equals(position)) continue;
const tangent = getTangent(object.geomParams, position);
if (tangent.lengthSq() === 0) continue;
if (end === 'startPoint') tangent.negate();
variants.push(tangent);
}
}
// deny click in arctangent mode if doesn't uniquely determine the tangent
if (this.name === 'arctangent' && variants.length !== 1) return false;
this.endTangent = variants[0] || new THREE.Vector3();
return true;
}
return true;
}
}
class PromiseQueue {
constructor() {
this.nPending = 0;
this.promise = Promise.resolve();
}
run(functor) {
this.promise = (async promise => {
++this.nPending;
await promise;
await functor();
--this.nPending;
})(this.promise);
return this.promise;
}
isBusy() {
return this.nPending > 0;
}
}