arc, fillet and radius equality

This commit is contained in:
Val Erastov (xibyte) 2020-02-07 21:08:53 -08:00
parent 95930151ca
commit f50d50fa98
11 changed files with 177 additions and 78 deletions

View file

@ -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;
}

View file

@ -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]);
}
}
},

View file

@ -34,6 +34,9 @@ export function matchTypes(selection) {
}
export function isInstanceOf(obj, shapeConstructor) {
if (!obj) {
return false;
}
return obj._class === shapeConstructor.prototype._class;
}

View file

@ -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;
});
}
}

View file

@ -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);

View file

@ -43,7 +43,7 @@ HistoryManager.prototype.lightCheckpoint = function (weight) {
HistoryManager.prototype.checkpoint = function () {
try {
this._checkpoint();
// this._checkpoint();
} catch(e) {
console.log(e);
}

View file

@ -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();

View file

@ -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);

View file

@ -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;
}

View file

@ -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();

View file

@ -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();
}
}