remove constraints / arc support

This commit is contained in:
Val Erastov (xibyte) 2020-02-05 23:24:10 -08:00
parent d52d3a565d
commit 95930151ca
16 changed files with 277 additions and 215 deletions

19
modules/gems/traverse.js Normal file
View file

@ -0,0 +1,19 @@
export function dfs(node, children, callback) {
const visited = new Set();
const stack = [];
stack.push(node);
while (stack.length) {
const node = stack.pop();
if (visited.has(node)) {
continue;
}
visited.add(node);
if (callback(node)) {
return;
}
children(node, child => stack.push(child));
}
}

View file

@ -50,7 +50,6 @@
"webpack-dev-server": "^3.7.2"
},
"dependencies": {
"@dagrejs/graphlib": "^2.1.4",
"classnames": "2.2.5",
"clipper-lib": "6.2.1",
"diff-match-patch": "1.0.0",

View file

@ -4,6 +4,7 @@ import {Circle} from "../shapes/circle";
import {Segment} from "../shapes/segment";
import {isInstanceOf, matchAll, matchTypes, sortSelectionByType} from "./matchUtils";
import constraints from "../../../test/cases/constraints";
import {Arc} from "../shapes/arc";
export default [
@ -29,7 +30,10 @@ export default [
{
shortName: 'Tangent',
description: 'Tangent Between Line And Circle',
selectionMatcher: (selection, sortedByType) => matchTypes(sortedByType, Circle, 1, Segment, 1),
selectionMatcher: [
(selection, sortedByType) => matchTypes(sortedByType, Circle, 1, Segment, 1),
(selection, sortedByType) => matchTypes(sortedByType, Arc, 1, Segment, 1),
],
invoke: ctx => {
@ -146,6 +150,28 @@ export default [
}
},
{
shortName: 'Fillet',
description: 'Add a Fillet',
selectionMatcher: (selection) => {
if (matchTypes(selection, EndPoint, 1)) {
} else {
return false;
}
},
invoke: ctx => {
const {viewer} = ctx;
const [point] = viewer.selected;
const constr = new AlgNumConstraint(ConstraintDefinitions.LockPoint, [point]);
constr.initConstants();
editConstraint(ctx, constr, () => viewer.parametricManager.add(constr));
}
},
{
shortName: 'Mirror',
description: 'Mirror Objects',

View file

@ -14,9 +14,19 @@ export function matchAvailableActions(selection) {
if (selection.length) {
for (let action of ALL_CONTEXTUAL_ACTIONS) {
if (action.selectionMatcher(selection, sortedByType)) {
matched.push(action);
if (Array.isArray(action.selectionMatcher)) {
action.selectionMatcher.forEach(matcher => {
if (matcher(selection, sortedByType)) {
matched.push(action);
}
})
} else {
if (action.selectionMatcher(selection, sortedByType)) {
matched.push(action);
}
}
}
}

View file

@ -6,9 +6,9 @@ import {Types} from "../io";
import {Constraints} from "../constraints";
import Vector from "../../../../modules/math/vector";
export const ConstraintDefinitions = indexById([
export const ConstraintDefinitions = {
{
PCoincident : {
id: 'PCoincident',
name: 'Two Points Coincidence',
@ -35,7 +35,7 @@ export const ConstraintDefinitions = indexById([
},
{
TangentLC: {
id: 'TangentLC',
name: 'Line & Circle Tangency',
constants: {
@ -59,22 +59,11 @@ export const ConstraintDefinitions = indexById([
collectPolynomials: (polynomials, [ang, w, cx, cy, r], {inverted}) => {
polynomials.push(new Polynomial(0)
.monomial(1)
.term(cx, POW_1_FN)
.term(ang, COS_FN)
.monomial(1)
.term(cy, POW_1_FN)
.term(ang, SIN_FN)
.monomial(-1)
.term(w, POW_1_FN)
.monomial(- (inverted ? -1 : 1))
.term(r, POW_1_FN)
);
polynomials.push(tangentLCPolynomial(ang, w, cx, cy, r, inverted));
},
},
{
PointOnLine: {
id: 'PointOnLine',
name: 'Point On Line',
@ -103,7 +92,7 @@ export const ConstraintDefinitions = indexById([
},
{
DistancePP: {
id: 'DistancePP',
name: 'Distance Between Two Point',
constants: {
@ -145,7 +134,7 @@ export const ConstraintDefinitions = indexById([
},
{
Angle: {
id: 'Angle',
name: 'Absolute Line Angle',
constants: {
@ -169,7 +158,7 @@ export const ConstraintDefinitions = indexById([
},
},
{
AngleBetween: {
id: 'AngleBetween',
name: 'Angle Between Two Lines',
constants: {
@ -198,7 +187,7 @@ export const ConstraintDefinitions = indexById([
},
},
{
SegmentLength: {
id: 'SegmentLength',
name: 'Segment Length',
constants: {
@ -225,7 +214,7 @@ export const ConstraintDefinitions = indexById([
},
},
{
Polar: {
id: 'Polar',
name: 'Polar Coordinate',
@ -251,7 +240,7 @@ export const ConstraintDefinitions = indexById([
},
{
LockPoint: {
id: 'LockPoint',
name: 'Lock Point',
constants: {
@ -277,8 +266,60 @@ export const ConstraintDefinitions = indexById([
},
},
ArcConsistency: {
id: 'ArcConsistency',
name: 'Arc Consistency',
{
defineParamsScope: ([arc], callback) => {
arc.visitParams(callback);
},
collectPolynomials: (polynomials, [r, ang1, ang2, ax, ay, bx, by, cx, cy]) => {
polynomials.push(new Polynomial()
.monomial(-1).term(ax, POW_1_FN)
.monomial().term(cx, POW_1_FN).monomial().term(r, POW_1_FN).term(ang1, COS_FN) );
polynomials.push(new Polynomial()
.monomial(-1).term(ay, POW_1_FN)
.monomial().term(cy, POW_1_FN).monomial().term(r, POW_1_FN).term(ang1, SIN_FN) );
polynomials.push(new Polynomial()
.monomial(-1).term(bx, POW_1_FN)
.monomial().term(cx, POW_1_FN).monomial().term(r, POW_1_FN).term(ang2, COS_FN) );
polynomials.push(new Polynomial()
.monomial(-1).term(by, POW_1_FN)
.monomial().term(cy, POW_1_FN).monomial().term(r, POW_1_FN).term(ang2, SIN_FN) );
},
},
Fillet: {
id: 'Fillet',
name: 'Fillet Between Two Lines',
constants: {
inverted1: {
type: 'boolean',
initialValue: () => false,
},
inverted2: {
type: 'boolean',
initialValue: () => false,
}
},
defineParamsScope: ([l1, l2, arc], callback) => {
l1.collectParams(callback);
l2.collectParams(callback);
arc.collectParams(callback);
},
collectPolynomials: (polynomials, [ang1, w1, cx1, cy1, r1, ang2, w2, cx2, cy2, r2], {inverted1, inverted2}) => {
polynomials.push(tangentLCPolynomial(ang1, w1, cx1, cy1, r1, inverted1));
polynomials.push(tangentLCPolynomial(ang2, w2, cx2, cy2, r2, inverted2));
},
},
Mirror: {
id: 'Mirror',
name: 'Mirror Objects',
@ -307,7 +348,22 @@ export const ConstraintDefinitions = indexById([
}
]);
};
function tangentLCPolynomial(ang, w, cx, cy, r, inverted) {
return new Polynomial(0)
.monomial(1)
.term(cx, POW_1_FN)
.term(ang, COS_FN)
.monomial(1)
.term(cy, POW_1_FN)
.term(ang, SIN_FN)
.monomial(-1)
.term(w, POW_1_FN)
.monomial(- (inverted ? -1 : 1))
.term(r, POW_1_FN);
}
export class AlgNumConstraint {

View file

@ -14,7 +14,9 @@ export class AlgNumSubSystem {
eliminatedParams = new Map();
polynomials = [];
substitutedParams = [];
substitutedParams = new Map();
substitutionOrder = [];
polyToConstr = new Map();
conflicting = new Set();
@ -66,25 +68,32 @@ export class AlgNumSubSystem {
// this.conflicting.add(constraint);
// this.redundant.add(constraint);
} else {
constraint.objects.forEach(o => o.constraints.add(constraint));
this.updateFullyConstrainedObjects();
}
}
invalidate() {
this.prepare();
this.solveFine();
this.updateFullyConstrainedObjects();
}
removeConstraint(constraint) {
this._removeConstraint(constraint);
this.invalidate();
}
_removeConstraint(constraint) {
let index = this.allConstraints.indexOf(constraint);
if (index !== -1) {
this.allConstraints.splice(index, 1);
this.conflicting.delete(constraint);
this.redundant.delete(constraint);
this.prepare();
this.prepare();
this.solveFine();
this.updateFullyConstrainedObjects();
constraint.objects.forEach(o => o.constraints.delete(constraint));
}
}
isConflicting(constraint) {
return this.conflicting.has(constraint);
}
@ -100,7 +109,8 @@ export class AlgNumSubSystem {
reset() {
this.polynomials = [];
this.substitutedParams = [];
this.substitutedParams.clear();
this.substitutionOrder = [];
this.eliminatedParams.clear();
this.polyToConstr.clear();
this.paramToIsolation.clear();
@ -169,8 +179,9 @@ export class AlgNumSubSystem {
polynomial.substitute(p1, p2, constant);
}
}
this.substitutedParams.push([p1, new Polynomial().monomial(constant).term(p2, POW_1_FN)]);
this.substitute(p1, new Polynomial().monomial(constant).term(p2, POW_1_FN));
this.polynomials[i] = null;
requirePass = true;
} else {
const b = - polynomial.constant / m1.constant;
@ -185,7 +196,7 @@ export class AlgNumSubSystem {
}
transaction.push(polyTransaction);
transaction.push(() => {
this.substitutedParams.push([p1, new Polynomial(b).monomial(constant).term(p2, POW_1_FN)]);
this.substitute(p1, new Polynomial(b).monomial(constant).term(p2, POW_1_FN));
this.polynomials[i] = null;
});
}
@ -208,11 +219,14 @@ export class AlgNumSubSystem {
}
substitute(param, overPolynomial) {
this.substitutionOrder.push(param);
this.substitutedParams.set(param, overPolynomial);
}
prepare() {
this.isDirty = false;
this.reset();
this.evaluatePolynomials();
@ -229,7 +243,7 @@ export class AlgNumSubSystem {
});
console.log('with respect to:');
this.substitutedParams.forEach(([x, expr]) => console.log('X' + x.id + ' = ' + expr.toString()));
this.substitutionOrder.forEach(x => console.log('X' + x.id + ' = ' + this.substitutedParams.get(x).toString()));
}
splitByIsolatedClusters(polynomials) {
@ -330,18 +344,15 @@ export class AlgNumSubSystem {
p.set(val);
}
for (let i = this.substitutedParams.length - 1; i >= 0; i--) {
let [param, expression] = this.substitutedParams[i];
for (let i = this.substitutionOrder.length - 1; i >= 0; i--) {
const param = this.substitutionOrder[i];
const expression = this.substitutedParams.get(param);
param.set(expression.value());
}
}
updateFullyConstrainedObjects() {
const substitutedParamsLookup = new Set();
this.substitutedParams.forEach(([p]) => substitutedParamsLookup.add(p));
this.validConstraints(c => {
c.objects.forEach(obj => {
@ -349,11 +360,7 @@ export class AlgNumSubSystem {
let allLocked = true;
obj.visitParams(p => {
const eliminated = this.eliminatedParams.has(p);
const substituted = substitutedParamsLookup.has(p);
const iso = this.paramToIsolation.get(p);
if (!eliminated && !substituted && (!iso || !iso.fullyConstrained)) {
if (!this.isParamFullyConstrained(p)) {
allLocked = false;
}
});
@ -363,6 +370,27 @@ export class AlgNumSubSystem {
});
}
isParamShallowConstrained(p) {
const iso = this.paramToIsolation.get(p);
return this.eliminatedParams.has(p) || (iso && iso.fullyConstrained);
}
isParamFullyConstrained(p) {
const stack = [p];
while (stack.length) {
const param = stack.pop();
if (!this.isParamShallowConstrained(param)) {
return false;
}
const substitution = this.substitutedParams.get(p);
if (substitution) {
substitution.visitParams(p => stack.push(p));
}
}
return true;
}
}

View file

@ -207,10 +207,6 @@ IO.prototype._loadSketch = function(sketch) {
if (boundaryNeedsUpdate) {
this.addNewBoundaryObjects(boundary, maxEdge);
}
const boundaryLayer = this.viewer.findLayerByName(IO.BOUNDARY_LAYER_NAME);
if (boundaryLayer != null) {
this.linkEndPoints(boundaryLayer.objects);
}
let sketchConstraints = sketch['constraints'];
if (sketchConstraints !== undefined) {
@ -233,23 +229,6 @@ IO.prototype._loadSketch = function(sketch) {
}
};
IO.prototype.linkEndPoints = function(objects) {
const index = HashTable.forVector2d();
for (let obj of objects) {
obj.accept((o) => {
if (o._class == Types.POINT) {
const equalPoint = index.get(o);
if (equalPoint == null) {
index.put(o, o);
} else {
o.linked.push(equalPoint);
equalPoint.linked.push(o);
}
}
return true;
})
}
};
IO.prototype.synchLine = function(skobj, edgeObj) {
skobj.a.x = edgeObj.a.x;

View file

@ -8,24 +8,20 @@ export {Constraints, ParametricManager}
class ParametricManager {
algNumSystem = new AlgNumSubSystem();
constantTable = {};
externalConstantResolver = null;
solveSystems;
$update = stream();
$constraints = this.$update.map(layers => layers.reduce((all, layer) => {
layer.allConstraints.forEach(c => all.push(c));
layer.modifiers.forEach(c => all.push(c));
return all
}, []).sort((c1, c2) => c1.id - c2.id)).remember([]);
$constraints = this.$update
.map(() => [...this.algNumSystem.allConstraints, ...this.algNumSystem.modifiers].sort((c1, c2) => c1.id - c2.id))
.remember([]);
constructor(viewer) {
this.viewer = viewer;
this.reset();
this.viewer.params.define('constantDefinition', null);
this.viewer.params.subscribe('constantDefinition', 'parametricManager', this.onConstantsExternalChange, this)();
this.constantResolver = this.createConstantResolver();
@ -38,7 +34,7 @@ class ParametricManager {
}
reset() {
this.solveSystems = [new AlgNumSubSystem()];
this.algNumSystem = new AlgNumSubSystem();
}
addAlgNum(constr) {
@ -118,7 +114,7 @@ class ParametricManager {
};
notify() {
this.$update.next(this.solveSystems);
this.$update.next();
};
commit() {
@ -126,43 +122,22 @@ class ParametricManager {
this.viewer.refresh();
};
getPlacementLayerIndex(objects) {
for (let i = this.solveSystems.length - 1; i >= 0; --i) {
const system = this.solveSystems[i];
for (let o of objects) {
if (o.solveSystem === system) {
return i;
}
}
}
return 0;
}
_add(constr) {
if (constr.modifier) {
throw 'use addModifier instead';
}
let system = this.solveSystems[this.getPlacementLayerIndex(constr.objects)];
for (let o of constr.objects) {
if (!o.solveSystem) {
o.solveSystem = system;
}
}
system.addConstraint(constr);
this.algNumSystem.addConstraint(constr);
};
refresh() {
this.notify();
this.viewer.refresh();
}
add(constr) {
this.viewer.historyManager.checkpoint();
this._add(constr);
this.notify();
this.viewer.refresh();
this.refresh();
};
addAll(constrs) {
@ -170,86 +145,38 @@ class ParametricManager {
for (let i = 0; i < constrs.length; i++) {
this._add(constrs[i]);
}
this.notify();
this.viewer.refresh();
this.refresh();
};
remove(constr) {
this.viewer.historyManager.checkpoint();
// this.algNumSystem.removeConstraint(constr);
this.algNumSystem.removeConstraint(constr);
this.refresh();
};
removeObjects(objects) {
throw 'implement me';
let toRemove = new Set();
objects.forEach(obj => obj.visitParams(p => {
this.algNumSystem.allConstraints.forEach(c => {
c.objects.forEach(o => {
})
});
let constraints = this.system.paramToConstraintsIndex.get(p);
if (constraints) {
constraints.forEach(constr => {
toRemove.add(constr);
});
}
}));
objects.forEach(obj => {
obj.constraints.forEach(c => this.algNumSystem._removeConstraint(c));
if (obj.layer != null) {
obj.layer.remove(obj);
}
});
if (toRemove.size !== 0) {
// toRemove.forEach(constr => {
// this.cleanUpOnRemove(constr);
// });
let survivedConstraints = [];
this.system.constraints.forEach(c => {
if (!toRemove.has(c)) {
survivedConstraints.push(c);
}
});
this.system.setConstraints(survivedConstraints);
this.notify();
}
if (dependants.length > 0) {
this.removeObjects(dependants);
}
this.algNumSystem.invalidate();
this.refresh();
};
prepare() {
this.solveSystems.forEach(system => system.prepare());
//backward comp.
}
solve(rough) {
this.solveSystems.forEach(system => system.solve(rough));
this.algNumSystem.solve(rough);
}
addModifier(modifier) {
modifier.managedObjects.forEach(o => {
if (o.solveSystem) {
throw 'adding modifiers to already constrained objects isn not supported yet';
}
});
this.viewer.historyManager.checkpoint();
let index = this.getPlacementLayerIndex(modifier.referenceObjects) + 1;
if (index === this.solveSystems.length) {
this.solveSystems.push(new AlgNumSubSystem());
}
const solveSystem = this.solveSystems[index];
solveSystem.modifiers.push(modifier);
modifier.managedObjects.forEach(go => go.solveSystem = solveSystem);
this.notify();
this.viewer.refresh();
this.algNumSystem.addModifier(modifier);
this.refresh();
}
}

View file

@ -1,10 +1,10 @@
import * as utils from '../../utils/utils';
import * as math from '../../math/math';
import Vector from 'math/vector';
import {Ref} from './ref'
import {Constraints} from '../parametric'
import {pointIterator, SketchObject} from './sketch-object';
import {EndPoint} from "./point";
import {SketchObject} from './sketch-object';
import {Param} from "./param";
import {greaterThanConstraint} from "../constr/barriers";
import {MIN_RADIUS} from "./circle";
import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints";
export class Arc extends SketchObject {
@ -17,16 +17,29 @@ export class Arc extends SketchObject {
b.parent = this;
c.parent = this;
this.children.push(a, b, c);
this.r = new Ref(0);
this.r.value = this.distanceA();
this.r.obj = this;
this.r = new Param(MIN_RADIUS + 0.001);
this.r.constraints = [greaterThanConstraint(MIN_RADIUS)];
this.ang1 = new Param(0);
this.ang2 = new Param(0);
this.syncGeometry();
}
syncGeometry() {
this.ang1.set(this.calcStartAng());
this.ang2.set(this.calcEndAng());
this.r.set(this.distanceA());
}
visitParams(callback) {
callback(this.r);
callback(this.ang1);
callback(this.ang2);
this.a.visitParams(callback);
this.b.visitParams(callback);
this.c.visitParams(callback);
callback(this.r);
}
getReferencePoint() {
@ -41,7 +54,7 @@ export class Arc extends SketchObject {
radiusForDrawing() {
return this.distanceA();
return this.r.get();
}
distanceA() {
@ -51,20 +64,28 @@ export class Arc extends SketchObject {
distanceB() {
return math.distance(this.b.x, this.b.y, this.c.x, this.c.y);
}
getStartAngle() {
calcStartAng() {
return Math.atan2(this.a.y - this.c.y, this.a.x - this.c.x);
}
calcEndAng() {
return Math.atan2(this.b.y - this.c.y, this.b.x - this.c.x);
}
getStartAngle() {
return this.ang1.get();
}
getEndAngle() {
return Math.atan2(this.b.y - this.c.y, this.b.x - this.c.x);
return this.ang2.get();
}
drawImpl(ctx, scale) {
ctx.beginPath();
var r = this.radiusForDrawing();
var startAngle = this.getStartAngle();
var endAngle;
let r = this.radiusForDrawing();
let startAngle = this.getStartAngle();
let endAngle;
if (math.areEqual(this.a.x, this.b.x, math.TOLERANCE) &&
math.areEqual(this.a.y, this.b.y, math.TOLERANCE)) {
endAngle = startAngle + 2 * Math.PI;
@ -72,9 +93,9 @@ export class Arc extends SketchObject {
endAngle = this.getEndAngle();
}
ctx.arc(this.c.x, this.c.y, r, startAngle, endAngle);
var distanceB = this.distanceB();
let distanceB = this.distanceB();
if (Math.abs(r - distanceB) * scale > 1) {
var adj = r / distanceB;
let adj = r / distanceB;
ctx.save();
ctx.setLineDash([7 / scale]);
ctx.lineTo(this.b.x, this.b.y);
@ -124,9 +145,10 @@ export class Arc extends SketchObject {
}
stabilize(viewer) {
this.r.set(this.distanceA());
viewer.parametricManager._add(new Constraints.P2PDistanceV(this.b, this.c, this.r));
viewer.parametricManager._add(new Constraints.P2PDistanceV(this.a, this.c, this.r));
this.syncGeometry();
const constr = new AlgNumConstraint(ConstraintDefinitions.ArcConsistency, [this]);
constr.internal = true;
viewer.parametricManager._add(constr);
}
copy() {

View file

@ -3,7 +3,7 @@ import {SketchObject} from './sketch-object'
import {Param} from "./param";
import {greaterThanConstraint} from "../constr/barriers";
const MIN_RADIUS = 100;
export const MIN_RADIUS = 100;
export class Circle extends SketchObject {

View file

@ -1,6 +1,5 @@
import {SketchObject} from './sketch-object'
import {DrawPoint} from './draw-utils'
import {Generator} from '../id-generator'
import Vector from 'math/vector';
import {Param} from "./param";

View file

@ -58,8 +58,8 @@ export class Segment extends SketchObject {
const c2 = new AlgNumConstraint(ConstraintDefinitions.Polar, [this, this.a, this.b]);
c1.internal = true;
c2.internal = true;
viewer.parametricManager.addAlgNum(c1);
viewer.parametricManager.addAlgNum(c2);
viewer.parametricManager._add(c1);
viewer.parametricManager._add(c2);
}
recoverIfNecessary() {

View file

@ -2,6 +2,8 @@ import {Generator} from '../id-generator'
import {Shape} from './shape'
import {Types} from '../io';
import {Styles} from "../styles";
import {dfs} from 'gems/traverse';
import {ConstraintDefinitions} from "../constr/ANConstraints";
export class SketchObject extends Shape {
constructor() {
@ -9,11 +11,9 @@ export class SketchObject extends Shape {
this.id = Generator.genID();
this.marked = null;
this.children = [];
this.linked = [];
this.layer = null;
this.fullyConstrained = false;
this.managedBy = null;
this.solveSystem = null;
this.constraints = new Set();
}
normalDistance(aim, scale) {
@ -43,32 +43,28 @@ export class SketchObject extends Shape {
recoverIfNecessary() {
return false;
}
isAuxOrLinkedTo() {
if (!!this.aux) {
return true;
}
for (var i = 0; i < this.linked.length; ++i) {
if (!!this.linked[i].aux) {
return true;
visitLinked(cb) {
dfs(this, (obj, chCb) => obj.constraints.forEach(c => {
if (c.schema.id === ConstraintDefinitions.PCoincident.id) {
c.objects.forEach(chCb);
}
}
return false;
}), cb);
}
_translate(dx, dy, translated) {
translated[this.id] = 'x';
for (var i = 0; i < this.linked.length; ++i) {
if (translated[this.linked[i].id] != 'x') {
this.linked[i]._translate(dx, dy, translated);
this.visitLinked(l => {
if (translated[l.id] !== 'x') {
l._translate(dx, dy, translated);
}
}
});
this.translateImpl(dx, dy);
};
translate(dx, dy) {
// this.translateImpl(dx, dy);
if (this.isAuxOrLinkedTo()) {
if (this.fullyConstrained) {
return;
}
this._translate(dx, dy, {});

View file

@ -34,6 +34,7 @@ export class AddArcTool extends Tool {
} else {
this.demoSecondPoint();
}
this.arc.syncGeometry();
this.viewer.snap(p.x, p.y, [this.arc.a, this.arc.b, this.arc.c]);
this.viewer.refresh();
@ -80,7 +81,7 @@ export class AddArcTool extends Tool {
finishStep() {
this.arc.stabilize(this.viewer);
this.pointPicked(this.arc.b.x, this.arc.b.y);
this.viewer.refresh();
this.viewer.parametricManager.refresh();
this.viewer.toolManager.releaseControl();
}

View file

@ -75,7 +75,7 @@ export class DragTool extends Tool {
getParamsToLock() {
var params = [];
this.obj.accept(function (obj) {
if (obj._class === 'TCAD.TWO.EndPoint' && !obj.isAuxOrLinkedTo()) {
if (obj._class === 'TCAD.TWO.EndPoint' && !obj.fullyConstrained) {
params.push(obj._x);
params.push(obj._y);
}

View file

@ -38,7 +38,7 @@ export class BasePanTool extends Tool {
}
}
this.viewer.select([toSelect], true);
if (!toSelect.isAuxOrLinkedTo()) {
if (!toSelect.fullyConstrained) {
const tool = GetShapeEditTool(this.viewer, toSelect, e.altKey);
tool.mousedown(e);
this.viewer.toolManager.switchTool(tool);