2020-11-05 14:47:18 -05:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
2025-07-15 18:39:48 -04:00
|
|
|
const RAD2DEG = 180 / Math.PI;
|
2025-12-06 00:09:17 -05:00
|
|
|
const DEG2RAD = Math.PI / 180;
|
2020-12-01 19:54:54 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
class Tool {
|
|
|
|
|
constructor(settings, id, number) {
|
|
|
|
|
if (number >= 0) {
|
|
|
|
|
this.tool = settings.tools.filter(tool => tool.number == number)[0];
|
|
|
|
|
} else {
|
|
|
|
|
this.tool = settings.tools.filter(tool => tool.id == id)[0];
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
if (!this.tool) {
|
|
|
|
|
this.tool = Object.assign({}, settings.tools[0]);
|
|
|
|
|
this.tool.number = number >= 0 ? number : this.tool.number;
|
|
|
|
|
this.tool.id = id >= 0 ? id : this.tool.id;
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2025-12-06 00:09:17 -05:00
|
|
|
this.work_units = settings.controller.units === 'mm' ? 1 : 25.4;
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
getID() {
|
|
|
|
|
return this.tool.id;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
getName() {
|
|
|
|
|
return this.tool.name;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
getType() {
|
|
|
|
|
return this.tool.type;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
getNumber() {
|
|
|
|
|
return this.tool.number;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2025-12-06 00:09:17 -05:00
|
|
|
// for taper tips, returns value in tool units
|
|
|
|
|
// otherwise returns a fraction of the flute diameter in tool units
|
|
|
|
|
getStepSize(frac) {
|
2025-12-07 00:18:42 -05:00
|
|
|
return (this.hasTaper() ? frac : this.fluteDiameter() * (frac ?? 1));
|
2025-12-06 00:09:17 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
isMetric() {
|
|
|
|
|
return this.tool.metric;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
unitScale() {
|
|
|
|
|
return this.isMetric() ? 1 : 25.4;
|
|
|
|
|
}
|
2020-11-24 20:18:02 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
fluteLength() {
|
|
|
|
|
return this.unitScale() * this.tool.flute_len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fluteDiameter() {
|
|
|
|
|
return this.unitScale() * this.tool.flute_diam;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tipDiameter() {
|
|
|
|
|
return this.unitScale() * this.tool.taper_tip;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-03 21:55:19 -05:00
|
|
|
maxDiameter() {
|
|
|
|
|
return Math.max(this.fluteDiameter(), this.tipDiameter(), this.shaftDiameter());
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 19:10:39 -05:00
|
|
|
traceOffset() {
|
2025-04-17 10:18:13 -06:00
|
|
|
return (this.hasTaper() ? this.tipDiameter() : this.fluteDiameter()) / 2;
|
2023-01-17 19:10:39 -05:00
|
|
|
}
|
|
|
|
|
|
2023-01-19 16:26:58 -05:00
|
|
|
contourOffset(step) {
|
2025-04-17 10:18:13 -06:00
|
|
|
const diam = Math.min(this.hasTaper() ? this.tipDiameter() : this.fluteDiameter());
|
2023-01-19 16:26:58 -05:00
|
|
|
return diam ? diam * step : step;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 11:34:51 -05:00
|
|
|
getTaperAngle() {
|
2025-12-06 00:09:17 -05:00
|
|
|
let { flute_diam, flute_len, taper_tip } = this.tool;
|
|
|
|
|
return calcTaperAngle((flute_diam - taper_tip) / 2, flute_len);
|
2023-02-17 11:34:51 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 22:26:58 -05:00
|
|
|
getBallTaperParams() {
|
|
|
|
|
let a = this.getTaperAngle() * DEG2RAD,
|
|
|
|
|
r = (this.tipDiameter()/2) * (1 + Math.sin(a)) / Math.cos(a),
|
|
|
|
|
b = r * Math.cos(a);
|
|
|
|
|
return { a, r, b };
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
shaftLength() {
|
|
|
|
|
return this.unitScale() * this.tool.shaft_len;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-21 10:12:22 -06:00
|
|
|
/**
|
|
|
|
|
* returns the length of the drill tip as a function of the flute diameter,
|
|
|
|
|
* given a 118 deg point angle. the result is in workspace units.
|
|
|
|
|
*
|
|
|
|
|
* @return {number} length of the drill tip.
|
|
|
|
|
*/
|
|
|
|
|
drillTipLength() {
|
|
|
|
|
const drillAngleRad = 140 * Math.PI / 180;
|
|
|
|
|
return 0.5 * this.fluteDiameter() * Math.sin(drillAngleRad);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
shaftDiameter() {
|
|
|
|
|
return this.unitScale() * this.tool.shaft_diam;
|
|
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2022-10-10 18:01:43 -04:00
|
|
|
isBallMill() {
|
2022-10-10 19:24:16 -04:00
|
|
|
return this.tool.type === "ballmill";
|
2022-10-10 18:01:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isTaperMill() {
|
2022-10-10 19:24:16 -04:00
|
|
|
return this.tool.type === "tapermill";
|
2022-10-10 18:01:43 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-06 00:09:17 -05:00
|
|
|
isTaperBall() {
|
|
|
|
|
return this.tool.type === "taperball";
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 18:39:48 -04:00
|
|
|
isDrill() {
|
2025-04-17 10:18:13 -06:00
|
|
|
return this.tool.type === "drill";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hasTaper() {
|
2025-12-06 00:09:17 -05:00
|
|
|
return this.isTaperMill() || this.isTaperBall();
|
2025-04-17 10:18:13 -06:00
|
|
|
}
|
|
|
|
|
|
2025-04-21 10:12:22 -06:00
|
|
|
/**
|
|
|
|
|
* generate tool profile with resolution number of pixels. the result is a
|
|
|
|
|
* Float32Array stored in `this.profile` and `this.profileDim` containing
|
|
|
|
|
* dimensions of the profile in pixels and shared array buffer size.
|
|
|
|
|
*
|
2025-12-06 00:09:17 -05:00
|
|
|
* @param {number} resolution - pixel size in mm for the raster tool profile
|
2025-04-21 10:12:22 -06:00
|
|
|
*
|
|
|
|
|
* @return {Tool} this
|
|
|
|
|
*/
|
2022-02-21 10:01:19 -05:00
|
|
|
generateProfile(resolution) {
|
|
|
|
|
// generate tool profile
|
2025-02-27 14:19:03 -05:00
|
|
|
let ball = this.isBallMill(),
|
2025-12-06 00:09:17 -05:00
|
|
|
taperball = this.isTaperBall(),
|
2025-04-17 13:18:56 -06:00
|
|
|
taper = this.hasTaper(),
|
2025-04-21 10:12:22 -06:00
|
|
|
drill = this.isDrill(),
|
2025-12-06 00:09:17 -05:00
|
|
|
tip_dia = this.tipDiameter(),
|
|
|
|
|
flute_dia = this.fluteDiameter(),
|
|
|
|
|
flute_len = this.fluteLength(),
|
|
|
|
|
flute_rad = flute_dia / 2,
|
|
|
|
|
shaft_dia = Math.max(flute_dia, this.shaftDiameter()),
|
|
|
|
|
max_dia = Math.max(flute_dia, shaft_dia),
|
|
|
|
|
larger_shaft = shaft_dia - flute_dia > 0.001,
|
|
|
|
|
pix_sh_dia_float = max_dia / resolution,
|
|
|
|
|
pix_sh_dia_int = Math.round(pix_sh_dia_float),
|
|
|
|
|
pix_sh_rad_float = pix_sh_dia_float / 2,
|
|
|
|
|
pix_fl_dia_float = flute_dia / resolution,
|
|
|
|
|
pix_fl_rad_float = pix_fl_dia_float / 2,
|
|
|
|
|
pix_tip_dia_float = tip_dia / resolution,
|
|
|
|
|
pix_tip_rad_float = pix_tip_dia_float / 2,
|
|
|
|
|
tip_max_rad_offset = pix_fl_rad_float - pix_tip_rad_float,
|
|
|
|
|
pix_profile_iter = pix_sh_dia_int + (1 - pix_sh_dia_int % 2),
|
|
|
|
|
toolCenter = (pix_sh_dia_int - (pix_sh_dia_int % 2)) / 2,
|
2022-02-21 10:01:19 -05:00
|
|
|
toolOffset = [],
|
2025-12-06 00:09:17 -05:00
|
|
|
maxo = -Infinity,
|
|
|
|
|
// ball taper magic
|
2025-12-12 22:26:58 -05:00
|
|
|
{ b, r } = this.getBallTaperParams(),
|
2025-12-06 00:09:17 -05:00
|
|
|
pix_b = b / resolution,
|
|
|
|
|
pix_r = r / resolution;
|
2022-02-21 10:01:19 -05:00
|
|
|
|
|
|
|
|
// for each point in tool profile, check inside radius
|
2025-12-06 00:09:17 -05:00
|
|
|
for (let x = 0; x < pix_profile_iter; x++) {
|
|
|
|
|
for (let y = 0; y < pix_profile_iter; y++) {
|
2022-02-21 10:01:19 -05:00
|
|
|
let dx = x - toolCenter,
|
|
|
|
|
dy = y - toolCenter,
|
|
|
|
|
dist_from_center = Math.sqrt(dx * dx + dy * dy);
|
2025-12-06 00:09:17 -05:00
|
|
|
if (dist_from_center <= pix_fl_rad_float) { // if xy point inside flute radius
|
2023-02-13 23:10:42 -05:00
|
|
|
maxo = Math.max(maxo, dx, dy);
|
2022-02-21 10:01:19 -05:00
|
|
|
// flute offset points
|
|
|
|
|
let z_offset = 0;
|
|
|
|
|
if (ball) {
|
2025-12-06 00:09:17 -05:00
|
|
|
let ball_rad_sq = dist_from_center * dist_from_center;
|
|
|
|
|
let ball_rpixsq = pix_fl_rad_float * pix_fl_rad_float;
|
|
|
|
|
z_offset = Math.sqrt(ball_rpixsq - ball_rad_sq) * resolution - flute_rad;
|
|
|
|
|
} else if (taperball) {
|
|
|
|
|
// taperball: spherical tip, conical above
|
|
|
|
|
if (dist_from_center <= pix_b) {
|
|
|
|
|
// inside ball radius - spherical surface
|
|
|
|
|
let ball_rad_sq = dist_from_center * dist_from_center;
|
|
|
|
|
let ball_rpixsq = pix_r * pix_r;
|
|
|
|
|
z_offset = Math.sqrt(ball_rpixsq - ball_rad_sq) * resolution - r;
|
|
|
|
|
} else {
|
|
|
|
|
// outside ball radius - conical taper
|
|
|
|
|
z_offset = ((dist_from_center - pix_tip_rad_float) / tip_max_rad_offset) * -flute_len;
|
|
|
|
|
}
|
|
|
|
|
} else if (taper && dist_from_center >= pix_tip_rad_float) {
|
|
|
|
|
// if tapered and not in the flat tip radius
|
|
|
|
|
z_offset = ((dist_from_center - pix_tip_rad_float) / tip_max_rad_offset) * -flute_len;
|
2025-07-15 18:39:48 -04:00
|
|
|
} else if (drill) {
|
|
|
|
|
z_offset = -dist_from_center / 45;
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
toolOffset.push(dx, dy, z_offset);
|
2025-12-06 00:09:17 -05:00
|
|
|
} else if (flute_len && larger_shaft && dist_from_center <= pix_sh_rad_float) {
|
2022-02-21 10:01:19 -05:00
|
|
|
// shaft offset points
|
2025-12-06 00:09:17 -05:00
|
|
|
toolOffset.push(dx, dy, -flute_len);
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
|
2023-01-14 11:35:07 -05:00
|
|
|
// convert to shared array for use with minions
|
|
|
|
|
const profile = new Float32Array(new SharedArrayBuffer(toolOffset.length * 4));
|
|
|
|
|
profile.set(toolOffset);
|
|
|
|
|
|
|
|
|
|
this.profile = profile;
|
2022-02-21 10:01:19 -05:00
|
|
|
this.profileDim = {
|
2025-12-06 00:09:17 -05:00
|
|
|
size: shaft_dia,
|
|
|
|
|
pix: pix_profile_iter + 2,
|
2023-02-13 23:10:42 -05:00
|
|
|
maxo
|
2022-02-21 10:01:19 -05:00
|
|
|
};
|
2023-01-16 23:33:24 -05:00
|
|
|
|
2022-02-21 10:01:19 -05:00
|
|
|
return this;
|
2020-11-05 14:47:18 -05:00
|
|
|
}
|
2022-02-21 10:01:19 -05:00
|
|
|
}
|
|
|
|
|
|
2025-07-06 23:44:33 -04:00
|
|
|
function calcTaperAngle(rad, len) {
|
2023-02-19 08:35:10 -05:00
|
|
|
return (Math.atan(rad / len) * RAD2DEG);
|
2025-12-06 00:09:17 -05:00
|
|
|
}
|
2023-02-17 11:34:51 -05:00
|
|
|
|
2025-07-06 23:44:33 -04:00
|
|
|
function calcTaperLength(rad, angle) {
|
2023-02-19 08:35:10 -05:00
|
|
|
return (rad / Math.tan(angle));
|
2025-12-06 00:09:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calcTaperBallExtent(rad, angle) {
|
|
|
|
|
return rad * (1 - Math.sin(angle * 2));
|
|
|
|
|
}
|
2023-02-17 11:34:51 -05:00
|
|
|
|
2025-07-06 23:44:33 -04:00
|
|
|
function getToolDiameter(settings, id) {
|
|
|
|
|
return new Tool(settings, id).fluteDiameter();
|
2025-12-06 00:09:17 -05:00
|
|
|
}
|
2020-11-05 14:47:18 -05:00
|
|
|
|
2025-07-06 23:44:33 -04:00
|
|
|
export {
|
|
|
|
|
Tool,
|
|
|
|
|
calcTaperAngle,
|
2025-12-06 00:09:17 -05:00
|
|
|
calcTaperBallExtent,
|
2025-07-06 23:44:33 -04:00
|
|
|
calcTaperLength,
|
|
|
|
|
getToolDiameter
|
|
|
|
|
};
|