polynomial analysis

This commit is contained in:
Val Erastov (xibyte) 2020-01-21 00:35:04 -08:00
parent 58f3ac1846
commit da613a082e
14 changed files with 434 additions and 97 deletions

View file

@ -34,5 +34,6 @@ export function compositeFn() {
funcs.push(fn); funcs.push(fn);
} }
} }
fn.push = push;
return fn return fn
} }

View file

@ -1,6 +1,9 @@
import numeric from 'numeric'; import numeric from 'numeric';
import {_vec, _matrix} from './math' import {_vec, _matrix} from './math'
const SUCCESS = 1, ITER_LIMIT = 2, SMALL_DELTA = 3, SMALL_STEP = 4, DIVERGENCE = 5, INVALID_STATE = 6;
//Added strong wolfe condition to numeric's uncmin //Added strong wolfe condition to numeric's uncmin
export function fmin_bfgs(f,x0,tol,gradient,maxit,callback,options) { export function fmin_bfgs(f,x0,tol,gradient,maxit,callback,options) {
var grad = numeric.gradient; var grad = numeric.gradient;
@ -256,9 +259,10 @@ var inv = function inv(A) {
}; };
var _result = function(evalCount, error, returnCode) { var _result = function(evalCount, error, returnCode) {
this.evalCount = evalCount; return {
this.error = error; evalCount, error, returnCode,
this.returnCode = returnCode; success: returnCode === SUCCESS
};
}; };
var dog_leg = function (subsys, rough) { var dog_leg = function (subsys, rough) {
@ -279,7 +283,7 @@ var dog_leg = function (subsys, rough) {
var csize = subsys.constraints.length; var csize = subsys.constraints.length;
if (xsize == 0) { if (xsize == 0) {
return new _result(0, 0, 1); return _result(0, 0, 1);
} }
var vec = _vec; var vec = _vec;
@ -330,8 +334,6 @@ var dog_leg = function (subsys, rough) {
var iter = 0, returnCode = 0; var iter = 0, returnCode = 0;
//var log = []; //var log = [];
var SUCCESS = 1, ITER_LIMIT = 2, SMALL_DELTA = 3, SMALL_STEP = 4, DIVERGENCE = 5, INVALID_STATE = 6;
while (returnCode === 0) { while (returnCode === 0) {
optim.DEBUG_HANDLER(iter, err); optim.DEBUG_HANDLER(iter, err);
@ -457,7 +459,7 @@ var dog_leg = function (subsys, rough) {
} }
//log.push(returnCode); //log.push(returnCode);
//window.___log(log); //window.___log(log);
return new _result(iter, err, returnCode); return _result(iter, err, returnCode);
}; };
var cg = function(A, x, b, tol, maxIt) { var cg = function(A, x, b, tol, maxIt) {

View file

@ -105,6 +105,46 @@ export default [
pm.refresh(); pm.refresh();
}); });
} }
},
{
shortName: 'Length',
description: 'Segment Length',
selectionMatcher: (selection) => matchAll(selection, Segment, 1),
invoke: ctx => {
const {viewer} = ctx;
const [firstSegment, ...others] = viewer.selected;
const firstConstr = new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [firstSegment]);
firstConstr.initConstants();
editConstraint(ctx, firstConstr, () => {
const pm = viewer.parametricManager;
pm.algnNumSystem.addConstraint(firstConstr);
for (let other of others) {
pm.algnNumSystem.addConstraint(new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [other], {...firstConstr.constants}));
}
pm.refresh();
});
}
},
{
shortName: 'Lock',
description: 'Lock Point',
selectionMatcher: (selection) => matchTypes(selection, EndPoint, 1),
invoke: ctx => {
const {viewer} = ctx;
const [point] = viewer.selected;
const constr = new AlgNumConstraint(ConstraintDefinitions.LockPoint, [point]);
constr.initConstants();
editConstraint(ctx, constr, () => viewer.parametricManager.addAlgNum(constr));
}
} }
]; ];

View file

@ -111,18 +111,13 @@ export const ConstraintDefinitions = indexById([
initialValue: (constraint) => { initialValue: (constraint) => {
const [a, b] = constraint.objects; const [a, b] = constraint.objects;
return distanceAB(a, b); return distanceAB(a, b);
} },
} }
}, },
defineParamsScope: ([pt, segment], callback) => { defineParamsScope: ([pt1, pt2], callback) => {
pt.visitParams(callback); pt1.visitParams(callback);
callback(segment.params.ang); pt2.visitParams(callback);
callback(segment.params.w);
},
collectResiduals: (residuals, params, {distance}) => {
residuals.push([R_DistancePP, params, [distance]]);
}, },
collectPolynomials: (polynomials, [x1, y1, x2, y2], {distance}) => { collectPolynomials: (polynomials, [x1, y1, x2, y2], {distance}) => {
@ -201,6 +196,85 @@ export const ConstraintDefinitions = indexById([
}, },
}, },
{
id: 'SegmentLength',
name: 'Segment Length',
constants: {
length: {
type: 'number',
description: 'length of the segment',
initialValue: (constraint) => {
const [segment] = constraint.objects;
const dx = segment.b.x - segment.a.x;
const dy = segment.b.y - segment.a.y;
return Math.sqrt(dx*dx + dy*dy);
},
// transform: length => length * length
}
},
defineParamsScope: ([segment], callback) => {
callback(segment.params.t);
},
collectPolynomials: (polynomials, [t], {length}) => {
polynomials.push(new Polynomial( - length).monomial(1).term(t, POW_1_FN));
},
},
{
id: 'Polar',
name: 'Polar Coordinate',
defineParamsScope: ([segment, originPt, targetPt], callback) => {
callback(segment.params.ang);
callback(segment.params.t);
originPt.visitParams(callback);
targetPt.visitParams(callback);
},
collectPolynomials: (polynomials, [ang, t, x1, y1, x2, y2]) => {
// v = [sin(ang), - cos(ang)]
// v * t = pt2 - pt1
//sin(ang) * t - x2 + x1
//-cos(ang) * t - y2 + y1
polynomials.push(new Polynomial().monomial() .term(ang, SIN_FN).term(t, POW_1_FN).monomial(-1).term(x2, POW_1_FN).monomial(1).term(x1, POW_1_FN));
polynomials.push(new Polynomial().monomial(-1).term(ang, COS_FN).term(t, POW_1_FN).monomial(-1).term(y2, POW_1_FN).monomial(1).term(y1, POW_1_FN));
},
},
{
id: 'LockPoint',
name: 'Lock Point',
constants: {
x: {
type: 'number',
description: 'X Coordinate',
initialValue: (constraint) => constraint.objects[0].x,
},
y: {
type: 'number',
description: 'y Coordinate',
initialValue: (constraint) => constraint.objects[0].y,
}
},
defineParamsScope: ([pt], callback) => {
pt.visitParams(callback);
},
collectPolynomials: (polynomials, [px, py], {x, y}) => {
polynomials.push(new Polynomial(-x).monomial().term(px, POW_1_FN));
polynomials.push(new Polynomial(-y).monomial().term(py, POW_1_FN));
},
}
]); ]);
export class AlgNumConstraint { export class AlgNumConstraint {

View file

@ -11,7 +11,7 @@ export class AlgNumSubSystem {
generator = NOOP;//new BoundaryObjectsGenerator(); generator = NOOP;//new BoundaryObjectsGenerator();
constraints = []; allConstraints = [];
ownParams = new Set(); ownParams = new Set();
@ -21,6 +21,7 @@ export class AlgNumSubSystem {
beingSolvedParams = new Set(); beingSolvedParams = new Set();
eliminatedParams = new Map();
// generators = []; // generators = [];
subSystems = []; //subsystems are generated by generators only subSystems = []; //subsystems are generated by generators only
@ -28,13 +29,31 @@ export class AlgNumSubSystem {
polynomials = []; polynomials = [];
substitutedParams = []; substitutedParams = [];
polyToConstr = new Map(); polyToConstr = new Map();
conflicting = new Set(); conflicting = new Set();
redundant = new Set(); redundant = new Set();
snapshot = new Map();
dof = 0;
SubSystem(generator) { SubSystem(generator) {
this.generator = generator; this.generator = generator;
} }
get fullyConstrained() {
return this.dof === 0;
}
validConstraints(callback) {
this.allConstraints.forEach(c => {
if (!this.conflicting.has(c)) {
callback(c);
}
});
}
addConstraint(constraint, _ancestorParams) { addConstraint(constraint, _ancestorParams) {
if (this.canBeAdded(constraint.params)) { if (this.canBeAdded(constraint.params)) {
@ -42,20 +61,73 @@ export class AlgNumSubSystem {
// this.constraints // this.constraints
} }
this.constraints.push(constraint); this.makeSnapshot();
this.allConstraints.push(constraint);
this.prepare();
if (!this.isConflicting(constraint)) {
this.solveFine();
console.log(this.solveStatus);
if (!this.solveStatus.success) {
console.log("adding to conflicts");
this.conflicting.add(constraint);
}
}
if (this.isConflicting(constraint)) {
this.rollback();
// } else if (this.fullyConstrained) {
// this.rollback();
// this.conflicting.add(constraint);
// this.redundant.add(constraint);
} else {
this.updateDOF();
this.updateFullyConstrainedObjects();
}
}
isConflicting(constraint) {
return this.conflicting.has(constraint);
}
updateDOF() {
let vars = 0;
let equations = 0;
this.validConstraints(c => {
equations ++;
c.params.forEach(p => {
if (!this.readOnlyParams.has(p)) {
vars++
}
})
});
this.dof = vars - equations;
}
makeSnapshot() {
this.snapshot.clear();
this.validConstraints(c => c.params.forEach(p => this.snapshot.set(p, p.get())));
}
rollback() {
this.snapshot.forEach((val, param) => param.set(val));
} }
reset() { reset() {
this.polynomials = []; this.polynomials = [];
this.substitutedParams = []; this.substitutedParams = [];
this.polyToConstr.clear(); this.eliminatedParams.clear();
this.conflicting.clear();
this.redundant.clear();
this.beingSolvedParams.clear(); this.beingSolvedParams.clear();
this.polyToConstr.clear();
} }
evaluatePolynomials() { evaluatePolynomials() {
this.constraints.forEach(c => { this.validConstraints(c => {
c.collectPolynomials(this.polynomials); c.collectPolynomials(this.polynomials);
this.polynomials.forEach(p => this.polyToConstr.set(p, c)) this.polynomials.forEach(p => this.polyToConstr.set(p, c))
}); });
@ -63,11 +135,6 @@ export class AlgNumSubSystem {
console.log('reducing system:'); console.log('reducing system:');
this.polynomials.forEach(p => console.log(p.toString())); this.polynomials.forEach(p => console.log(p.toString()));
const toEliminate = Array.from(this.readOnlyParams);
const toSubstitute = [];
const linearSub = [];
let requirePass = true; let requirePass = true;
while (requirePass) { while (requirePass) {
@ -79,74 +146,74 @@ export class AlgNumSubSystem {
} }
if (polynomial.monomials.length === 0) { if (polynomial.monomials.length === 0) {
if (!eqEps(polynomial.constant, 0)) {
this.conflicting.add(this.polyToConstr.get(polynomial)); this.conflicting.add(this.polyToConstr.get(polynomial));
console.log("CONFLICT: " + polynomial.toString());
if (eqEps(polynomial.constant, 0)) {
this.redundant.add(this.polyToConstr.get(polynomial));
console.log("REDUNDANT");
} }
this.polynomials[i] = null; this.polynomials[i] = null;
} else if (polynomial.monomials.length === 1) { } else if (polynomial.isLinear && polynomial.monomials.length === 1) {
const monomial = polynomial.monomials[0]; const monomial = polynomial.monomials[0];
const terms = monomial.terms; const terms = monomial.terms;
if (terms.length === 1) { if (terms.length === 1) {
const term = terms[0]; const term = terms[0];
if (term.fn.degree === 1) { if (term.fn.degree === 1) {
term.param.set(- polynomial.constant / monomial.constant); const p = term.param;
toEliminate.push(term.param); const val = - polynomial.constant / monomial.constant;
p.set(val);
this.eliminatedParams.set(p, val);
for (let polynomial of this.polynomials) {
if (polynomial) {
polynomial.eliminate(p, val);
}
}
requirePass = true;
} }
} }
this.polynomials[i] = null; this.polynomials[i] = null;
} else if (polynomial.monomials.length === 2 && polynomial.isLinear) { } else if (polynomial.monomials.length === 2 && polynomial.isLinear) {
const [m1, m2] = polynomial.monomials; const [m1, m2] = polynomial.monomials;
let p1 = m1.linearParam;
let p2 = m2.linearParam;
const constant = - m2.constant / m1.constant; const constant = - m2.constant / m1.constant;
if (eqEps(polynomial.constant, 0)) { if (eqEps(polynomial.constant, 0)) {
toSubstitute.push([m1.linearParam, m2.linearParam, constant]);
this.substitutedParams.push([m1.linearParam, new Polynomial().monomial(constant).term(m2.linearParam, POW_1_FN)]); for (let polynomial of this.polynomials) {
} else { if (polynomial) {
linearSub.push([m1.param, m2.param, constant, - polynomial.constant / m1.constant]); polynomial.substitute(p1, p2, constant);
} }
}
this.substitutedParams.push([p1, new Polynomial().monomial(constant).term(p2, POW_1_FN)]);
this.polynomials[i] = null; this.polynomials[i] = null;
}
}
while (toEliminate.length) {
requirePass = true; requirePass = true;
const param = toEliminate.pop(); } else {
for (let polynomial of this.polynomials) { const b = - polynomial.constant / m1.constant;
if (polynomial) {
polynomial.eliminate(param, param.get());
}
}
}
while (toSubstitute.length) {
requirePass = true;
const [param, toParam, dotConstant] = toSubstitute.pop();
for (let polynomial of this.polynomials) {
if (polynomial) {
polynomial.substitute(param, toParam, dotConstant);
}
}
}
if (linearSub.length) {
while (linearSub.length) {
const [param, toParam, k, b] = linearSub.pop();
let transaction = compositeFn(); let transaction = compositeFn();
for (let polynomial of this.polynomials) { for (let polynomial of this.polynomials) {
if (polynomial) { if (polynomial) {
const polyTransaction = polynomial.linearSubstitution(param, toParam, k, b); const polyTransaction = polynomial.linearSubstitution(p1, p2, constant, b);
if (!polyTransaction) { if (!polyTransaction) {
transaction = null; transaction = null;
break; break;
} }
transaction.push(polyTransaction); transaction.push(polyTransaction);
transaction.push(() => this.substitutedParams.push(param, new Polynomial(b).monomial(k).term(toParam, POW_1_FN))); transaction.push(() => {
this.substitutedParams.push([p1, new Polynomial(b).monomial(constant).term(p2, POW_1_FN)]);
this.polynomials[i] = null;
});
} }
} }
if (transaction) { if (transaction) {
requirePass = true;
transaction(); transaction();
requirePass = true;
}
} }
} }
} }
@ -168,12 +235,13 @@ export class AlgNumSubSystem {
this.evaluatePolynomials(); this.evaluatePolynomials();
// this.polynomialClusters = splitByIsolatedClusters(this.polynomials);
console.log('solving system:'); console.log('solving system:');
this.polynomials.forEach(p => console.log(p.toString())); this.polynomials.forEach(p => console.log(p.toString()));
console.log('with respect to:'); console.log('with respect to:');
this.substitutedParams.forEach(([x, expr]) => console.log('X' + x.id + ' = ' + expr.toString())); this.substitutedParams.forEach(([x, expr]) => console.log('X' + x.id + ' = ' + expr.toString()));
const residuals = []; const residuals = [];
this.polynomials.forEach(p => residuals.push(p.asResidual())); this.polynomials.forEach(p => residuals.push(p.asResidual()));
@ -189,6 +257,63 @@ export class AlgNumSubSystem {
this.numericalSolver = prepare(residuals); this.numericalSolver = prepare(residuals);
} }
splitByIsolatedClusters(polynomials) {
const graph = [polynomials.length];
const params = [];
const paramMap = new Map();
polynomials.forEach((pl, i) => pl.visitParams(p => {
let pIndex = paramMap.get(p);
if (pIndex === undefined) {
pIndex = paramMap.size;
paramMap.set(p, pIndex);
params.push(p);
}
graph[i][pIndex] = 1;
}));
const visited = new Set();
const clusters = [];
for (let param of params) {
if (visited.has(param)) {
continue
}
const stack = [param];
const isolation = [];
while (stack.length) {
const p = stack.pop();
if (visited.has(p)) {
continue;
}
isolation.push(p);
const index = paramMap.get(p);
for (let i = 0; i < graph.length; ++i) {
if (graph[i][index] === 1) {
for (let j = 0; j < params.length; j ++) {
if (j !== index) {
stack.push(params[j]);
}
}
}
}
}
if (isolation.length) {
clusters.push(new Cluster(isolation));
}
}
return clusters;
}
solveRough() {
this.solve(true);
}
solveFine() {
this.solve(false);
}
solve(rough) { solve(rough) {
this.generator(); this.generator();
@ -197,7 +322,15 @@ export class AlgNumSubSystem {
solverParam.set(solverParam.objectParam.get()); solverParam.set(solverParam.objectParam.get());
}); });
this.numericalSolver.solveSystem(rough); const solveStatus = this.numericalSolver.solveSystem(rough);
if (!rough) {
this.solveStatus = solveStatus;
}
console.log('numerical result: ' + solveStatus.success);
for (let [p, val] of this.eliminatedParams) {
p.set(val);
}
this.beingSolvedParams.forEach(solverParam => { this.beingSolvedParams.forEach(solverParam => {
solverParam.objectParam.set(solverParam.get()); solverParam.objectParam.set(solverParam.get());
@ -208,6 +341,27 @@ export class AlgNumSubSystem {
} }
} }
updateFullyConstrainedObjects() {
this.validConstraints(c => {
c.objects.forEach(obj => {
let allLocked = true;
obj.visitParams(p => {
if (!this.eliminatedParams.has(p)) {
allLocked = false;
}
});
obj.fullyConstrained = allLocked;
});
});
}
canBeAdded(subjectParams, ancestorParams) { canBeAdded(subjectParams, ancestorParams) {
@ -223,6 +377,41 @@ export class AlgNumSubSystem {
} }
class Cluster {
constructor(polynomials) {
this.polynomials = polynomials;
this.beingSolvedParams = new Set();
const residuals = [];
this.polynomials.forEach(p => residuals.push(p.asResidual()));
for (let residual of residuals) {
residual.params.forEach(solverParam => {
if (!this.beingSolvedParams.has(solverParam)) {
solverParam.reset(solverParam.objectParam.get());
this.beingSolvedParams.add(solverParam);
}
});
}
}
solve(rough) {
this.beingSolvedParams.forEach(solverParam => {
solverParam.set(solverParam.objectParam.get());
});
this.solveStatus = this.numericalSolver.solveSystem(rough);
this.beingSolvedParams.forEach(solverParam => {
solverParam.objectParam.set(solverParam.get());
});
}
}
export class AlgNumSystem { export class AlgNumSystem {
constraints = []; constraints = [];
generators = []; generators = [];

View file

@ -38,11 +38,16 @@ export class Polynomial {
substitute(param, toParam, dotConstant) { substitute(param, toParam, dotConstant) {
for (let m of this.monomials) { for (let m of this.monomials) {
let touched = false;
for (let i = 0; i < m.terms.length; ++i) { for (let i = 0; i < m.terms.length; ++i) {
if (m.terms[i].param === param) { if (m.terms[i].param === param) {
m.substitute(i, toParam, dotConstant); m.substitute(i, toParam, dotConstant);
touched = true;
} }
} }
if (touched) {
m.mergeTerms();
}
} }
} }
@ -161,6 +166,14 @@ export class Polynomial {
} }
visitParams(callback) {
for (let m of this.monomials) {
for (let t of m.terms) {
callback(t.param);
}
}
}
toString() { toString() {
return this.monomials.map(m => { return this.monomials.map(m => {
@ -227,8 +240,12 @@ export class Monomial {
this.terms.splice(i, 1); this.terms.splice(i, 1);
} }
substitute(i, toParam, dotConstant) { substitute(index, toParam, dotConstant) {
this.terms[index].param = toParam;
this.constant *= dotConstant; this.constant *= dotConstant;
}
mergeTerms() {
let wasMerge = false; let wasMerge = false;
for (let i = 0; i < this.terms.length; ++i) { for (let i = 0; i < this.terms.length; ++i) {
const merger = this.terms[i]; const merger = this.terms[i];
@ -250,7 +267,6 @@ export class Monomial {
if (wasMerge) { if (wasMerge) {
this.terms = this.terms.filter(t => t); this.terms = this.terms.filter(t => t);
} }
} }
equalVars(other) { equalVars(other) {
@ -321,6 +337,7 @@ export class ToThePowerFunction {
} }
constructor(degree, fn, d1) { constructor(degree, fn, d1) {
this.degree = degree;
this.fn = fn; this.fn = fn;
this.d1 = d1; this.d1 = d1;
this.id = '^' + degree; this.id = '^' + degree;
@ -358,10 +375,11 @@ export class FreeFunction {
fn; fn;
constructor(fn, d1, id) { constructor(fn, d1, id, linearSubstitutionFn) {
this.fn = fn; this.fn = fn;
this.d1 = d1; this.d1 = d1;
this.id = id; this.id = id;
this.linearSubstitutionFn = linearSubstitutionFn || null;
} }
apply(x) { apply(x) {
@ -381,7 +399,7 @@ export class FreeFunction {
} }
linearSubstitution(k, x, b) { linearSubstitution(k, x, b) {
return null; return this.linearSubstitutionFn(k, x, b);
} }
} }
@ -415,7 +433,7 @@ export class CosineOfSum {
} }
render(x) { render(x) {
return 'cos(' + this.k.toFixed(2) + x + ' + ' + this.b.toFixed(2) + ')'; return 'cos(' + this.k.toFixed(2) + '*' + x + ' + ' + this.b.toFixed(2) + ')';
} }
} }
@ -449,7 +467,7 @@ export class SineOfSum {
} }
render(x) { render(x) {
return 'sin(' + this.k.toFixed(2) + x + ' + ' + this.b.toFixed(2) + ')'; return 'sin(' + this.k.toFixed(2) + '*' + x + ' + ' + this.b.toFixed(2) + ')';
} }
} }

View file

@ -400,7 +400,7 @@ IO.prototype._serializeSketch = function(metadata) {
} }
sketch.constraints = []; sketch.constraints = [];
const systemConstraints = this.viewer.parametricManager.algnNumSystem.constraints; const systemConstraints = this.viewer.parametricManager.algnNumSystem.allConstraints;
for (let sc of systemConstraints) { for (let sc of systemConstraints) {
if (!sc.internal) { if (!sc.internal) {
sketch.constraints.push(sc.write()); sketch.constraints.push(sc.write());

View file

@ -105,6 +105,10 @@ class ParametricManager {
this.refresh(); this.refresh();
} }
coincidePoints(pt1, pt2) {
this.addAlgNum(new AlgNumConstraint(ConstraintDefinitions.PCoincident, [pt1, pt2]));
}
} }
ParametricManager.prototype.createConstantResolver = function() { ParametricManager.prototype.createConstantResolver = function() {

View file

@ -47,18 +47,15 @@ export class Segment extends SketchObject {
let ang = Math.atan2(ny, nx); let ang = Math.atan2(ny, nx);
if (this.ang !== undefined && Math.abs(ang - this.ang) > Math.PI) {
ang = Math.atan2(-ny, -nx);
}
this.params.ang.set(ang||0); this.params.ang.set(ang||0);
this.params.w.set(nx * this.a.x + ny * this.a.y); this.params.w.set(nx * this.a.x + ny * this.a.y);
this.params.t.set(l);
} }
stabilize(viewer) { stabilize(viewer) {
this.syncGeometry(); this.syncGeometry();
const c1 = new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [this.a, this]); const c1 = new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [this.a, this]);
const c2 = new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [this.b, this]); const c2 = new AlgNumConstraint(ConstraintDefinitions.Polar, [this, this.a, this.b]);
c1.internal = true; c1.internal = true;
c2.internal = true; c2.internal = true;
viewer.parametricManager.addAlgNum(c1); viewer.parametricManager.addAlgNum(c1);

View file

@ -1,6 +1,7 @@
import {Generator} from '../id-generator' import {Generator} from '../id-generator'
import {Shape} from './shape' import {Shape} from './shape'
import {Types} from '../io'; import {Types} from '../io';
import {Styles} from "../styles";
export class SketchObject extends Shape { export class SketchObject extends Shape {
constructor() { constructor() {
@ -11,6 +12,7 @@ export class SketchObject extends Shape {
this.children = []; this.children = [];
this.linked = []; this.linked = [];
this.layer = null; this.layer = null;
this.fullyConstrained = false;
} }
normalDistance(aim, scale) { normalDistance(aim, scale) {
@ -86,8 +88,12 @@ export class SketchObject extends Shape {
ctx.save(); ctx.save();
viewer.setStyle(this.marked, ctx); viewer.setStyle(this.marked, ctx);
} }
if (this.fullyConstrained) {
ctx.save();
viewer.setStyle(Styles.FULLY_CONSTRAINED, ctx);
}
this.drawImpl(ctx, scale, viewer); this.drawImpl(ctx, scale, viewer);
if (this.marked != null) ctx.restore(); if (this.marked != null || this.fullyConstrained) ctx.restore();
} }
copy() { copy() {

View file

@ -39,6 +39,12 @@ export const Styles = {
fillStyle : "#000000" fillStyle : "#000000"
}, },
FULLY_CONSTRAINED: {
lineWidth : 2,
strokeStyle : "#d7e6ff",
fillStyle : "#424e56"
},
CONSTRUCTION : { CONSTRUCTION : {
lineWidth : 1, lineWidth : 1,
strokeStyle : "#aaaaaa", strokeStyle : "#aaaaaa",

View file

@ -23,7 +23,7 @@ export class DragTool extends Tool {
// this.lockedValues[i + 1] = this._point.y - this.lockedShifts[i + 1]; // this.lockedValues[i + 1] = this._point.y - this.lockedShifts[i + 1];
// } // }
this.obj.translate(dx, dy); this.obj.translate(dx, dy);
(this.obj.parent || this.obj).syncGeometry();
if (!Tool.dumbMode(e)) { if (!Tool.dumbMode(e)) {
this.viewer.parametricManager.algnNumSystem.solve(true); this.viewer.parametricManager.algnNumSystem.solve(true);
} }

View file

@ -19,7 +19,7 @@ export class AddSegmentTool extends Tool {
} }
mousemove(e) { mousemove(e) {
var p = this.viewer.screenToModel(e); let p = this.viewer.screenToModel(e);
if (this.line != null) { if (this.line != null) {
this.viewer.snap(p.x, p.y, [this.line.a, this.line.b]); this.viewer.snap(p.x, p.y, [this.line.a, this.line.b]);
this.line.b.x = p.x; this.line.b.x = p.x;
@ -35,7 +35,7 @@ export class AddSegmentTool extends Tool {
if (this.line == null) { if (this.line == null) {
const b = this.viewer.screenToModel(e); const b = this.viewer.screenToModel(e);
let a = b; let a = b;
var needSnap = false; let needSnap = false;
if (this.viewer.snapped != null) { if (this.viewer.snapped != null) {
a = this.viewer.snapped; a = this.viewer.snapped;
this.viewer.cleanSnap(); this.viewer.cleanSnap();
@ -43,7 +43,7 @@ export class AddSegmentTool extends Tool {
} }
this.line = this.viewer.addSegment(a.x, a.y, b.x, b.y, this.viewer.activeLayer); this.line = this.viewer.addSegment(a.x, a.y, b.x, b.y, this.viewer.activeLayer);
if (needSnap) { if (needSnap) {
this.line.a.coincideWith(a); this.viewer.parametricManager.coincidePoints(this.line.a, a);
} }
this.firstPointPicked(); this.firstPointPicked();
this.viewer.refresh(); this.viewer.refresh();
@ -53,7 +53,7 @@ export class AddSegmentTool extends Tool {
this.viewer.cleanSnap(); this.viewer.cleanSnap();
this.line.b.x = p.x; this.line.b.x = p.x;
this.line.b.y = p.y; this.line.b.y = p.y;
this.line.b.coincideWith(p); this.viewer.parametricManager.coincidePoints(this.line.b, p);
} }
this.nextPointPicked(); this.nextPointPicked();
} }
@ -61,10 +61,11 @@ export class AddSegmentTool extends Tool {
nextPointPicked() { nextPointPicked() {
this.pointPicked(this.line.b.x, this.line.b.y); this.pointPicked(this.line.b.x, this.line.b.y);
this.line.stabilize(this.viewer);
if (this.multi) { if (this.multi) {
const b = this.line.b; const b = this.line.b;
this.line = this.viewer.addSegment(b.x, b.y, b.x, b.y, this.viewer.activeLayer); this.line = this.viewer.addSegment(b.x, b.y, b.x, b.y, this.viewer.activeLayer);
this.line.a.coincideWith(b); this.viewer.parametricManager.coincidePoints(this.line.a, b);
} else { } else {
this.restart() this.restart()
} }

View file

@ -121,7 +121,6 @@ class Viewer {
var b = new EndPoint(x2, y2); var b = new EndPoint(x2, y2);
var line = new Segment(a, b); var line = new Segment(a, b);
layer.add(line); layer.add(line);
line.stabilize(this);
return line; return line;
}; };