diff --git a/web/app/math/math.js b/web/app/math/math.js index 96e5beb3..e1786dcf 100644 --- a/web/app/math/math.js +++ b/web/app/math/math.js @@ -230,10 +230,12 @@ export function findLowestLeftPoint(poly) { return heroIdx; } +const _360 = 2 * Math.PI; + export function makeAngle0_360(angle) { - angle %= 2 * Math.PI; + angle %= _360; if (angle < 0) { - angle = 2 * Math.PI + angle; + angle = _360 + angle; } return angle; } diff --git a/web/app/sketcher/actions/constraintActions.js b/web/app/sketcher/actions/constraintActions.js index 92012102..d9b5622e 100644 --- a/web/app/sketcher/actions/constraintActions.js +++ b/web/app/sketcher/actions/constraintActions.js @@ -5,6 +5,7 @@ import {Segment} from "../shapes/segment"; import {isInstanceOf, matchAll, matchTypes, sortSelectionByType} from "./matchUtils"; import constraints from "../../../test/cases/constraints"; import {Arc} from "../shapes/arc"; +import {FilletTool} from "../tools/fillet"; export default [ @@ -48,6 +49,31 @@ export default [ }, + { + shortName: 'EqualRadius', + description: 'Equal Radius Between Two Circle', + selectionMatcher: selection => { + for (let obj of selection) { + if (!(isInstanceOf(obj, Circle) || isInstanceOf(obj, Arc))) { + return false; + } + } + return true; + }, + + invoke: ctx => { + + const {viewer} = ctx; + + const pm = viewer.parametricManager; + for (let i = 1; i < viewer.selected.length; ++i) { + pm._add(new AlgNumConstraint(ConstraintDefinitions.EqualRadius, [viewer.selected[i-1], viewer.selected[i]])); + } + pm.commit(); + } + + }, + { shortName: 'Point On Line', description: 'Point On Line', @@ -155,20 +181,34 @@ export default [ description: 'Add a Fillet', selectionMatcher: (selection) => { if (matchTypes(selection, EndPoint, 1)) { - - } else { - return false; + const [point] = selection; + if (isInstanceOf(point.parent, Segment)) { + let pair = null; + point.visitLinked(l => { + if (l !== point && isInstanceOf(l.parent, Segment)) { + pair = l; + return true; + } + }); + if (pair) { + return true; + } + } } + 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)); + + const filletTool = new FilletTool(ctx.viewer); + const cands = filletTool.getCandidateFromSelection(viewer.selected); + if (cands) { + filletTool.breakLinkAndMakeFillet(cands[0], cands[1]); + } + } }, diff --git a/web/app/sketcher/actions/matchUtils.js b/web/app/sketcher/actions/matchUtils.js index 7cd8d0f4..d1ffd1fe 100644 --- a/web/app/sketcher/actions/matchUtils.js +++ b/web/app/sketcher/actions/matchUtils.js @@ -34,6 +34,9 @@ export function matchTypes(selection) { } export function isInstanceOf(obj, shapeConstructor) { + if (!obj) { + return false; + } return obj._class === shapeConstructor.prototype._class; } diff --git a/web/app/sketcher/constr/ANConstraints.js b/web/app/sketcher/constr/ANConstraints.js index d1aec98d..9e92b09e 100644 --- a/web/app/sketcher/constr/ANConstraints.js +++ b/web/app/sketcher/constr/ANConstraints.js @@ -239,6 +239,19 @@ export const ConstraintDefinitions = { }, }, + EqualRadius: { + id: 'EqualRadius', + name: 'Equal Radius', + + defineParamsScope: ([c1, c2], callback) => { + callback(c1.r); + callback(c2.r); + }, + + collectPolynomials: (polynomials, [r1, r2]) => { + polynomials.push(new Polynomial().monomial().term(r1, POW_1_FN).monomial(-1).term(r2, POW_1_FN).monomial(1)); + }, + }, LockPoint: { id: 'LockPoint', @@ -307,14 +320,17 @@ export const ConstraintDefinitions = { }, defineParamsScope: ([l1, l2, arc], callback) => { - l1.collectParams(callback); - l2.collectParams(callback); - arc.collectParams(callback); + callback(l1.params.ang); + callback(l1.params.w); + callback(l2.params.ang); + callback(l2.params.w); + arc.c.visitParams(callback); + callback(arc.r); }, - 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)); + collectPolynomials: (polynomials, [ang1, w1, ang2, w2, cx, cy, r], {inverted1, inverted2}) => { + polynomials.push(tangentLCPolynomial(ang1, w1, cx, cy, r, inverted1)); + polynomials.push(tangentLCPolynomial(ang2, w2, cx, cy, r, inverted2)); }, }, @@ -411,13 +427,14 @@ export class AlgNumConstraint { } Object.keys(this.constants).map(name => { let def = this.schema.constants[name]; + let val = this.constants[name]; if (def.type === 'number') { - let val = parseFloat(this.constants[name]); - if (def.transform) { - val = def.transform(val); - } - this.resolvedConstants[name] = val; + val = parseFloat(val); } + if (def.transform) { + val = def.transform(val); + } + this.resolvedConstants[name] = val; }); } } diff --git a/web/app/sketcher/constr/AlgNumSystem.js b/web/app/sketcher/constr/AlgNumSystem.js index 79973657..b4a7c31b 100644 --- a/web/app/sketcher/constr/AlgNumSystem.js +++ b/web/app/sketcher/constr/AlgNumSystem.js @@ -3,6 +3,8 @@ import {eqEps} from "../../brep/geom/tolerance"; import {Polynomial, POW_1_FN} from "./polynomial"; import {compositeFn} from "gems/func"; +const DEBUG = true; + export class AlgNumSubSystem { modifiers = []; @@ -54,7 +56,6 @@ export class AlgNumSubSystem { 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); @@ -121,9 +122,10 @@ export class AlgNumSubSystem { c.collectPolynomials(this.polynomials); this.polynomials.forEach(p => this.polyToConstr.set(p, c)) }); - - console.log('reducing system:'); - this.polynomials.forEach(p => console.log(p.toString())); + if (DEBUG) { + console.log('reducing system:'); + this.polynomials.forEach(p => console.log(p.toString())); + } let requirePass = true; @@ -137,10 +139,12 @@ export class AlgNumSubSystem { if (polynomial.monomials.length === 0) { this.conflicting.add(this.polyToConstr.get(polynomial)); - console.log("CONFLICT: " + polynomial.toString()); + if (DEBUG) { + console.log("CONFLICT: " + polynomial.toString()); + } if (eqEps(polynomial.constant, 0)) { this.redundant.add(this.polyToConstr.get(polynomial)); - console.log("REDUNDANT"); + // console.log("REDUNDANT"); } this.polynomials[i] = null; } else if (polynomial.isLinear && polynomial.monomials.length === 1) { @@ -236,14 +240,16 @@ export class AlgNumSubSystem { iso.beingSolvedParams.forEach(solverParam => this.paramToIsolation.set(solverParam.objectParam, iso)) }); - console.log('solving system:'); - this.polynomialIsolations.forEach((iso, i) => { - console.log(i + ". ISOLATION, DOF: " + iso.dof); - iso.polynomials.forEach(p => console.log(p.toString())); - }); + if (DEBUG) { + console.log('solving system:'); + this.polynomialIsolations.forEach((iso, i) => { + console.log(i + ". ISOLATION, DOF: " + iso.dof); + iso.polynomials.forEach(p => console.log(p.toString())); + }); - console.log('with respect to:'); - this.substitutionOrder.forEach(x => console.log('X' + x.id + ' = ' + this.substitutedParams.get(x).toString())); + console.log('with respect to:'); + this.substitutionOrder.forEach(x => console.log('X' + x.id + ' = ' + this.substitutedParams.get(x).toString())); + } } splitByIsolatedClusters(polynomials) { @@ -337,7 +343,9 @@ export class AlgNumSubSystem { this.solveStatus.success = this.solveStatus.success && iso.solveStatus.success; }); - console.log('numerical result: ' + this.solveStatus.success); + if (DEBUG) { + console.log('numerical result: ' + this.solveStatus.success); + } } for (let [p, val] of this.eliminatedParams) { @@ -422,7 +430,7 @@ class Isolation { }); if (penaltyFunction.params.length) { - residuals.push(penaltyFunction); + // residuals.push(penaltyFunction); } this.numericalSolver = prepare(residuals); diff --git a/web/app/sketcher/history.js b/web/app/sketcher/history.js index 8572b83e..a196e66e 100644 --- a/web/app/sketcher/history.js +++ b/web/app/sketcher/history.js @@ -43,7 +43,7 @@ HistoryManager.prototype.lightCheckpoint = function (weight) { HistoryManager.prototype.checkpoint = function () { try { - this._checkpoint(); + // this._checkpoint(); } catch(e) { console.log(e); } diff --git a/web/app/sketcher/shapes/arc.js b/web/app/sketcher/shapes/arc.js index 0c579311..40751e16 100644 --- a/web/app/sketcher/shapes/arc.js +++ b/web/app/sketcher/shapes/arc.js @@ -5,6 +5,7 @@ import {Param} from "./param"; import {greaterThanConstraint} from "../constr/barriers"; import {MIN_RADIUS} from "./circle"; import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints"; +import {makeAngle0_360} from "../../math/math"; export class Arc extends SketchObject { @@ -20,7 +21,6 @@ export class Arc extends SketchObject { this.r = new Param(MIN_RADIUS + 0.001); this.r.constraints = [greaterThanConstraint(MIN_RADIUS)]; - this.ang1 = new Param(0); this.ang2 = new Param(0); @@ -84,13 +84,13 @@ export class Arc extends SketchObject { drawImpl(ctx, scale) { ctx.beginPath(); let r = this.radiusForDrawing(); - let startAngle = this.getStartAngle(); + let startAngle = makeAngle0_360(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; } else { - endAngle = this.getEndAngle(); + endAngle = makeAngle0_360(this.getEndAngle()); } ctx.arc(this.c.x, this.c.y, r, startAngle, endAngle); let distanceB = this.distanceB(); diff --git a/web/app/sketcher/tools/circle.js b/web/app/sketcher/tools/circle.js index a6b4f80f..1ab5cb5c 100644 --- a/web/app/sketcher/tools/circle.js +++ b/web/app/sketcher/tools/circle.js @@ -51,7 +51,7 @@ export class EditCircleTool extends Tool { this.circle = new Circle( new EndPoint(p.x, p.y) ); - if (needSnap) this.viewer.parametricManager.linkObjects([this.circle.c, p]); + if (needSnap) this.viewer.parametricManager.coincidePoints(this.circle.c, p); this.pointPicked(this.circle.c.x, this.circle.c.y); this.sendHint('specify radius'); this.viewer.activeLayer.add(this.circle); diff --git a/web/app/sketcher/tools/fillet.js b/web/app/sketcher/tools/fillet.js index f4f8d355..cec74c52 100644 --- a/web/app/sketcher/tools/fillet.js +++ b/web/app/sketcher/tools/fillet.js @@ -6,6 +6,9 @@ import {EndPoint} from '../shapes/point' import {Arc} from '../shapes/arc' import {Constraints} from '../parametric' import {Tool} from './tool' +import {isInstanceOf} from "../actions/matchUtils"; +import {Segment} from "../shapes/segment"; +import {AlgNumConstraint, ConstraintDefinitions} from "../constr/ANConstraints"; export class FilletTool extends Tool { @@ -15,22 +18,18 @@ export class FilletTool extends Tool { } restart() { - for (let master of this.viewer.selected) { - if (master instanceof EndPoint) { - for (let slave of master.linked) { - if (slave instanceof EndPoint) { - if (this.breakLinkAndMakeFillet(master, slave)) { - this.viewer.toolManager.releaseControl(); - } - } - } + const cands = this.getCandidateFromSelection(this.viewer.selected); + if (cands) { + const [c1, c2] = cands; + if (this.breakLinkAndMakeFillet(c1, c2)) { + this.viewer.toolManager.releaseControl(); } } } makeFillet(point1, point2) { function shrink(point1) { - var a, b; + let a, b; if (point1.id === point1.parent.a.id) { a = point1.parent.b; b = point1.parent.a; @@ -38,37 +37,50 @@ export class FilletTool extends Tool { a = point1.parent.a; b = point1.parent.b; } - var d = math.distanceAB(a, b); - var k = 4 / 5; + const d = math.distanceAB(a, b); + const k = 4 / 5; b.x = a.x + (b.x - a.x) * k; b.y = a.y + (b.y - a.y) * k; return new Vector(a.x - b.x, a.y - b.y, 0); } - - var v1 = shrink(point1); - var v2 = shrink(point2); + + const v1 = shrink(point1); + const v2 = shrink(point2); if (v1.cross(v2).z > 0) { - var _ = point1; + const _ = point1; point1 = point2; point2 = _; } - - var vec = new Vector(); + + const vec = new Vector(); vec.setV(point2); vec._minus(point1); vec._multiply(0.5); vec._plus(point1); - var arc = new Arc( + const arc = new Arc( new EndPoint(point1.x, point1.y), new EndPoint(point2.x, point2.y), new EndPoint(vec.x, vec.y)); point1.parent.layer.add(arc); - var pm = this.viewer.parametricManager; + const pm = this.viewer.parametricManager; + + const s1 = point1.parent; + const s2 = point2.parent; + + const inverted1 = Math.cos(s1.ang) * arc.c.x + Math.sin(s1.ang) * arc.c.y < s1.w; + const inverted2 = Math.cos(s2.ang) * arc.c.x + Math.sin(s2.ang) * arc.c.y < s2.w; arc.stabilize(this.viewer); - pm._add(new Constraints.Fillet( point1, point2, arc)); - + + pm.add(new AlgNumConstraint(ConstraintDefinitions.Fillet, [point1.parent, point2.parent, arc], { + inverted1, + inverted2 + })); + + pm.add(new AlgNumConstraint(ConstraintDefinitions.PCoincident, [point1, arc.a])); + pm.add(new AlgNumConstraint(ConstraintDefinitions.PCoincident, [point2, arc.b])); + //function otherEnd(point) { // if (point.parent.a.id === point.id) { // return point.parent.b; @@ -80,10 +92,10 @@ export class FilletTool extends Tool { //pm._add(new Constraints.LockConvex(arc.c, arc.a, otherEnd(point1))); //pm._add(new Constraints.LockConvex(otherEnd(point2), arc.b, arc.c)); - var solver = pm.solve(); + // var solver = pm.solve(); // var solver = pm.solve([point1._x, point1._y, point2._x, point2._y]); - pm.notify(); - this.viewer.refresh(); + // pm.notify(); + // this.viewer.refresh(); } mouseup(e) { @@ -96,7 +108,15 @@ export class FilletTool extends Tool { breakLinkAndMakeFillet(point1, point2) { const pm = this.viewer.parametricManager; - const coi = pm.findCoincidentConstraint(point1, point2); + let coi = null; + for (let c of point1.constraints) { + if (c.schema.id === ConstraintDefinitions.PCoincident.id) { + if (c.objects.indexOf(point2) !== -1) { + coi = c; + break; + } + } + } if (coi != null) { pm.remove(coi); this.makeFillet(point1, point2); @@ -109,23 +129,32 @@ export class FilletTool extends Tool { static isLine(line) { return line != null && line._class === 'TCAD.TWO.Segment'; } - + getCandidate(e) { + const picked = this.viewer.pick(e); + return this.getCandidateFromSelection(picked); + } + + getCandidateFromSelection(picked) { let preferSketchLayer = (a, b) => (a.effectiveLayer === b.effectiveLayer)? 0 : a.effectiveLayer.name === 'sketch' ? -1 : 1; - - let picked = this.viewer.pick(e); + if (picked.length > 0) { let res = fetch.sketchObjects(picked, true, ['TCAD.TWO.EndPoint']); if (res == null) return null; let point1 = res.sort(preferSketchLayer)[0]; if (!FilletTool.isLine(point1.parent)) return; - let linked = [...point1.linked].sort(preferSketchLayer); - for (let i = 0; i < linked.length; i++) { - let point2 = linked[i]; - if (FilletTool.isLine(point2.parent)) { - return [point1, point2]; + + const linked = []; + point1.visitLinked(l => { + if (l !== point1 && FilletTool.isLine(l.parent)) { + linked.push(l); } + }); + + if (linked.length) { + linked.sort(preferSketchLayer); + return [point1, linked[0]]; } } return null; @@ -133,7 +162,7 @@ export class FilletTool extends Tool { mousemove(e) { var needRefresh = false; - if (this.viewer.selected.length != 0) { + if (this.viewer.selected.length !== 0) { this.viewer.deselectAll(); needRefresh = true; } diff --git a/web/app/sketcher/tools/rectangle.js b/web/app/sketcher/tools/rectangle.js index f2313975..a92126f7 100644 --- a/web/app/sketcher/tools/rectangle.js +++ b/web/app/sketcher/tools/rectangle.js @@ -53,10 +53,10 @@ export class RectangleTool extends Tool { this.alignSegments(p); if (this.viewer.snapped != null) { - this.viewer.parametricManager.linkObjects([this.rectangle[2].a, this.viewer.snapped]); + this.viewer.parametricManager.coincidePoints(this.rectangle[2].a, this.viewer.snapped); } if (this.firstPointSnap != null) { - this.viewer.parametricManager.linkObjects([this.rectangle[0].a, this.firstPointSnap]); + this.viewer.parametricManager.coincidePoints(this.rectangle[0].a, this.firstPointSnap); } this.viewer.cleanSnap(); diff --git a/web/app/sketcher/tools/tool.js b/web/app/sketcher/tools/tool.js index 0549bf0a..635abdec 100644 --- a/web/app/sketcher/tools/tool.js +++ b/web/app/sketcher/tools/tool.js @@ -48,7 +48,7 @@ export class Tool { const snapWith = this.viewer.snapped; this.viewer.cleanSnap(); p.setFromPoint(snapWith); - this.viewer.parametricManager.linkObjects([p, snapWith]); + this.viewer.parametricManager.coincidePoints(p, snapWith); this.viewer.parametricManager.refresh(); } }