awv3
Version:
⚡ AWV3 embedded CAD
246 lines (218 loc) • 10.8 kB
JavaScript
import { addCommand, removeCommands, updateCommand } from './command/highlevel';
import { Assign, Member, Sequence, Variable } from './command/lowlevel';
import * as Constraints from './constraint/type';
import { drawArcBy_S_E_CP, getTangent, intersectLines } from './geomutils';
import {ReplaceEntityInConstraints} from "./command/lowlevel";
export default class FilletProcessor {
constructor(sketch) {
this.sketch = sketch;
}
// check if the given position is at the angle that can be filleted
// return full information about the angle or null
recognizeFilletableAngle(pos) {
// find exactly matching points - children of lines
const samePoints = this.findPointsAt(pos);
// exactly two curves must end at pos
if (samePoints.length !== 2)
return null;
//both of them must be lines
const lines = samePoints.map(pnt => pnt.parent);
if (!lines.every(ln => ln && ln.isLine()))
return null;
// these two points must be marked as incident
const incidenceConstr = this.findConstraints(Constraints.incidence.type, samePoints[0], samePoints[1]);
if (incidenceConstr.length !== 1)
return null;
// check that parent lines are not collinear
const dirA = getTangent(lines[0].geomParams), dirB = getTangent(lines[1].geomParams);
if (Math.abs(dirA.cross(dirB).z) <= FilletProcessor.angularTolerance)
return null;
// find outer endpoints of the lines
let lineEnds = [];
for (let i = 0; i < 2; i++) {
const vertexAtStart = (lines[i].startPoint.id === samePoints[i].id);
lineEnds[i] = vertexAtStart ? lines[i].endPoint : lines[i].startPoint;
}
//return all the found data
return {
lines: lines,
lineStarts: samePoints,
lineEnds: lineEnds,
incidence: incidenceConstr[0],
control: pos,
};
}
// checks if the given arc is a part of a correct fillet
// return full information about the fillet or null
recognizeFilletByArcOrEdge(obj) {
let res = { obj, objEnds: [], lines: [], lineStarts: [], lineEnds: [], vertex: null };
let lineDirs = [];
for (let objEnd of [obj.startPoint, obj.endPoint]) {
// find incident line
let samePoints = this.findPointsAt(objEnd.pos);
samePoints = samePoints.filter(
point => point.parent.id !== obj.id && point.parent.state.class === 'CC_Line'
);
if (samePoints.length !== 1)
return null;
const lineStart = samePoints[0];
const line = lineStart.parent;
const lineEnd = line.startPoint.id === lineStart.id ? line.endPoint : line.startPoint;
// check that all constraints are in objEnd
if (this.findConstraints(Constraints.incidence.type, objEnd, lineStart).length !== 1)
return null;
if (obj.state.class === 'CC_Arc' && this.findConstraints(Constraints.tangency.type, obj, line).length !== 1)
return null;
// check that a line is tangent at a common point
const lineDir = lineEnd.pos.sub(lineStart.pos).normalize();
if (obj.state.class === 'CC_Arc') {
const arcDir = getTangent(obj.geomParams, objEnd.pos);
if (objEnd.state.name === 'startPoint') arcDir.negate();
if (lineDir.distanceTo(arcDir) > FilletProcessor.angularTolerance)
return null;
}
// save found objects
res.objEnds.push(objEnd);
res.lines.push(line);
res.lineStarts.push(lineStart);
res.lineEnds.push(lineEnd);
lineDirs.push(lineDir);
}
// check that the fillet angle is in range (0; pi)
if (obj.state.class === 'CC_Arc' && Math.abs(obj.state.members.bulge.value) >= 1.0)
return null; //arc's angle > pi
let angle = lineDirs[0].angleTo(lineDirs[1]);
if (angle <= FilletProcessor.angularTolerance || angle >= Math.PI - FilletProcessor.angularTolerance)
return null;
// calculate the location of the fillet's vertex
const vertexPos = intersectLines(
res.lineStarts[0].pos,
lineDirs[0],
res.lineStarts[1].pos,
lineDirs[1],
FilletProcessor.angularTolerance
);
if (!vertexPos)
return null;
// check for a point there
const vertices = this.findPointsAt(vertexPos);
if (vertices.length !== 1)
return null;
res.vertex = vertices[0];
res.control = res.vertex.pos;
// check incidence of the vertex to lines
for (let i = 0; i < 2; ++i)
if (this.findConstraints(Constraints.incidence.type, res.vertex, res.lines[i]).length !== 1)
return null;
return res;
}
getAngle(info) {
let dirs = [];
for (let i = 0; i < 2; i++)
dirs[i] = info.lineEnds[i].pos.sub(info.lineStarts[i].pos).normalize();
return dirs[0].angleTo(dirs[1]);
}
offsetToRadius(info, offset) {
let angle = this.getAngle(info);
return offset * Math.tan(angle / 2);
}
radiusToOffset(info, radius) {
let angle = this.getAngle(info);
return radius / Math.tan(angle / 2);
}
// calculate the filleting arc params (center, touch points) by specified radius
calculateFilletParams(info, options) {
const touch = this.getTouchPoints(info, options.radius, options.offset);
if (!touch) return null;
return { ...drawArcBy_S_E_CP([...touch, info.control], {}), control: info.control };
}
calculateChamferParams(info, options) {
const touch = this.getTouchPoints(info, options.radius, options.offset);
if (!touch) return null;
return { start: touch[0], end: touch[1], control: info.control };
}
findPointsAt(pos) {
let samePoints = [];
for (let obj of this.sketch.descendants) {
if (obj.state.class === 'CC_Point' && obj.pos.distanceTo(pos) <= FilletProcessor.linearTolerance)
samePoints.push(obj);
}
return samePoints;
}
findConstraints(type, objA, objB) {
return this.sketch.children.filter(
constr =>
constr.state.class === type &&
(constr.entities[0].id === objA.id && constr.entities[1].id === objB.id ||
constr.entities[0].id === objB.id && constr.entities[1].id === objA.id)
);
}
getTouchPoints(info, radius, distance) {
const vertexPos = info.control;
let dirs = [], maxDists = [];
for (let i = 0; i < 2; i++) {
let dir = info.lineEnds[i].pos.sub(info.lineStarts[i].pos).normalize();
let maxDist = info.lineEnds[i].pos.distanceTo(vertexPos);
dirs.push(dir);
maxDists.push(maxDist);
}
const angle = dirs[0].angleTo(dirs[1]);
const dist = distance !== undefined ? distance : radius / Math.tan(angle / 2);
if (dist + FilletProcessor.linearTolerance >= Math.min(maxDists[0], maxDists[1]))
return null; // too large fillet
return dirs.map(dir => dir.clone().multiplyScalar(dist).add(vertexPos));
}
createNewFilletStatement(info, params) {
const vertex = Variable(), curve = Variable();
return Sequence(
...removeCommands(this.sketch, info.incidence),
updateCommand(this.sketch, info.lineStarts[0], { start: params.start }),
updateCommand(this.sketch, info.lineStarts[1], { start: params.end }),
Assign(vertex, addCommand(this.sketch, { start: info.control })),
Assign(curve, addCommand(this.sketch, params)),
ReplaceEntityInConstraints(this.sketch, undefined, info.lineStarts[0], vertex),
ReplaceEntityInConstraints(this.sketch, undefined, info.lineStarts[1], vertex),
addCommand(this.sketch, { class: Constraints.incidence.type, entities: [info.lines[0], vertex], value: {} }),
addCommand(this.sketch, { class: Constraints.incidence.type, entities: [info.lines[1], vertex], value: {} }),
addCommand(this.sketch, {
class: Constraints.incidence.type,
entities: [info.lineStarts[0], Member(curve, 'startPoint')],
value: {}
}),
addCommand(this.sketch, {
class: Constraints.incidence.type,
entities: [info.lineStarts[1], Member(curve, 'endPoint')],
value: {}
}),
addCommand(this.sketch, { class: Constraints.tangency.type, entities: [info.lines[0], curve], value: {} }),
addCommand(this.sketch, { class: Constraints.tangency.type, entities: [info.lines[1], curve], value: {} }),
addCommand(this.sketch, { class: Constraints.radius.type, entities: [curve], value: {} })
);
}
deleteFilletStatement(info) {
let constraints = Array.from(this.sketch.descendants).filter(obj => obj.isConstraint());
//note: when moving constraints from fillet's vertex to the resulting angle's vertex,
//ignore everything that involves removed geometry, filleting lines and their closer endpoints
//the ignored constraints would be removed automatically
let blacklist = [...info.lines, ...info.lineStarts, info.obj, info.obj.startPoint, info.obj.endPoint, info.obj.center];
let involves = (constr, obj) => constr.entities.some(e => e.id === obj.id);
constraints = constraints.filter(constr => !blacklist.some(t => involves(constr, t)));
return Sequence(
...removeCommands(this.sketch, info.obj),
ReplaceEntityInConstraints(this.sketch, constraints, info.vertex, info.lineStarts[0]),
...removeCommands(this.sketch, info.vertex),
updateCommand(this.sketch, info.lineStarts[0], { start: info.vertex.pos }),
updateCommand(this.sketch, info.lineStarts[1], { start: info.vertex.pos }),
addCommand(this.sketch, { class: Constraints.incidence.type, entities: [info.lineStarts[0], info.lineStarts[1]], value: {} })
);
}
updateFilletStatement(info, params) {
return Sequence(
updateCommand(this.sketch, info.lineStarts[0], { start: params.start }),
updateCommand(this.sketch, info.lineStarts[1], { start: params.end }),
updateCommand(this.sketch, info.obj, params)
);
}
static linearTolerance = 1e-3;
static angularTolerance = 1e-3;
}