commit
daebb07ee1
18 changed files with 792 additions and 44 deletions
|
|
@ -54,6 +54,21 @@
|
|||
"camDrillLift": 2,
|
||||
"camDrillingOn": false,
|
||||
"camRegisterSpeed": 1000,
|
||||
"camHelicalTool": 1000,
|
||||
"camHelicalSpindle": 1000,
|
||||
"camHelicalDownSpeed": 250,
|
||||
"camHelicalSpeed": 1000,
|
||||
"camHelicalDown": 5,
|
||||
"camHelicalBottomFinish":true,
|
||||
"camHelicalThru": 0,
|
||||
"camHelicalOffset": "auto",
|
||||
"camHelicalForceStartAngle": false,
|
||||
"camHelicalStartAngle": 0,
|
||||
"camHelicalOffsetOverride": 0,
|
||||
"camHelicalEntry": false,
|
||||
"camHelicalEntryOffset":0,
|
||||
"camHelicalReverse": false,
|
||||
"camHelicalClockwise": true,
|
||||
"camFlipAxis": "X",
|
||||
"camFlipOther": "",
|
||||
"camTabsWidth": 20,
|
||||
|
|
|
|||
|
|
@ -538,6 +538,10 @@ export function arcToPath(start, end, arcdivs = 24, opts) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(arcdivs <= 2){
|
||||
return [start.clone(), end.clone()];
|
||||
}
|
||||
|
||||
if (center) {
|
||||
// center = center.add(start);
|
||||
center.r = center.distTo2D(start);
|
||||
|
|
@ -569,27 +573,17 @@ export function arcToPath(start, end, arcdivs = 24, opts) {
|
|||
let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI;
|
||||
let ad = base.util.thetaDiff(a1, a2, clockwise); // angle difference in radians
|
||||
let samePoint = Math.abs(ad) < 0.001
|
||||
let ofFull = Math.abs(ad) / (2 * Math.PI);
|
||||
let steps = samePoint ? arcdivs : Math.max(Math.floor(arcdivs * ofFull), 4);
|
||||
let step = (samePoint ? (Math.PI * 2) : ad) / steps;
|
||||
let numPoints = steps / step;
|
||||
let ofFull = Math.abs(ad)/(2*Math.PI);
|
||||
let steps = samePoint? arcdivs : Math.max(Math.floor( arcdivs * ofFull),4);
|
||||
let step = (samePoint? (Math.PI*2)*(clockwise? -1 : 1) : ad) / steps;
|
||||
let zStart = start.z;
|
||||
let zStep = dz / numPoints;
|
||||
let rot = a1 + step;
|
||||
let zStep = -dz / steps;
|
||||
let rot = a1;
|
||||
|
||||
//unused deltas
|
||||
let da = Math.abs(a1 - a2);
|
||||
let dd = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// LOG({index, da, dd, first: pos, last: rec, center, a1, a2, ad, step, steps, rot, line});
|
||||
// G0G1(false, [`X${center.x}`, `Y${center.y}`, `E1`]);
|
||||
|
||||
// under 1 degree arc and 5mm, convert to straight line
|
||||
// if (da < 0.005 && dd < 5) {
|
||||
// G0G1(false, [`X${end.x}`, `Y${end.y}`, `E1`]);
|
||||
// return ;
|
||||
// }
|
||||
|
||||
let arr = [] // point accumulator
|
||||
for (let i = 0; i <= steps - 2; i++) {
|
||||
if (isNaN(center.r) || isNaN(center.x) || isNaN(center.y)) {
|
||||
|
|
|
|||
|
|
@ -518,6 +518,21 @@ export const conf = {
|
|||
camDrillingOn: false,
|
||||
camRegisterSpeed: 1000,
|
||||
camRegisterThru: 5,
|
||||
camHelicalTool: 1000,
|
||||
camHelicalSpindle: 1000,
|
||||
camHelicalDownSpeed: 250,
|
||||
camHelicalSpeed: 1000,
|
||||
camHelicalDown: 5,
|
||||
camHelicalBottomFinish: true,
|
||||
camHelicalThru: 0,
|
||||
camHelicalOffset: "auto",
|
||||
camHelicalForceStartAngle: false,
|
||||
camHelicalStartAngle: 0,
|
||||
camHelicalOffsetOverride: 0,
|
||||
camHelicalEntry: false,
|
||||
camHelicalEntryOffset:0,
|
||||
camHelicalReverse: false,
|
||||
camHelicalClockwise: true,
|
||||
camFlipAxis: "X",
|
||||
camFlipOther: "",
|
||||
camLaserEnable: ["M321"],
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@ const LISTS = {
|
|||
{ name: "inside" },
|
||||
{ name: "outside" }
|
||||
],
|
||||
helicaloff:[
|
||||
{ name: "auto" },
|
||||
{ name: "inside" },
|
||||
{ name: "outside" },
|
||||
],
|
||||
zanchor: [
|
||||
{ name: "top" },
|
||||
{ name: "middle" },
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export const UI = {
|
|||
bound,
|
||||
toInt,
|
||||
toFloat,
|
||||
toDegsFloat,
|
||||
isSticky,
|
||||
setSticky,
|
||||
newBoolean,
|
||||
|
|
@ -334,6 +335,18 @@ function toFloat() {
|
|||
return nv;
|
||||
}
|
||||
|
||||
function toDegsFloat(){
|
||||
let nv = this.value !== '' ? parseFloat(this.value) : null;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
nv = (nv+360)% 360; // bound the val to 0-359.99
|
||||
if (this.setv) {
|
||||
return this.setv(nv);
|
||||
} else {
|
||||
this.value = nv;
|
||||
}
|
||||
return nv;
|
||||
}
|
||||
|
||||
function raw() {
|
||||
return this.value !== '' ? this.value : null;
|
||||
}
|
||||
|
|
|
|||
63
src/kiri/mode/cam/cl-helical.js
Normal file
63
src/kiri/mode/cam/cl-helical.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { api } from "../../core/api.js";
|
||||
import { CAM } from "./driver-fe.js";
|
||||
import { clearPops, env } from "./client.js";
|
||||
|
||||
let alert, lastWidget;
|
||||
export let helicalOn = false;
|
||||
export function selectHelical() {
|
||||
if (helicalOn) {
|
||||
return helicalDone();
|
||||
}
|
||||
clearPops();
|
||||
alert = api.show.alert("analyzing surfaces...", 1000);
|
||||
let cylinders = env.poppedRec.cylinders;
|
||||
|
||||
CAM.surface_prep(false, () => {
|
||||
api.hide.alert(alert);
|
||||
alert = api.show.alert("[esc] cancels surface selection");
|
||||
for (let [wid, arr] of Object.entries(cylinders)) {
|
||||
let widget = api.widgets.forid(wid);
|
||||
if ( widget && arr?.length )
|
||||
for (let faceid of arr) {
|
||||
CAM.cylinderToggle(widget, faceid, (faceids) => {
|
||||
// cylinders[widget.id] = faceids;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
helicalOn = env.hoveredOp;
|
||||
helicalOn.classList.add("editing");
|
||||
api.feature.on_mouse_up = (obj, ev) => {
|
||||
let { face } = obj;
|
||||
let min = Math.min(face.a, face.b, face.c);
|
||||
let faceid = min / 3;
|
||||
let widget = (lastWidget = obj.object.widget);
|
||||
CAM.cylinderToggle(widget, faceid, ({ faces, error }) => {
|
||||
if ( error ) {
|
||||
api.show.alert(error, 3000);
|
||||
console.log( error );
|
||||
return;
|
||||
}
|
||||
cylinders[widget.id] = faces;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function helicalDone(){
|
||||
if (!( helicalOn && env.poppedRec && env.poppedRec.cylinders )) {
|
||||
return;
|
||||
}
|
||||
let { cylinders } = env.poppedRec;
|
||||
for (let wid of Object.keys(cylinders)) {
|
||||
let widget = api.widgets.forid(wid);
|
||||
if (widget) {
|
||||
CAM.cylinderClear(widget);
|
||||
} else {
|
||||
delete cylinders[wid];
|
||||
}
|
||||
}
|
||||
api.hide.alert(alert);
|
||||
api.feature.on_mouse_up = undefined;
|
||||
helicalOn.classList.remove("editing");
|
||||
helicalOn = false;
|
||||
};
|
||||
|
|
@ -140,17 +140,7 @@ export async function selectHoles(individual) {
|
|||
|
||||
export function selectHolesHover(data) {
|
||||
//not used right now. may be useful in the future
|
||||
if (lastTrace) {
|
||||
let { color, colorSave } = lastTrace.material[0] || lastTrace.material;
|
||||
color.r = colorSave.r;
|
||||
color.g = colorSave.g;
|
||||
color.b = colorSave.b;
|
||||
lastTrace.position.z -= 0.01;
|
||||
}
|
||||
if (data.type === 'platform') {
|
||||
setLastTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function selectHolesHoverUp(int, ev) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { env } from './client.js';
|
|||
import { Tool } from './tool.js';
|
||||
import { opFlip } from './cl-flip.js';
|
||||
import { selectHoles } from './cl-hole.js';
|
||||
import { selectHelical } from './cl-helical.js';
|
||||
import { surfaceAdd } from './cl-surface.js';
|
||||
import { traceAdd } from './cl-trace.js';
|
||||
|
||||
|
|
@ -524,6 +525,49 @@ export function createPopOps() {
|
|||
thru: UC.newInput(LANG.cd_thru_s, { title: LANG.cd_thru_l, convert: UC.toFloat, units: true }),
|
||||
};
|
||||
|
||||
createPopOp('helical', {
|
||||
tool: 'camHelicalTool',
|
||||
offset: 'camHelicalOffset',
|
||||
spindle: 'camHelicalSpindle',
|
||||
rate: 'camHelicalDownSpeed',
|
||||
feed: 'camHelicalSpeed',
|
||||
down: 'camHelicalDown',
|
||||
finish: 'camHelicalBottomFinish',
|
||||
startAng:'camHelicalStartAngle',
|
||||
offOver: 'camHelicalOffsetOverride',
|
||||
entry: 'camHelicalEntry',
|
||||
entryOffset: 'camHelicalEntryOffset',
|
||||
reverse: 'camHelicalReverse',
|
||||
clockwise:'camHelicalClockwise',
|
||||
thru: 'camHelicalThru',
|
||||
forceStartAng:'camHelicalForceStartAngle',
|
||||
fromTop: 'camHelicalFromStockTop',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l,}, "helicaloff"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
spindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, show:hasSpindle}),
|
||||
rate: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toFloat, units:true}),
|
||||
feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toFloat, units:true}),
|
||||
down: UC.newInput(LANG.ch_sdwn_s, {title:LANG.ch_sdwn_l, convert:UC.toFloat, units:true}),
|
||||
startAng: UC.newInput(LANG.ch_stra_s, {title:LANG.ch_stra_l, convert:UC.toDegsFloat, bound:UC.bound(-360,360),show:() => env.poppedRec.forceStartAng}),
|
||||
offOver: UC.newInput(LANG.cc_offd_s, {title:LANG.cc_offd_l, convert:UC.toFloat, units:true, bound:UC.bound(0,Infinity)}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
entry: UC.newBoolean(LANG.ch_entr_s,undefined, {title:LANG.ch_entr_l}),
|
||||
entryOffset: UC.newInput(LANG.ch_ento_s, {title:LANG.ch_ento_l, convert:UC.toFloat, units:true, show:() => env.poppedRec.entry}),
|
||||
reverse: UC.newBoolean(LANG.ch_rvrs_s,undefined, {title:LANG.ch_rvrs_l}),
|
||||
clockwise:UC.newBoolean(LANG.ch_clkw_s,undefined, {title:LANG.ch_clkw_l}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
finish: UC.newBoolean(LANG.ch_fini_s,undefined, { title:LANG.ch_fini_l ,show: ()=>!env.poppedRec.reverse}),
|
||||
forceStartAng: UC.newBoolean(LANG.ch_fsta_s, undefined, {title:LANG.ch_fsta_l }),
|
||||
fromTop: UC.newBoolean(LANG.cd_ftop_s,undefined, {title:LANG.cd_ftop_l}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}),
|
||||
actions: UC.newRow([
|
||||
UC.newButton(LANG.select, selectHelical, {title:LANG.cd_seli_l}),
|
||||
], {class:"ext-buttons f-col"})
|
||||
};
|
||||
|
||||
createPopOp('flip', {
|
||||
axis: 'camFlipAxis',
|
||||
invert: 'camFlipInvert'
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { tabAdd, tabDone, tabClear, restoreTabs, rotateTabs, updateTabs, clearTa
|
|||
import { traceOn, traceDone, unselectTraces } from './cl-trace.js';
|
||||
import { holeSelOn, selectHolesDone, clearHolesRec } from './cl-hole.js';
|
||||
import { surfaceOn, surfaceDone } from './cl-surface.js';
|
||||
import { helicalOn, helicalDone } from './cl-helical.js';
|
||||
import { originSelectDone } from './cl-origin.js';
|
||||
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
|
@ -60,6 +61,8 @@ export function clearPops() {
|
|||
surfaceDone();
|
||||
tabDone();
|
||||
traceDone();
|
||||
selectHolesDone();
|
||||
helicalDone();
|
||||
}
|
||||
|
||||
function animFn() {
|
||||
|
|
@ -172,6 +175,14 @@ const opAddPocket = () => {
|
|||
opAdd(rec);
|
||||
};
|
||||
|
||||
const opAddHelical = () => {
|
||||
traceDone();
|
||||
surfaceDone();
|
||||
let rec = env.popOp.helical.new();
|
||||
rec.cylinders = { /* widget.id: [ faces... ] */ };
|
||||
opAdd(rec);
|
||||
};
|
||||
|
||||
const opAddContour = (axis) => {
|
||||
let rec = env.popOp.contour.new();
|
||||
rec.axis = axis.toUpperCase();
|
||||
|
|
@ -728,6 +739,7 @@ export function init() {
|
|||
case "drill": return opAddDrill();
|
||||
case "trace": return opAddTrace();
|
||||
case "pocket": return opAddPocket();
|
||||
case "helical": return opAddHelical();
|
||||
case "flip":
|
||||
// only one flip op permitted
|
||||
for (let op of env.current.process.ops) {
|
||||
|
|
@ -793,6 +805,9 @@ export function init() {
|
|||
if (traceOn) {
|
||||
traceDone();
|
||||
}
|
||||
if(helicalOn){
|
||||
helicalDone();
|
||||
}
|
||||
unselectTraces(widget);
|
||||
if (env.flipping) {
|
||||
return;
|
||||
|
|
@ -816,6 +831,9 @@ export function init() {
|
|||
if (holeSelOn) {
|
||||
selectHolesDone();
|
||||
}
|
||||
if(helicalOn){
|
||||
helicalDone();
|
||||
}
|
||||
clearHolesRec(widget)
|
||||
unselectTraces(widget);
|
||||
if (env.flipping) {
|
||||
|
|
@ -836,6 +854,9 @@ export function init() {
|
|||
if (holeSelOn) {
|
||||
selectHolesDone();
|
||||
}
|
||||
if(helicalOn){
|
||||
helicalDone();
|
||||
}
|
||||
if (env.flipping) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { cam_slice, holes, traces } from './slice.js';
|
|||
import { cam_prepare } from './prepare.js';
|
||||
import { cam_export } from './export.js';
|
||||
import { tool as mesh_tool } from '../../../mesh/tool.js';
|
||||
import { cylinder_poly_find } from './slice.js';
|
||||
|
||||
import { init as init_2d } from './anim-2d-be.js';
|
||||
import { init as init_3d } from './anim-3d-be.js';
|
||||
|
|
@ -97,6 +98,16 @@ function init(worker) {
|
|||
shadowed: widget.shadowedDrills
|
||||
} } )));
|
||||
}
|
||||
|
||||
dispatch.cam_cylinder_find = async function(data,send){
|
||||
const { id, face, settings } = data;
|
||||
const widget = worker.cache[id];
|
||||
try{
|
||||
send.done(cylinder_poly_find(widget, face));
|
||||
}catch(error){
|
||||
send.done({error});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CAM = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ function surface_show(widget) {
|
|||
widget.selectFaces(Object.values(widget._surfaces).flat());
|
||||
};
|
||||
|
||||
function cylinder_show(widget) {
|
||||
widget.selectFaces(Object.values(widget._cylinders).flat());
|
||||
};
|
||||
|
||||
function surface_toggle(widget, face, radians, ondone) {
|
||||
let surfaces = widget._surfaces = widget._surfaces || {};
|
||||
for (let [root, faces] of Object.entries(surfaces)) {
|
||||
|
|
@ -78,9 +82,44 @@ function holes(indiv, rec, onProgress, onDone) {
|
|||
})
|
||||
};
|
||||
|
||||
function cylinderShow(onProgress,onDone){
|
||||
kiri.client.sync();
|
||||
const settings = api.conf.get();
|
||||
}
|
||||
|
||||
function cylinderToggle(widget, face, onDone){
|
||||
let cyls = widget._cylinders = widget._cylinders || {};
|
||||
for (let [root, faces] of Object.entries(cyls)) {
|
||||
if (faces.contains(face)) { // if a face group contains the selected face
|
||||
// delete this face group
|
||||
delete cyls[root];
|
||||
CAM.cylinder_show(widget);
|
||||
onDone(Object.keys(cyls).map(v => parseInt(v)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
//send
|
||||
api.client.send("cam_cylinder_find", { id: widget.id, face }, ({faces,error}) => {
|
||||
if (faces?.length) {
|
||||
cyls[face] = faces;
|
||||
CAM.cylinder_show(widget);
|
||||
}
|
||||
|
||||
onDone({faces:Object.keys(cyls).map(v => parseInt(v)),error});
|
||||
});
|
||||
}
|
||||
|
||||
function cylinderClear(widget){
|
||||
widget.selectFaces([]);
|
||||
widget._cylinders = {};
|
||||
}
|
||||
|
||||
export const CAM = {
|
||||
surface_prep,
|
||||
surface_show,
|
||||
cylinder_show,
|
||||
cylinderToggle,
|
||||
cylinderClear,
|
||||
surface_toggle,
|
||||
surface_clear,
|
||||
traces,
|
||||
|
|
|
|||
278
src/kiri/mode/cam/op-helical.js
Normal file
278
src/kiri/mode/cam/op-helical.js
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
import { arcToPath } from "../../../geo/paths.js";
|
||||
import { newPolygon } from "../../../geo/polygon.js";
|
||||
import { CamOp } from "./op.js";
|
||||
import { Tool } from "./tool.js";
|
||||
import { cylinder_poly_find } from "./slice.js";
|
||||
import { newPoint } from "../../../geo/point.js";
|
||||
import { newSlice } from "../../core/slice.js";
|
||||
|
||||
export class OpHelical extends CamOp {
|
||||
constructor(state, op) {
|
||||
super(state, op);
|
||||
}
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, addSlices, widget, updateToolDiams, slicer } = state;
|
||||
let { color } = state;
|
||||
let {
|
||||
cylinders,
|
||||
forceStartAng,
|
||||
startAng,
|
||||
fromTop,
|
||||
offOver,
|
||||
offset,
|
||||
down,
|
||||
thru,
|
||||
finish,
|
||||
clockwise,
|
||||
reverse,
|
||||
entry,
|
||||
entryOffset,
|
||||
} = op;
|
||||
let {stock} = settings
|
||||
|
||||
let tool = new Tool(settings, op.tool),
|
||||
toolDiam = tool.fluteDiameter(),
|
||||
sliceOut = (this.sliceOut = []),
|
||||
startAngle = forceStartAng ? startAng : 0;
|
||||
|
||||
updateToolDiams(toolDiam);
|
||||
// console.log({cylinders})
|
||||
let faces = cylinders[widget.id] ?? [];
|
||||
if (faces.faces) faces = faces.faces;
|
||||
let polys = [];
|
||||
|
||||
//iterate over selected faces
|
||||
for (let [i,face] of faces.entries()) {
|
||||
//get poly info for the cylinder
|
||||
let res
|
||||
try{
|
||||
res = cylinder_poly_find(
|
||||
widget,
|
||||
face
|
||||
);
|
||||
}catch(error){
|
||||
// don't quit if a cylinder errors out, just skip
|
||||
console.error(error)
|
||||
continue
|
||||
}
|
||||
let { zmin, zmax, diam, center, interior, faces } = res;
|
||||
// console.log({ zmin, zmax, diam, center, interior });
|
||||
let radius = diam >> 1;
|
||||
zmax = fromTop ? stock.z: zmax;
|
||||
|
||||
let radAdd,
|
||||
zBottom = zmin - thru,
|
||||
numSegs = faces.length / 2,
|
||||
poly = newPolygon().setOpen();
|
||||
|
||||
if (offOver) {
|
||||
//apply offset override
|
||||
if (offset == "auto") {
|
||||
radAdd = interior ? -offOver : offOver;
|
||||
} else if (offset == "inside") {
|
||||
radAdd = -offOver;
|
||||
} else if (offset == "outside") {
|
||||
radAdd = offOver;
|
||||
}
|
||||
} else {
|
||||
//otherwise, calculate based on offset type
|
||||
if (offset == "auto") {
|
||||
radAdd = (interior ? -toolDiam : toolDiam) / 2;
|
||||
} else if (offset == "inside") {
|
||||
radAdd = -toolDiam / 2;
|
||||
} else if (offset == "outside") {
|
||||
radAdd = toolDiam / 2;
|
||||
}
|
||||
}
|
||||
radius += radAdd;
|
||||
|
||||
if( radius <= 0 ){
|
||||
//if negative radius, skip this cylinder
|
||||
console.log('negative radius', radius);
|
||||
continue
|
||||
}
|
||||
|
||||
let startPoint = center
|
||||
.clone()
|
||||
.setZ(zmax)
|
||||
.add(
|
||||
newPoint(
|
||||
Math.cos(startAngle) * radius,
|
||||
Math.sin(startAngle) * radius,
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
let currentZ = zmax;
|
||||
for (;;) {
|
||||
let bottom = currentZ - down;
|
||||
if ( bottom > zBottom ) {
|
||||
//if not at the bottom, do a whole helix
|
||||
let start = startPoint.setZ(currentZ),
|
||||
end = startPoint.clone().setZ(bottom);
|
||||
// console.log(currentZ, bottom, [start.z, end.z]);
|
||||
let path = arcToPath(start, end, numSegs, {
|
||||
clockwise,
|
||||
radius,
|
||||
center,
|
||||
});
|
||||
// console.log({ path, start, end, radius, center });
|
||||
poly.addPoints(path);
|
||||
} else {
|
||||
//if at the bottom, do a partial helix
|
||||
let toBottom = currentZ - zBottom,
|
||||
ofFull = toBottom / down,
|
||||
ofCircle = ofFull * (2 * Math.PI),
|
||||
cwMultiplier = clockwise ? -1 : 1,
|
||||
finalAngle = startAngle + (ofCircle*cwMultiplier),
|
||||
finalEnd = center
|
||||
.clone()
|
||||
.setZ(zBottom)
|
||||
.add(
|
||||
newPoint(
|
||||
Math.cos(finalAngle) * radius,
|
||||
Math.sin(finalAngle) * radius,
|
||||
0
|
||||
)
|
||||
);
|
||||
// console.log({down,toBottom,ofFull,ofCircle,startAngle,finalAngle,finalEnd});
|
||||
|
||||
//circle down to final point
|
||||
poly.addPoints(
|
||||
arcToPath(startPoint.setZ(currentZ), finalEnd, numSegs , {
|
||||
clockwise,
|
||||
radius,
|
||||
center,
|
||||
})
|
||||
);
|
||||
|
||||
//if starting at the bottom, reverse poly
|
||||
if ( reverse ){
|
||||
poly.reverse();
|
||||
}
|
||||
|
||||
if ( entry ){
|
||||
//get first z of poly to use for entry
|
||||
let firstZ = poly.first().z;
|
||||
//create new pointArray
|
||||
let entry =[newPoint(0,0,0)];
|
||||
//add center point
|
||||
let entryCenter = center.clone().setZ(firstZ)
|
||||
|
||||
//if doing a curved enter, add more
|
||||
if ( entryOffset > 0 ){
|
||||
if ( entryOffset > radius ){
|
||||
console.error("entryOffset must be less than radius of helix");
|
||||
}else{
|
||||
//calculate entry center
|
||||
let lineOut = radius - entryOffset;
|
||||
let centerOffset = (entryOffset**2 + 2*entryOffset*lineOut) / (2*(entryOffset + lineOut));
|
||||
//add entry points
|
||||
entry.push(...
|
||||
newPoint(0,-lineOut,0),
|
||||
arcToPath(
|
||||
newPoint(0,-lineOut,0),
|
||||
newPoint(radius,0,0),
|
||||
128,
|
||||
{
|
||||
clockwise:false,
|
||||
radius:lineOut,
|
||||
center:newPoint(centerOffset,0,0)
|
||||
}
|
||||
),
|
||||
newPoint(radius,0,0),
|
||||
)
|
||||
}
|
||||
}
|
||||
//for each point
|
||||
entry = entry.map(p => {
|
||||
//rotate by start angle
|
||||
// add to hole center
|
||||
// TODO: there's totally a way to do this with a matrix
|
||||
p.y *= clockwise ? 1 : -1
|
||||
p.rotate(startAngle)
|
||||
return p.add(entryCenter)
|
||||
})
|
||||
//append to start of poly
|
||||
poly = newPolygon().setOpen().addPoints(entry).addPoints(poly.points);
|
||||
}
|
||||
//if doing a finish pass, do a full circle at the bottom
|
||||
if ( finish && !reverse ){
|
||||
poly.addPoints(
|
||||
arcToPath(finalEnd, finalEnd, numSegs, {
|
||||
clockwise,
|
||||
radius,
|
||||
center,
|
||||
}),
|
||||
);
|
||||
poly.append(
|
||||
finalEnd.clone().setZ(zBottom),
|
||||
)
|
||||
}
|
||||
break;
|
||||
}
|
||||
currentZ = bottom;
|
||||
}
|
||||
|
||||
progress(i/faces.length, "Helical intrerpolation");
|
||||
polys.push(poly);
|
||||
}
|
||||
|
||||
let slice = newSlice(0);
|
||||
|
||||
slice.camTrace = { tool: op.tool, rate: op.feed, plunge: op.rate };
|
||||
slice.camLines = polys;
|
||||
|
||||
slice
|
||||
.output()
|
||||
.setLayer("Helical", { face: color, line: color })
|
||||
.addPolys(slice.camLines);
|
||||
addSlices(slice);
|
||||
sliceOut.push(slice);
|
||||
}
|
||||
|
||||
async prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, addSlices, updateToolDiams } = state;
|
||||
let {
|
||||
setTool,
|
||||
setSpindle,
|
||||
polyEmit,
|
||||
camOut,
|
||||
setPrintPoint,
|
||||
getPrintPoint,
|
||||
} = ops;
|
||||
let { tool, spindle, rate, feed } = op;
|
||||
setTool(tool, feed, rate);
|
||||
setSpindle(spindle);
|
||||
|
||||
let [polys] = this.sliceOut.map((s) => s.camLines);
|
||||
let pp = getPrintPoint();
|
||||
|
||||
polys = polys.slice();
|
||||
for (;;) {
|
||||
let closestDist = Infinity,
|
||||
closestI,
|
||||
closest = null,
|
||||
dist,
|
||||
poly;
|
||||
|
||||
for (let i = 0; i < polys.length; i++) {
|
||||
if (!polys[i]) continue;
|
||||
if ((dist = polys[i].first().distTo2D(pp)) < closestDist) {
|
||||
closestDist = dist;
|
||||
closest = polys[i];
|
||||
closestI = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!closest) break;
|
||||
poly = polys[closestI]
|
||||
polys[closestI] = null;
|
||||
//emit
|
||||
pp = polyEmit(poly,0,1,poly.points[0])
|
||||
}
|
||||
setPrintPoint(pp);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import { OpLaserOff } from './op-laser-off.js';
|
|||
import { OpGCode } from './op-gcode.js';
|
||||
import { OpIndex } from './op-index.js';
|
||||
import { CamOp } from './op.js';
|
||||
import { OpHelical } from './op-helical.js';
|
||||
|
||||
export const ops = {
|
||||
"xray": OpXRay,
|
||||
|
|
@ -29,6 +30,7 @@ export const ops = {
|
|||
"trace": OpTrace,
|
||||
"drill": OpDrill,
|
||||
"register": OpRegister,
|
||||
"helical": OpHelical,
|
||||
"laser on": OpLaserOn,
|
||||
"laser off": OpLaserOff,
|
||||
"gcode": OpGCode,
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
* @param {number} opts.factor speed scale factor
|
||||
*/
|
||||
function camOut(point, emit, opts) {
|
||||
// console.log({point, emit, opts})
|
||||
// console.trace({point, emit, opts})
|
||||
let {
|
||||
center = {},
|
||||
clockwise = true,
|
||||
|
|
@ -742,7 +742,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
function polyEmit(poly, index, count, fromPoint) {
|
||||
let arcQ = [],
|
||||
arcMax = Infinity, // no max arc radius
|
||||
lineTolerance = 0.001; // do not consider points under 0.001mm for arcs
|
||||
lineTolerance = 0.001, // do not consider points under 0.001mm for arcs
|
||||
zTolerance = 0.1; // allow zs of Points to be off by 0.001mm per radian of rotation
|
||||
|
||||
fromPoint = fromPoint || printPoint;
|
||||
arcQ.angle = []
|
||||
|
|
@ -818,6 +819,18 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
radFault = true;
|
||||
}
|
||||
|
||||
let endDelta = e1.distTo2D(e3)
|
||||
if(endDelta < 0.01){
|
||||
console.log("circleCompleteFault")
|
||||
let last = arcQ.peek()
|
||||
drainQ(true)
|
||||
arcQ.push(last)
|
||||
return e3
|
||||
}
|
||||
|
||||
let ddz = Math.abs((e3.z - e4.z) - (e4.z - e5.z)) // take second derivitive of z
|
||||
let zFault = Math.abs(ddz) > zTolerance
|
||||
|
||||
if (cc) {
|
||||
if ([cc.x, cc.y, cc.z, cc.r].hasNaN()) {
|
||||
// console.log({cc, e1, e2, e3});
|
||||
|
|
@ -829,15 +842,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
arcQ.rSum = cc.r;
|
||||
|
||||
// check if first angles should have caused radFault
|
||||
let a = toRadians(arcQ[0].slopeTo(cc).angle)
|
||||
let b = toRadians(arcQ[1].slopeTo(cc).angle)
|
||||
let angle = Math.abs(b - a)
|
||||
|
||||
if (Math.abs(angle) > arcRes) {
|
||||
let angle = toRadians(arcQ[0].slopeTo(cc).angleDiff(arcQ[1].slopeTo(cc)).angle)
|
||||
radFault = Math.abs(angle) > arcRes
|
||||
if (radFault) {
|
||||
// if so, remove first point
|
||||
|
||||
console.log("secondary radfault,",structuredClone(arcQ),{angle,arcRes,a,b})
|
||||
camOut(arcQ.shift(), 1)
|
||||
radFault = true
|
||||
}
|
||||
|
||||
} else {
|
||||
// check center point delta
|
||||
arcQ.xSum = arcQ.center.reduce(function (t, v) { return t + v.x }, 0);
|
||||
|
|
@ -848,10 +861,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
dc = Math.hypot(dx, dy); // delta center distance
|
||||
}
|
||||
// if new point is off the arc
|
||||
// if (deem || depm || desp || dc > arcTolerance || cc.r < arcMin || cc.r > arcMax || dist > cc.r) {
|
||||
if (dc * arcQ.center.length / arcQ.rSum > arcTolerance || dist > cc.r || cc.r > arcMax || radFault) {
|
||||
// let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcTolerance, dist > cc.r, cc.r > arcMax, radFault];
|
||||
// console.log("point off the arc,",structuredClone(arcQ),);
|
||||
// if point is off-center, or too far from center, or too large of a radius
|
||||
if (dc * arcQ.center.length / arcQ.rSum > arcTolerance || dist > cc.r || cc.r > arcMax || radFault || zFault) {
|
||||
// let debug = [ dc * arcQ.center.length / arcQ.rSum > arcTolerance, dist > cc.r, cc.r > arcMax, radFault];
|
||||
// console.log("point off the arc,",structuredClone(arcQ),radFault,zFault,[dc * arcQ.center.length / arcQ.rSum > arcTolerance , dist > cc.r , cc.r > arcMax]);
|
||||
if (arcQ.length === 4) {
|
||||
// not enough points for an arc, drop first point and recalc center
|
||||
camOut(arcQ.shift(), 1);
|
||||
|
|
@ -899,7 +912,12 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
return point;
|
||||
}
|
||||
|
||||
function drainQ() {
|
||||
/**
|
||||
* Emits arcs and/or lines from the arcQ to the current point set.
|
||||
* @param {boolean} forceCircle emits a single circle iven if poly is not closed.
|
||||
*/
|
||||
function drainQ(forceCircle = false) {
|
||||
// console.trace("draining")
|
||||
let arcPreviewRes = 64
|
||||
|
||||
if (!arcTolerance) {
|
||||
|
|
@ -915,6 +933,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
let from = arcQ[0];
|
||||
let to = arcQ.peek();
|
||||
let delta = from.distTo2D(to)
|
||||
let closed = poly.isClosed()
|
||||
arcQ.xSum = arcQ.center.reduce((t, v) => t + v.x, 0);
|
||||
arcQ.ySum = arcQ.center.reduce((t, v) => t + v.y, 0);
|
||||
arcQ.rSum = arcQ.center.reduce((t, v) => t + v.r, 0);
|
||||
|
|
@ -925,15 +944,20 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
)
|
||||
|
||||
// console.log("draining")
|
||||
if (arcQ.length == poly.points.length) {
|
||||
if (closed && arcQ.length == poly.points.length || forceCircle ) {
|
||||
//if is a circle
|
||||
// generate circle
|
||||
// console.log("circle",{from, to,center});
|
||||
let arcPoints = arcToPath(from, from, arcPreviewRes, { clockwise, center })
|
||||
.map(applyWidgetMovement);
|
||||
to = forceCircle? from.clone().setZ(to.z) : from
|
||||
|
||||
|
||||
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center })
|
||||
.map(applyWidgetMovement);
|
||||
|
||||
// console.log({arcPoints})
|
||||
|
||||
camOut(from, 1);
|
||||
camOut(from, gc, { center: center.sub(from), clockwise, arcPoints });
|
||||
// lastPoint = to.clone();
|
||||
camOut(to, gc, { center: center.sub(from), clockwise, arcPoints });
|
||||
} else {
|
||||
//if a non-circle arc
|
||||
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center })
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { setSliceTracker } from '../../core/slice.js';
|
|||
import { ops as OPS } from './ops.js';
|
||||
import { Tool } from './tool.js';
|
||||
import { Slicer as cam_slicer } from './slicer.js';
|
||||
import { CAM } from './driver-be.js';
|
||||
|
||||
/**
|
||||
* DRIVER SLICE CONTRACT
|
||||
|
|
@ -508,6 +509,123 @@ export async function traces(settings, widget) {
|
|||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of arrays of perpindicular triangles in the mesh.
|
||||
* Each sub-array is a list of triangle data that are part of the same
|
||||
* cylinder.
|
||||
*
|
||||
* @param {object} settings - settings object
|
||||
* @param {object} widget - widget object
|
||||
* @param {object} opts - options object
|
||||
* @return {array} - array of arrays of triangle data
|
||||
*/
|
||||
export async function cylinders(settings, widget, opts){
|
||||
|
||||
let {} = opts ?? {}
|
||||
|
||||
let {array:verts} = widget.mesh.geometry.attributes.position
|
||||
let perpTriangles = []
|
||||
|
||||
//iterate over all triangles
|
||||
for(let i = 0; i < verts.length; i+=9){
|
||||
let a = [verts[i], verts[i+1], verts[i+2]],
|
||||
b = [verts[i+3], verts[i+4], verts[i+5]],
|
||||
c = [verts[i+6], verts[i+7], verts[i+8]];
|
||||
|
||||
//calculate normal
|
||||
let normal = new THREE.Vector3(a[0]-b[0], a[1]-b[1], a[2]-b[2])
|
||||
.cross(new THREE.Vector3(b[0]-c[0], b[1]-c[1], b[2]-c[2]))
|
||||
.normalize()
|
||||
|
||||
// if perpindicular normal, and at least 2 Zs are the same
|
||||
if(normal.z.round(5) == 0 && ! (a[2] != b[2] && b[2] != c[2])){
|
||||
let minZ = Math.min(a[2], b[2], c[2]),
|
||||
maxZ = Math.max(a[2], b[2], c[2]);
|
||||
if(minZ == maxZ){ // all Zs are the same indicated malformed geometry
|
||||
continue;
|
||||
}
|
||||
perpTriangles.push({...[a,b,c], normal,minZ,maxZ,i:index})
|
||||
}
|
||||
|
||||
}
|
||||
//map where zmax, zmin -> triangleData
|
||||
let cylinderTriangles = new Map()
|
||||
for(let t of perpTriangles){
|
||||
let hash = `${t.minZ.round(5)},${t.maxZ.round(5)}`
|
||||
if(!cylinderTriangles.has(hash)){
|
||||
cylinderTriangles.set(hash,[])
|
||||
}
|
||||
cylinderTriangles.get(hash).push(t)
|
||||
}
|
||||
|
||||
return Array.from(cylinderTriangles.values())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find all triangles that are part of the same cylinder as the given triangle
|
||||
* @param {Widget} widget - widget object
|
||||
* @param {number} face - index of face in triangle vertex data
|
||||
* @return {array} - array of all triangle data that belong to the same cylinder
|
||||
* @requires surface_prep must be called first
|
||||
*/
|
||||
export function cylinder_find (widget, face){
|
||||
CAM.surface_prep(widget,false);
|
||||
return widget.tool.findCylinderSurface(face);
|
||||
}
|
||||
|
||||
export function cylinder_poly_find(widget, face){
|
||||
let faces = cylinder_find(widget, face);
|
||||
let vert = widget.getGeoVertices({ unroll: true, translate: true }).map(v => v.round(4));
|
||||
let slicer = new cam_slicer(widget,{})
|
||||
let opts = {
|
||||
dedup:false,
|
||||
edges: false,
|
||||
over:true
|
||||
}
|
||||
let firstOffset = faces[0]*9
|
||||
let [x1,y1,z1,x2,y2,z2,x3,y3,z3]= Array.from(vert.subarray(firstOffset,firstOffset+9))
|
||||
let zs = [z1,z2,z3].map(z => z.round(5))
|
||||
let zmin = Math.min(...zs);
|
||||
let zmax = Math.max(...zs);
|
||||
let zmid = (zmin+zmax)/2
|
||||
let cylVerts = new Float32Array(faces.length*9)
|
||||
|
||||
let i = 0
|
||||
//for each cylinder face index
|
||||
for(let index of faces){
|
||||
let off = index*9
|
||||
//copy the 9 triangle vertices floats in
|
||||
for(let j = 0;j < 9;j++){
|
||||
cylVerts[i] = vert[off+j]
|
||||
i++
|
||||
}
|
||||
}
|
||||
//slice the poly at the midpoint
|
||||
let poly = slicer.sliceZ(zmid, cylVerts,opts)?.polys?.at(0)
|
||||
if(!poly) throw "slicing returned no poly"
|
||||
let circular = poly.circularity() > 0.98;
|
||||
//throw error if not circular
|
||||
if(!circular) throw "faces must be circular"
|
||||
|
||||
//calculate circle properties
|
||||
let area = poly.area(),
|
||||
diam = Math.sqrt(area / Math.PI)* 2,
|
||||
center = poly.calcCircleCenter();
|
||||
|
||||
//find direction using normal to center point
|
||||
|
||||
let point = newPoint(x1, y1, z1),
|
||||
delta = point.setZ(center.z).sub(center),
|
||||
normal = new THREE.Vector3(x1-x2, y1-y2, z1-z2)
|
||||
.cross(new THREE.Vector3(x2-x3, y2-y3, z2-z3)),
|
||||
dotProd = normal.dot(delta),
|
||||
interior = dotProd < 0
|
||||
|
||||
return {faces, zmin, zmax, poly, circular, area, diam, center,interior}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a list of holes in the model based on the given diameter.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -233,11 +233,20 @@ class MeshTool {
|
|||
fn++;
|
||||
}
|
||||
}
|
||||
//faces are the normals in groups of 6 [nx,]
|
||||
|
||||
this.indexed = {
|
||||
faces, sides, sideExt
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the adjacent faces for a given face index.
|
||||
*
|
||||
* @param {number} face - The index of the face for which adjacent faces are to be found.
|
||||
* @returns {number[]} An array of face indices that are adjacent to the given face.
|
||||
*/
|
||||
|
||||
getAdjacentFaces(face) {
|
||||
const { faces, sides } = this.getIndex();
|
||||
const foff = face * 6;
|
||||
|
|
@ -294,6 +303,88 @@ class MeshTool {
|
|||
return faces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all faces in a cylinder surface connected to the given face.
|
||||
*
|
||||
* @param {number} face - the face index to start from
|
||||
* @return {number[]} face indices in the cylinder surface
|
||||
*
|
||||
* @throws {string} if the face has only one Z value
|
||||
* @throws {string} if the face normal is not perpendicular to Z axis
|
||||
*
|
||||
* The algorithm works by:
|
||||
* 1. Finding the two distinct Z values of the given face
|
||||
* 2. Filtering adjacent faces to those with the same Z values
|
||||
* 3. Filtering faces with normals perpendicular to Z axis
|
||||
* 4. Recursively adding adjacent faces with the same Z values and normal perpendicular to Z axis
|
||||
*/
|
||||
findCylinderSurface(face){
|
||||
face = Number(face)
|
||||
|
||||
const radianTolerance = 8 * (Math.PI/180);
|
||||
const vertexTolerance = 1e-4;
|
||||
//helper function
|
||||
const dissimilar = (a,b,c) => Math.abs(a-b) > vertexTolerance &&
|
||||
Math.abs(b-c) > vertexTolerance &&
|
||||
Math.abs(a-c) > vertexTolerance;
|
||||
|
||||
let found = {},
|
||||
out = [face],
|
||||
{vertices} = this,
|
||||
{faces} = this.getIndex(),
|
||||
faceOffset = face*9,
|
||||
zs = [2,5,8].map(i => vertices[faceOffset+i].round(5)), //get zs from face vertices
|
||||
lowZ = Math.min(zs),
|
||||
highZ = Math.max(zs),
|
||||
checked = {},
|
||||
check = [face];
|
||||
|
||||
//check for errors, and throw if found
|
||||
if(dissimilar(...zs)){
|
||||
throw `face must have only 2 Z values. found: ${zs.join(", ")}`
|
||||
}else if(Math.abs( faces[face * 6 + 2] > 1e-6 )){
|
||||
throw "face's normal must be perpendicular to Z axis"
|
||||
}
|
||||
|
||||
found[face] = 1;
|
||||
while (check.length) {
|
||||
const face = check.shift();
|
||||
const froot = face * 6;
|
||||
const vroot = face * 9
|
||||
|
||||
// filter faces to those perpendicular to +z axis
|
||||
if ( Math.abs( faces[froot + 2] > 1e-6 )) continue;
|
||||
//filter faces with verts don't share the z values of the first face
|
||||
if(
|
||||
!(vertices[vroot + 2] !== lowZ || vertices[vroot + 2] !== highZ) &&
|
||||
!(vertices[vroot + 5] !== lowZ || vertices[vroot + 5] !== highZ) &&
|
||||
!(vertices[vroot + 8] !== lowZ || vertices[vroot + 8] !== highZ)
|
||||
) continue;
|
||||
|
||||
const fadj = this.getAdjacentFaces(face);
|
||||
for (let f of fadj) {
|
||||
if (found[f] || checked[f]) {
|
||||
continue;
|
||||
}
|
||||
const aroot = f * 6;
|
||||
let sum = 0;
|
||||
for (let i=0; i<3; i++) {
|
||||
sum += Math.pow(faces[froot + i] - faces[aroot + i], 2);
|
||||
}
|
||||
const fn = Math.sqrt(sum);
|
||||
if (fn <= radianTolerance) {
|
||||
out.push(f);
|
||||
check.push(f)
|
||||
checked[f] = 1;
|
||||
found[f] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// given a list of faces (indices), return an array of closed
|
||||
// polylines that represent the outlines of each discrete island
|
||||
generateOutlines(list) {
|
||||
const { faces, sides } = this.getIndex();
|
||||
const prec = this.precision;
|
||||
|
|
|
|||
|
|
@ -437,6 +437,7 @@
|
|||
<div title="clean up after roughing or perform a part cutout">outline</div>
|
||||
<div title="2.5D tracing along X or Y axis used for complex part features">contour</div>
|
||||
<div title="clear areas above selected surfaces">pocket</div>
|
||||
<div title="follow a helix around circular paths">helical</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -608,6 +608,26 @@ self.lang['en-us'] = {
|
|||
cd_sela_s: "Select Matching Diameter",
|
||||
cd_sela_l: ["select all holes that", "match selected tool diameter"],
|
||||
|
||||
// CNC HELICAL tolpath
|
||||
ch_menu: "helical",
|
||||
ch_sdwn_s: "step down (pitch)",
|
||||
ch_sdwn_l: ["z-distance","per revolution","in workspace units","(inverse of threads per unit)", "0 to disable"],
|
||||
ch_fsta_s: "force start angle",
|
||||
ch_fsta_l: ["override start angle","defaults to closest point to tool"],
|
||||
ch_stra_s: "start angle",
|
||||
ch_stra_l: ["start angle of helix","in degrees ccw of +x axis"],
|
||||
ch_selc_l: ["select a cylinder face", "inner holes that are too small","will be ignored."],
|
||||
ch_fini_s: "bottom finish pass",
|
||||
ch_fini_l: ["trace a circle at bottom of helix","used for hole clearing"],
|
||||
ch_entr_s: "enter from center",
|
||||
ch_entr_l: ["start cut from center of cylinder","gently arcing in horisontaly", "instead of plunging"],
|
||||
ch_ento_s: "center entry offset",
|
||||
ch_ento_l: ["offset from path for curved entry","usually thread depth if threadmilling","in workspace units","0 for a linear entry"],
|
||||
ch_rvrs_s: "reverse",
|
||||
ch_rvrs_l: ["reverse helical z-axis direction","low-Z to high-Z when checked","useful for thread milling","not recomended for other operations"],
|
||||
ch_clkw_s: "clockwise",
|
||||
ch_clkw_l: ["reverse helical rotation around cylinder","clockwise when checked"],
|
||||
|
||||
// CNC FLIP
|
||||
cf_menu: "flip",
|
||||
cf_nvrt_s: "z bottom",
|
||||
|
|
@ -643,6 +663,10 @@ self.lang['en-us'] = {
|
|||
cl_maxz_s: "max z height",
|
||||
cl_maxz_l: ["highest Z band position. when set to 0, uses the highest Z point of the workspace"],
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// CNC CUTOUT TABS
|
||||
ct_menu: "tabs",
|
||||
ct_angl_s: "angle",
|
||||
|
|
|
|||
Loading…
Reference in a new issue