grid-apps-cmms/src/kiri/mode/cam/tools.js

358 lines
11 KiB
JavaScript
Raw Normal View History

2024-09-14 22:35:29 -04:00
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $ } from '../../../moto/webui.js';
2025-12-25 01:44:20 -05:00
import { api } from '../../app/api.js';
import { consts } from '../../core/consts.js';
2025-12-25 11:37:12 -05:00
import { settings as setconf } from '../../app/conf/manager.js';
import { Tool, calcTaperAngle, calcTaperBallExtent, calcTaperLength } from './tool.js';
2024-09-14 22:35:29 -04:00
const DEG2RAD = Math.PI / 180;
2024-09-14 22:35:29 -04:00
let { MODES } = consts,
DOC = document,
selectedTool = null,
editTools = null,
maxTool = 0;
2024-09-14 22:35:29 -04:00
function settings() {
return api.conf.get();
}
2024-09-14 22:35:29 -04:00
function renderTools() {
api.ui.toolSelect.innerHTML = '';
maxTool = 0;
editTools.forEach(function(tool, index) {
maxTool = Math.max(maxTool, tool.number);
tool.order = index;
let opt = DOC.createElement('option');
opt.appendChild(DOC.createTextNode(tool.name));
opt.onclick = function() { selectTool(tool) };
api.ui.toolSelect.appendChild(opt);
2024-09-17 10:40:42 -04:00
});
}
2024-09-14 22:35:29 -04:00
function selectTool(tool) {
const { ui } = api;
selectedTool = tool;
api.util.rec2ui({
toolName: selectedTool.name,
toolType: selectedTool.type,
toolNum: selectedTool.number,
toolMetric: selectedTool.metric,
toolShaftDiam: selectedTool.shaft_diam,
toolShaftLen: selectedTool.shaft_len,
toolFluteDiam: selectedTool.flute_diam,
toolFluteLen: selectedTool.flute_len,
toolTaperAngle: selectedTool.taper_angle,
toolTaperTip: selectedTool.taper_tip,
},{
toolName: ui.toolName,
toolType: ui.toolType,
toolNum: ui.toolNum,
toolMetric: ui.toolMetric,
toolShaftDiam: ui.toolShaftDiam,
toolShaftLen: ui.toolShaftLen,
toolFluteDiam: ui.toolFluteDiam,
toolFluteLen: ui.toolFluteLen,
toolTaperAngle: ui.toolTaperAngle,
toolTaperTip: ui.toolTaperTip,
});
if (tool.type === 'tapermill' || tool.type === 'taperball') {
const taperLen = tool.flute_len;
ui.toolTaperAngle.value =
selectedTool.taper_angle =
calcTaperAngle( (tool.flute_diam - tool.taper_tip) / 2, taperLen ).round(1);
} else if (tool === 'drill') {
ui.toolTaperAngle.value = selectedTool.taper_angle = 90;
} else {
ui.toolTaperAngle.value = selectedTool.taper_angle = 0;
2024-09-14 22:35:29 -04:00
}
renderTool(tool);
}
2024-09-14 22:35:29 -04:00
export function updateTool(ev) {
const { ui } = api;
let changed = {
toolName: selectedTool.name,
toolType: selectedTool.type,
toolNum: selectedTool.number,
toolMetric: selectedTool.metric,
toolShaftDiam: selectedTool.shaft_diam,
toolShaftLen: selectedTool.shaft_len,
toolFluteDiam: selectedTool.flute_diam,
toolFluteLen: selectedTool.flute_len,
toolTaperAngle: selectedTool.taper_angle,
toolTaperTip: selectedTool.taper_tip,
};
api.util.ui2rec(changed,{
toolName: ui.toolName,
toolType: ui.toolType,
toolNum: ui.toolNum,
toolMetric: ui.toolMetric,
toolShaftDiam: ui.toolShaftDiam,
toolShaftLen: ui.toolShaftLen,
toolFluteDiam: ui.toolFluteDiam,
toolFluteLen: ui.toolFluteLen,
toolTaperAngle: ui.toolTaperAngle,
toolTaperTip: ui.toolTaperTip,
});
selectedTool.name = changed.toolName;
selectedTool.type = changed.toolType;
selectedTool.number = changed.toolNum;
selectedTool.metric = changed.toolMetric;
selectedTool.shaft_diam = changed.toolShaftDiam;
selectedTool.shaft_len = changed.toolShaftLen;
selectedTool.flute_diam = changed.toolFluteDiam;
selectedTool.flute_len = changed.toolFluteLen;
selectedTool.taper_angle = changed.toolTaperAngle;
selectedTool.taper_tip = changed.toolTaperTip;
if (selectedTool.type === 'tapermill' || selectedTool.type === 'taperball') {
const rad = (selectedTool.flute_diam - selectedTool.taper_tip) / 2;
const ballRadius = selectedTool.type === 'taperball' ? selectedTool.taper_tip / 2 : 0;
if (ev && ev.target === ui.toolTaperAngle) {
const angle = parseFloat(ev.target.value || 5);
const len = calcTaperLength(rad, angle * DEG2RAD);
selectedTool.flute_len = len + ballRadius;
ui.toolTaperAngle.value = angle.round(1);
ui.toolFluteLen.value = selectedTool.flute_len.round(4);
} else {
const taperLen = selectedTool.flute_len - ballRadius;
ui.toolTaperAngle.value =
selectedTool.taper_angle =
calcTaperAngle(rad, taperLen).round(1);
}
} else {
ui.toolTaperAngle.value = selectedTool.taper_angle = 0;
}
renderTools();
setToolChanged(true);
renderTool(selectedTool);
}
function otag(o) {
if (Array.isArray(o)) {
let out = []
o.forEach(oe => out.push(otag(oe)));
return out.join('');
2024-09-14 22:35:29 -04:00
}
let tags = [];
Object.keys(o).forEach(key => {
let val = o[key];
let att = [];
Object.keys(val).forEach(tk => {
let tv = val[tk];
att.push(`${tk.replace(/_/g,'-')}="${tv}"`);
});
tags.push(`<${key} ${att.join(' ')}></${key}>`);
});
return tags.join('');
}
2024-09-14 22:35:29 -04:00
function renderTool(tool) {
const { ui } = api;
$('tool-view').innerHTML = '<svg id="tool-svg" width="100%" height="100%"></svg>';
setTimeout(() => {
let svg = $('tool-svg');
let pad = 10;
let dim = { w: svg.clientWidth, h: svg.clientHeight };
let max = { w: dim.w - pad * 2, h: dim.h - pad * 2 };
let off = { x: pad, y: pad };
// Create Tool instance and generate profile
const toolInst = new Tool(settings(), tool.id);
const resolution = (toolInst.maxDiameter() / toolInst.unitScale()) / 100;
toolInst.generateProfile(resolution);
const profile = toolInst.profile;
const { pix } = toolInst.profileDim;
// Extract cross-section at y=0 (center line)
// Profile is stored as [dx, dy, z_offset, dx, dy, z_offset, ...]
let crossSection = [];
for (let i = 0; i < profile.length; i += 3) {
let dx = profile[i];
let dy = profile[i + 1];
let z = profile[i + 2];
// Only take points where dy is approximately 0 (center line)
if (Math.abs(dy) < 0.01) {
crossSection.push({ x: dx * resolution, z });
}
}
// Section lengths
let slen = toolInst.shaftLength();
let flen = toolInst.fluteLength();
// Find bounds
let minZ = Math.min(...crossSection.map(p => p.z), -(slen + flen));
let maxZ = Math.max(...crossSection.map(p => p.z), 0);
let minX = Math.min(...crossSection.map(p => p.x));
let maxX = Math.max(...crossSection.map(p => p.x));
let zRange = maxZ - minZ;
let xRange = maxX - minX;
// Scale to fit
let scale = Math.min(max.h / zRange, max.w / xRange);
// Center horizontally if tool is narrower than viewport
let xOffset = off.x + (max.w - xRange * scale) / 2;
// Draw vertical lines for each point
let parts = [];
let stroke_width = 1;
crossSection.forEach(p => {
let x = xOffset + (p.x - minX) * scale;
let y1 = dim.h - off.y - (maxZ - p.z) * scale; // tip point
let y2 = dim.h - off.y - (flen) * scale; // top flute
let y3 = dim.h - off.y - (slen + flen) * scale; // top shaft
// flute
parts.push({ line: {
x1: x,
x2: x,
y1: y1,
y2: y2,
stroke: "#999999",
stroke_width
}});
// shaft
parts.push({ line: {
x1: x,
x2: x,
y1: y2,
y2: y3,
stroke: "#666666",
stroke_width
}});
});
svg.innerHTML = otag(parts);
}, 10);
}
2024-09-14 22:35:29 -04:00
function setToolChanged(changed) {
editTools.changed = changed;
api.ui.toolsSave.disabled = !changed;
}
2024-09-14 22:35:29 -04:00
export function showTools() {
if (api.mode.get_id() !== MODES.CAM) return;
setconf.sync.get().then(_showTools);
}
2024-09-14 22:35:29 -04:00
function _showTools() {
const { ui } = api;
2024-09-14 22:35:29 -04:00
editTools = settings().tools.slice().sort((a,b) => {
return a.name > b.name ? 1 : -1;
});
2024-09-14 22:35:29 -04:00
setToolChanged(false);
2024-09-14 22:35:29 -04:00
ui.toolsClose.onclick = function() {
if (editTools.changed && !confirm("abandon changes?")) return;
api.dialog.hide();
};
ui.toolAdd.onclick = function() {
let metric = settings().controller.units === 'mm';
editTools.push(Object.assign({
id: Date.now(),
number: maxTool + 1,
name: "new tool",
type: "endmill",
taper_tip: 0,
metric
}, metric ? {
shaft_diam: 2,
shaft_len: 15,
flute_diam: 2,
flute_len: 20,
} : {
shaft_diam: 0.25,
shaft_len: 1.5,
flute_diam: 0.25,
flute_len: 2,
}));
setToolChanged(true);
2024-09-14 22:35:29 -04:00
renderTools();
ui.toolSelect.selectedIndex = editTools.length-1;
selectTool(editTools[editTools.length-1]);
};
ui.toolCopy.onclick = function() {
let clone = Object.assign({}, selectedTool);
let { name } = clone;
let split = name.split(' ');
let endv = parseInt(split.pop());
if (endv) {
name = split.join(' ') + ' ' + (endv + 1);
2024-09-14 22:35:29 -04:00
} else {
name = `${name} 2`;
2024-09-14 22:35:29 -04:00
}
clone.id = Date.now();
clone.number = maxTool + 1;
clone.name = name;
editTools.push(clone);
setToolChanged(true);
renderTools();
ui.toolSelect.selectedIndex = editTools.length-1;
selectTool(editTools[editTools.length-1]);
};
ui.toolDelete.onclick = function() {
editTools.remove(selectedTool);
setToolChanged(true);
renderTools();
};
ui.toolsSave.onclick = function() {
if (selectedTool) updateTool();
settings().tools = editTools.sort((a,b) => {
return a.name < b.name ? -1 : 1;
});
setToolChanged(false);
api.conf.save();
api.conf.update_fields();
api.event.settings();
setconf.sync.put();
};
ui.toolsImport.onclick = (ev) => api.event.import(ev);
ui.toolsExport.onclick = () => {
api.uc.prompt("Export Tools Filename", "tools").then(name => {
if (!name) {
return;
}
const record = {
2025-09-02 14:01:16 -04:00
version: api.version,
tools: api.conf.get().tools,
time: Date.now()
};
api.util.download(api.util.b64enc(record), `${name}.km`);
});
};
2024-09-14 22:35:29 -04:00
renderTools();
if (editTools.length > 0) {
selectTool(editTools[0]);
ui.toolSelect.selectedIndex = 0;
} else {
ui.toolAdd.onclick();
2024-09-14 22:35:29 -04:00
}
api.dialog.show('tools');
ui.toolSelect.focus();
}
export const tools = {
updateTool,
showTools
};