mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-15 12:53:52 +01:00
arc, fillet and radius equality
This commit is contained in:
parent
95930151ca
commit
f50d50fa98
11 changed files with 177 additions and 78 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ export function matchTypes(selection) {
|
|||
}
|
||||
|
||||
export function isInstanceOf(obj, shapeConstructor) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
return obj._class === shapeConstructor.prototype._class;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ HistoryManager.prototype.lightCheckpoint = function (weight) {
|
|||
|
||||
HistoryManager.prototype.checkpoint = function () {
|
||||
try {
|
||||
this._checkpoint();
|
||||
// this._checkpoint();
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue