Merge branch 'rel-4.5' of https://github.com/GridSpace/grid-apps into drill-optimization

This commit is contained in:
bakedpotatolord 2025-11-30 15:36:16 -07:00
commit ce8cbc2c5c
27 changed files with 1190 additions and 581 deletions

View file

@ -1,9 +1,9 @@
// back end for device export in dev mode
module.exports = function(server) {
server.inject("kiri", "kiri.js");
if (!server.env.debug) {
return;
}
server.inject("kiri", "kiri.js");
const fs = require('fs');
server.api.postDevice = (req, res, next) => {
server.handler.decodePost(req, res, () => {
@ -16,7 +16,7 @@ module.exports = function(server) {
}
const { mode } = device;
const { deviceName } = device;
const path = `${server.const.rootdir}/src/kiri-dev/${mode.toLowerCase()}/${deviceName.replace(/\ /g,'.')}`;
const path = `${server.const.rootdir}/src/kiri/dev/${mode.toLowerCase()}/${deviceName.replace(/\ /g,'.')}`;
// console.log({ mode, device, process, path });
fs.writeFileSync(path, JSON.stringify(device, undefined, 4));
res.end('profile saved');

View file

@ -4,7 +4,7 @@ self.kiri.load(api => {
let deviceExport = api.device.export;
api.device.export = (exp, name, opt = {}) => {
const { event, record } = opt;
if (event && event.shiftKey & api.const.LOCAL) {
if (event && event.shiftKey) {
const { code, process, profiles } = record;
fetch("/api/postDevice", {
method: "POST",
@ -12,7 +12,7 @@ self.kiri.load(api => {
})
.then(r => r.text())
.then(t => {
// console.log({server_said: t});
console.log({server_said: t});
api.show.alert('profile saved to server');
});
} else {

View file

@ -70,6 +70,18 @@ export class Polygon {
return bounds;
}
getBounds3D(bounds = new THREE.Box3()) {
for (let point of this.points) {
bounds.expandByPoint(point);
}
if (this.inner) {
for (let i of this.inner) {
i.getBounds3D(bounds);
}
}
return bounds;
}
toPath2D(offset) {
return paths.pointsToPath(this.points, offset, this.open);
}
@ -985,7 +997,13 @@ export class Polygon {
}
minZ() {
return Math.min(...this.points.map(p => p.z));
let minZ = Math.min(...this.points.map(p => p.z));
if (this.inner) {
for (let i of this.inner) {
minZ = Math.min(minZ, i.minZ());
}
}
return minZ;
}
avgZ() {

View file

@ -73,6 +73,9 @@ let UC = UI.prefix('kiri').inputAction(settings.conf.update),
redo: noop // do.js
},
devel = {
get enabled() {
return settings.ctrl().devel;
},
xray(layers, raw) {
let proc = api.conf.get().process,
size = proc.sliceHeight || proc.slaSlice || 1,

View file

@ -423,6 +423,20 @@ export const conf = {
camArcEnabled: false,
camArcResolution: 5,
camArcTolerance: 0.15,
camAreaSpindle: 1000,
camAreaTool: 1000,
camAreaMode: "clear",
camAreaTrace: "none",
camAreaSurface: "linear",
camAreaAngle: 0,
camAreaOver: 0.4,
camAreaDown: 1,
camAreaSpeed: 1000,
camAreaPlunge: 100,
camAreaExpand: 0,
camAreaSmooth: 1,
camAreaRefine: 0,
camAreaOutline: false,
camContourAngle: 85,
camContourBottom: false,
camContourBridge: 0,

View file

@ -121,6 +121,15 @@ const LISTS = {
{ name: "adaptive" },
{ name: "legacy 1" },
{ name: "legacy 2" }
],
opmode: [
{ name: "clear" },
{ name: "trace" },
{ name: "surface" }
],
surftyp: [
{ name: "linear" },
{ name: "offset" },
]
};

View file

@ -1579,16 +1579,6 @@ function init_two() {
api.event.emit('load.lib', lib);
});
// load CSS extensions
if (SETUP.ss) SETUP.ss.forEach(function(style) {
style = style.charAt(0) === '/' ? style : `/kiri/style-${style}`;
let ss = DOC.createElement('link');
ss.setAttribute("type", "text/css");
ss.setAttribute("rel", "stylesheet");
ss.setAttribute("href", `${style}.css?${version}`);
DOC.body.appendChild(ss);
});
// override stored settings
if (SETUP.v) SETUP.v.forEach(function(kv) {
kv = kv.split('=');

View file

@ -54,7 +54,7 @@ function onLeftySelect() {
let projectedNormal = new Vector3(lastface.normal.x, lastface.normal.y, 0).normalize();
// Target direction on XY plane (negative Y axis)
let targetDir = new Vector3(0, -1, 0);
let targetDir = new Vector3(-1, 0, 0);
// Calculate angle between projected normal and target
let angle = Math.atan2(

View file

@ -332,6 +332,10 @@ class Widget {
moto.space.refresh();
}
isSynth() {
return this.track.synth ?? false;
}
isVisible() {
return this.getMaterial().visible;
}

View file

@ -0,0 +1,166 @@
{
"mode": "FDM",
"internal": 0,
"bedHeight": 2.5,
"bedWidth": 252,
"bedDepth": 252,
"bedRound": false,
"bedBelt": false,
"deviceZMax": 0,
"gcodeTime": 1,
"maxHeight": 302,
"originCenter": false,
"gcodeFan": [
"M106 S{fan_speed}"
],
"gcodeFeature": [],
"gcodeTrack": [],
"gcodeLayer": [],
"gcodePre": [
"M140 S{bed_temp}",
"M105",
"M190 S{bed_temp}",
"M104 S{temp}",
"M105",
"M109 S{temp}",
"M82 ; absolute extrusion mode",
"G28 ; Home",
"G92 E0 ; Reset Extruder",
"G1 Z2.0 F3000 ; Move Z Axis up",
"G1 X10.1 Y20 Z0.28 F5000.0 ; Move to start position",
"G1 X10.1 Y200.0 Z0.28 F1500.0 E15 ; Draw the first line",
"G1 X10.4 Y200.0 Z0.28 F5000.0 ; Move to side a little",
"G1 X10.4 Y20 Z0.28 F1500.0 E30 ; Draw the second line",
"G92 E0 ; Reset Extruder",
"G1 Z2.0 F3000 ; Move Z Axis up"
],
"gcodePost": [
"G91 ; Relative positionning",
"G1 E-2 F2700 ; Retract a bit",
"G1 E-2 Z0.2 F2400 ; Retract and raise Z",
"G1 X5 Y5 F3000 ; Wipe out",
"G1 Z10 ; Raise Z more",
"G90 ; Absolute positionning",
"G28 X Y ; Present print",
"M106 S0 ; Turn-off fan",
"M104 S0 ; Turn-off hotend",
"M140 S0 ; Turn-off bed",
"M84 X Y E ; Disable all steppers but Z"
],
"gcodeProc": "",
"gcodeChange": [],
"gcodeFExt": "gcode",
"extruders": [
{
"extFilament": 1.75,
"extNozzle": 0.4,
"extOffsetX": 0,
"extOffsetY": 0
},
{
"extFilament": 1.75,
"extNozzle": 0.4,
"extOffsetX": 0,
"extOffsetY": 0
}
],
"new": false,
"deviceName": "Creality.Ender.7",
"imageURL": "",
"fwRetract": false,
"filamentSource": "direct",
"profiles": [
{
"antiBacklash": 0,
"arcTolerance": 0,
"firstLayerBedTemp": 60,
"firstLayerBeltBump": 0,
"firstLayerBeltLead": 3,
"firstLayerBeltFact": 1,
"firstLayerBrim": 0,
"firstLayerBrimGap": 0,
"firstLayerBrimIn": 0,
"firstLayerBrimTrig": 0,
"firstLayerFanSpeed": 0,
"firstLayerFillRate": 35,
"firstLayerFlatten": 0,
"firstLayerNozzleTemp": 210,
"firstLayerRate": 30,
"firstLayerYOffset": 0,
"firstSliceHeight": 0.25,
"outputAlternating": false,
"outputAvoidGaps": true,
"outputBedTemp": 65,
"outputBeltFirst": false,
"outputBrimCount": 2,
"outputBrimOffset": 2,
"outputCoastDist": 0,
"outputDraftShield": false,
"outputFanLayer": 1,
"outputFanSpeed": 255,
"outputFeedrate": 50,
"outputFillMult": 1.25,
"outputFinishrate": 50,
"outputInvertX": false,
"outputInvertY": false,
"outputLayerRetract": false,
"outputLoops": 0,
"outputMaxFlowrate": 15,
"outputMinLayerTime": 10,
"outputMinSpeed": 5,
"outputPurgeTower": 0,
"outputRaft": false,
"outputRaftSpacing": 0.2,
"outputRetractDist": 10,
"outputRetractDwell": 20,
"outputRetractSpeed": 40,
"outputRetractWipe": 0,
"outputScarfLength": 0,
"outputSeekrate": 250,
"outputShellMult": 1.25,
"outputShortPoly": 0,
"outputSparseMult": 1.25,
"outputTemp": 220,
"processName": "Ender 7 PLA",
"ranges": [],
"sliceAdaptive": false,
"sliceAngle": 45,
"sliceBottomLayers": 3,
"sliceCompInner": 0,
"sliceCompOuter": 0,
"sliceDetectThin": "basic",
"sliceFillAngle": 45,
"sliceFillGrow": 0,
"sliceFillOverlap": 0.35,
"sliceFillRepeat": 1,
"sliceFillSparse": 0.25,
"sliceFillType": "hex",
"sliceFillWidth": 1,
"sliceHeight": 0.25,
"sliceLayerStart": "last",
"sliceLayerStartX": 0,
"sliceLayerStartY": 0,
"sliceLineWidth": 0,
"sliceMinHeight": 0,
"sliceShellOrder": "in-out",
"sliceShells": 3,
"sliceSolidMinArea": 1,
"sliceSupportAngle": 50,
"sliceSupportArea": 0.1,
"sliceSupportDensity": 0.1,
"sliceSupportEnable": true,
"sliceSupportExtra": 0,
"sliceSupportGap": 1,
"sliceSupportGrow": 0,
"sliceSupportNozzle": 0,
"sliceSupportOffset": 1,
"sliceSupportOutline": true,
"sliceSupportSize": 5,
"sliceSupportSpan": 5,
"sliceTopLayers": 3,
"sliceZInterleave": false,
"zHopDistance": 0.2
}
],
"extras": {}
}

View file

@ -3,7 +3,9 @@ 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();
@ -25,6 +27,7 @@ export function selectHelical() {
}
}
});
helicalOn = env.hoveredOp;
helicalOn.classList.add("editing");
api.feature.on_mouse_up = (obj, ev) => {

View file

@ -15,7 +15,9 @@ const { MODES } = api.const;
const { uc: UC } = api;
const { alerts, conf } = api;
const { current: LANG } = api.language;
const { toInt, toFloat } = UC;
const units = true;
let seed = Date.now();
function hasIndexing() {
@ -205,12 +207,12 @@ export function createPopOps() {
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
stepz: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true, bound: UC.bound(0, 100.0) }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
down: UC.newInput(LANG.cc_loff_s, { title: LANG.cc_loff_l, convert: UC.toFloat, units: true }),
inset: UC.newInput(LANG.cc_lxyo_s, { title: LANG.cc_lxyo_l, convert: UC.toFloat, units: true, show: () => !env.popOp.level.rec.stock }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0) }),
stepz: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units, bound: UC.bound(0, 100.0) }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
down: UC.newInput(LANG.cc_loff_s, { title: LANG.cc_loff_l, convert: toFloat, units }),
inset: UC.newInput(LANG.cc_lxyo_s, { title: LANG.cc_lxyo_l, convert: toFloat, units, show: () => !env.popOp.level.rec.stock }),
sep: UC.newBlank({ class: "pop-sep" }),
stock: UC.newBoolean(LANG.cc_lsto_s, undefined, { title: LANG.cc_lsto_l }),
};
@ -235,14 +237,14 @@ export function createPopOps() {
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
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_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
sep: UC.newBlank({ class: "pop-sep" }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
leave: UC.newInput(LANG.cr_lsto_s, { title: LANG.cr_lsto_l, convert: UC.toFloat, units: true }),
leavez: UC.newInput(LANG.cr_lstz_s, { title: LANG.cr_lstz_l, convert: UC.toFloat, bound: UC.bound(0, 10), units: true }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0) }),
leave: UC.newInput(LANG.cr_lsto_s, { title: LANG.cr_lsto_l, convert: toFloat, units }),
leavez: UC.newInput(LANG.cr_lstz_s, { title: LANG.cr_lstz_l, convert: toFloat, bound: UC.bound(0, 10), units }),
sep: UC.newBlank({ class: "pop-sep" }),
all: UC.newBoolean(LANG.cr_clst_s, undefined, { title: LANG.cr_clst_l, show: hasIndexing }),
voids: UC.newBoolean(LANG.cr_clrp_s, undefined, { title: LANG.cr_clrp_l }),
@ -251,8 +253,8 @@ export function createPopOps() {
omitthru: UC.newBoolean(LANG.co_omit_s, undefined, { title: LANG.co_omit_l }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("overrides"),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
};
@ -278,12 +280,12 @@ export function createPopOps() {
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
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_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0), show: () => env.popOp.outline.rec.wide }),
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: UC.toInt, bound: UC.bound(1, 500), show: () => env.popOp.outline.rec.wide }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0), show: () => env.popOp.outline.rec.wide }),
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: toInt, bound: UC.bound(1, 500), show: () => env.popOp.outline.rec.wide }),
sep: UC.newBlank({ class: "pop-sep" }),
top: UC.newBoolean(LANG.co_clrt_s, undefined, { title: LANG.co_clrt_l }),
inside: UC.newBoolean(LANG.co_olin_s, undefined, { title: LANG.co_olin_l, show: (op) => { return !op.inputs.outside.checked } }),
@ -295,8 +297,8 @@ export function createPopOps() {
dogbones: UC.newBoolean(LANG.co_dogb_s, undefined, { title: LANG.co_dogb_l, show: (op) => { return !op.inputs.wide.checked } }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("overrides"),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
};
@ -323,17 +325,17 @@ export function createPopOps() {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"),
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_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 10.0) }),
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: UC.toFloat, bound: UC.bound(0, 100) }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 10.0) }),
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: toFloat, bound: UC.bound(0, 100) }),
sep: UC.newBlank({ class: "pop-sep" }),
angle: UC.newInput(LANG.cf_angl_s, { title: LANG.cf_angl_l, convert: UC.toFloat, bound: UC.bound(45, 90), show: (op) => op.inputs.curves.checked }),
flatness: UC.newInput(LANG.ou_flat_s, { title: LANG.ou_flat_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), units: false, round: 4 }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4 }),
reduction: UC.newInput(LANG.ou_redu_s, { title: LANG.ou_redu_l, convert: UC.toInt, bound: UC.bound(0, 10), units: false }),
// bridging: UC.newInput(LANG.ou_brdg_s, {title:LANG.ou_brdg_l, convert:UC.toFloat, bound:UC.bound(0,1000.0), units:true, round:4, show:(op) => op.inputs.curves.checked}),
angle: UC.newInput(LANG.cf_angl_s, { title: LANG.cf_angl_l, convert: toFloat, bound: UC.bound(45, 90), show: (op) => op.inputs.curves.checked }),
flatness: UC.newInput(LANG.ou_flat_s, { title: LANG.ou_flat_l, convert: toFloat, bound: UC.bound(0, 1.0), units: false, round: 4 }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: toFloat, bound: UC.bound(0, 10.0), units, round: 4 }),
reduction: UC.newInput(LANG.ou_redu_s, { title: LANG.ou_redu_l, convert: toInt, bound: UC.bound(0, 10), units: false }),
// bridging: UC.newInput(LANG.ou_brdg_s, {title:LANG.ou_brdg_l, convert:toFloat, bound:UC.bound(0,1000.0), units:true, round:4, show:(op) => op.inputs.curves.checked}),
sep: UC.newBlank({ class: "pop-sep" }),
curves: UC.newBoolean(LANG.cf_curv_s, undefined, { title: LANG.cf_curv_l }),
inside: UC.newBoolean(LANG.cf_olin_s, undefined, { title: LANG.cf_olin_l }),
@ -357,17 +359,17 @@ export function createPopOps() {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
// axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"),
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_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 100.0) }),
angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: UC.toFloat, bound: UC.bound(0.01, 180.0) }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 100.0) }),
angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: toFloat, bound: UC.bound(0.01, 180.0) }),
sep: UC.newBlank({ class: "pop-sep" }),
offStart: UC.newInput(LANG.ci_laso_s, { title: LANG.ci_laso_l, convert: UC.toFloat}),
offEnd: UC.newInput(LANG.ci_laeo_s, { title: LANG.ci_laeo_l, convert: UC.toFloat}),
offStart: UC.newInput(LANG.ci_laso_s, { title: LANG.ci_laso_l, convert: toFloat}),
offEnd: UC.newInput(LANG.ci_laeo_s, { title: LANG.ci_laeo_l, convert: toFloat}),
sep: UC.newBlank({ class: "pop-sep" }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4 }),
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: UC.toFloat, bound: UC.bound(0, 100) }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: toFloat, bound: UC.bound(0, 10.0), units, round: 4 }),
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: toFloat, bound: UC.bound(0, 100) }),
sep: UC.newBlank({ class: "pop-sep" }),
linear: UC.newBoolean(LANG.ci_line_s, undefined, { title: LANG.ci_line_l }),
// filter: UC.newRow([ UC.newButton(LANG.filter, contourFilter) ], {class:"ext-buttons f-row"})
@ -408,22 +410,22 @@ export function createPopOps() {
mode: UC.newSelect(LANG.cu_type_s, { title: LANG.cu_type_l }, "trace"),
offset: UC.newSelect(LANG.cc_offs_s, { title: LANG.cc_offs_l, show: () => (env.poppedRec.mode === 'follow') }, "traceoff"),
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_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0), show: (op) => env.popOp.trace.rec.mode === "clear" }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
thru: UC.newInput(LANG.cc_thru_s, { title: LANG.cc_thru_l, convert: UC.toFloat, units: true }),
offover: UC.newInput(LANG.cc_offd_s, { title: LANG.cc_offd_l, convert: UC.toFloat, units: true, show: () => env.poppedRec.offset !== "none" || env.poppedRec.mode === "clear" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0), show: (op) => env.popOp.trace.rec.mode === "clear" }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
thru: UC.newInput(LANG.cc_thru_s, { title: LANG.cc_thru_l, convert: toFloat, units }),
offover: UC.newInput(LANG.cc_offd_s, { title: LANG.cc_offd_l, convert: toFloat, units, show: () => env.poppedRec.offset !== "none" || env.poppedRec.mode === "clear" }),
sep: UC.newBlank({ class: "pop-sep", modes: MODES.CAM, xshow: zDogSep }),
merge: UC.newBoolean(LANG.co_merg_s, undefined, { title: LANG.co_merg_l, show: () => !env.popOp.trace.rec.down }),
dogbone: UC.newBoolean(LANG.co_dogb_s, undefined, { title: LANG.co_dogb_l, show: canDogBones }),
revbone: UC.newBoolean(LANG.co_dogr_s, undefined, { title: LANG.co_dogr_l, show: canDogBonesRev }),
exp: UC.newExpand("overrides"),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
sep: UC.newBlank({ class: "pop-sep" }),
@ -451,26 +453,26 @@ export function createPopOps() {
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
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_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.contour }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0) }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units, show: () => !env.poppedRec.contour }),
sep: UC.newBlank({ class: "pop-sep" }),
expand: UC.newInput(LANG.cp_xpnd_s, { title: LANG.cp_xpnd_l, convert: UC.toFloat, units: true, xshow: () => !env.poppedRec.contour }),
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: UC.toInt, show: () => env.poppedRec.contour }),
smooth: UC.newInput(LANG.cp_smoo_s, { title: LANG.cp_smoo_l, convert: UC.toInt }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4, show: () => env.poppedRec.contour }),
follow: UC.newInput(LANG.cp_foll_s, { title: LANG.cp_foll_l, convert: UC.toFloat }),
expand: UC.newInput(LANG.cp_xpnd_s, { title: LANG.cp_xpnd_l, convert: toFloat, units, xshow: () => !env.poppedRec.contour }),
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: toInt, show: () => env.poppedRec.contour }),
smooth: UC.newInput(LANG.cp_smoo_s, { title: LANG.cp_smoo_l, convert: toInt }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: toFloat, bound: UC.bound(0, 10.0), units, round: 4, show: () => env.poppedRec.contour }),
follow: UC.newInput(LANG.cp_foll_s, { title: LANG.cp_foll_l, convert: toFloat }),
sep: UC.newBlank({ class: "pop-sep" }),
contour: UC.newBoolean(LANG.cp_cont_s, undefined, { title: LANG.cp_cont_s }),
engrave: UC.newBoolean(LANG.cp_engr_s, undefined, { title: LANG.cp_engr_l, show: () => env.poppedRec.contour }),
outline: UC.newBoolean(LANG.cp_outl_s, undefined, { title: LANG.cp_outl_l }),
exp: UC.newExpand("overrides"),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
sep: UC.newBlank({ class: "pop-sep" }),
@ -491,16 +493,16 @@ export function createPopOps() {
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
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.toInt, units: true }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
dwell: UC.newInput(LANG.cd_dwll_s, { title: LANG.cd_dwll_l, convert: UC.toFloat }),
lift: UC.newInput(LANG.cd_lift_s, { title: LANG.cd_lift_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.mark }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
dwell: UC.newInput(LANG.cd_dwll_s, { title: LANG.cd_dwll_l, convert: toFloat }),
lift: UC.newInput(LANG.cd_lift_s, { title: LANG.cd_lift_l, convert: toFloat, units, show: () => !env.poppedRec.mark }),
mark: UC.newBoolean(LANG.cd_mark_s, undefined, { title: LANG.cd_mark_l, show: () => !env.poppedRec.fromTop }),
fromTop: UC.newBoolean(LANG.cd_ftop_s, undefined, { title: LANG.cd_ftop_l, show: () => !env.poppedRec.mark }),
sep: UC.newBlank({ class: "pop-sep" }),
thru: UC.newInput(LANG.cd_dtru_s, { title: LANG.cd_dtru_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.mark }),
precision: UC.newInput(LANG.cd_prcn_s, { title: LANG.cd_prcn_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.mark }),
thru: UC.newInput(LANG.cd_dtru_s, { title: LANG.cd_dtru_l, convert: toFloat, units, show: () => !env.poppedRec.mark }),
precision: UC.newInput(LANG.cd_prcn_s, { title: LANG.cd_prcn_l, convert: toFloat, units, show: () => !env.poppedRec.mark }),
sep: UC.newBlank({ class: "pop-sep", }),
actions: UC.newRow([
UC.newButton(LANG.select, () => selectHoles(true), { title: LANG.cd_seli_l }),
@ -523,16 +525,16 @@ export function createPopOps() {
axis: UC.newSelect(LANG.cd_axis, {}, "regaxis"),
points: UC.newSelect(LANG.cd_points, {show:() => env.poppedRec.axis !== '-'}, "regpoints"),
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.toInt, units:true}),
feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toInt, units:true, show:() => env.poppedRec.axis === '-'}),
spindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:toInt, show:hasSpindle}),
rate: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:toInt, units:true}),
feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:toInt, units:true, show:() => env.poppedRec.axis === '-'}),
sep: UC.newBlank({class:"pop-sep"}),
down: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, units:true}),
dwell: UC.newInput(LANG.cd_dwll_s, {title:LANG.cd_dwll_l, convert:UC.toFloat, show:() => env.poppedRec.axis !== '-'}),
lift: UC.newInput(LANG.cd_lift_s, {title:LANG.cd_lift_l, convert:UC.toFloat, units:true, show:() => env.poppedRec.axis !== '-'}),
down: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:toFloat, units:true}),
dwell: UC.newInput(LANG.cd_dwll_s, {title:LANG.cd_dwll_l, convert:toFloat, show:() => env.poppedRec.axis !== '-'}),
lift: UC.newInput(LANG.cd_lift_s, {title:LANG.cd_lift_l, convert:toFloat, units:true, show:() => env.poppedRec.axis !== '-'}),
sep: UC.newBlank({class:"pop-sep"}),
offset: UC.newInput(LANG.cd_rego_s, {title:LANG.cd_rego_l, convert:UC.toFloat, units:true, }),
thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}),
offset: UC.newInput(LANG.cd_rego_s, {title:LANG.cd_rego_l, convert:toFloat, units:true, }),
thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:toFloat, units:true}),
};
createPopOp('helical', {
@ -556,15 +558,15 @@ export function createPopOps() {
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}),
spindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:toInt, show:hasSpindle}),
rate: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:toFloat, units:true}),
feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:toFloat, units:true}),
down: UC.newInput(LANG.ch_sdwn_s, {title:LANG.ch_sdwn_l, convert: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)}),
offOver: UC.newInput(LANG.cc_offd_s, {title:LANG.cc_offd_l, convert: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}),
entryOffset: UC.newInput(LANG.ch_ento_s, {title:LANG.ch_ento_l, convert: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"}),
@ -572,7 +574,7 @@ export function createPopOps() {
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}),
thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:toFloat, units:true}),
actions: UC.newRow([
UC.newButton(LANG.select, selectHelical, {title:LANG.cd_seli_l}),
], {class:"ext-buttons f-col"})
@ -603,7 +605,7 @@ export function createPopOps() {
degrees: 'camIndexAxis',
absolute: 'camIndexAbs'
}).inputs = {
degrees: UC.newInput(LANG.ci_degr_s, { title: LANG.ci_degr_l, convert: UC.toFloat, bound: UC.bound(-360, 360.0) }),
degrees: UC.newInput(LANG.ci_degr_s, { title: LANG.ci_degr_l, convert: toFloat, bound: UC.bound(-360, 360.0) }),
absolute: UC.newBoolean(LANG.ci_abso_s, undefined, { title: LANG.ci_abso_l }),
select: UC.newRow([UC.newButton(LANG.ci_face_s, angleTowardZUp)], { class: "ext-buttons f-row" })
};
@ -630,12 +632,12 @@ export function createPopOps() {
on: UC.newRow([UC.newButton(LANG.on, editOn)], { class: "ext-buttons f-row" }),
off: UC.newRow([UC.newButton(LANG.off, editOff)], { class: "ext-buttons f-row" }),
sep: UC.newBlank({ class: "pop-sep" }),
power: UC.newInput(LANG.cl_powr_s, { title: LANG.cl_powr_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), show: () => !env.poppedRec.adapt }),
maxp: UC.newInput(LANG.cl_maxp_s, { title: LANG.cl_maxp_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), show: () => env.poppedRec.adapt }),
minp: UC.newInput(LANG.cl_minp_s, { title: LANG.cl_minp_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), show: () => env.poppedRec.adapt }),
maxz: UC.newInput(LANG.cl_maxz_s, { title: LANG.cl_maxz_l, convert: UC.toFloat, show: () => env.poppedRec.adapt }),
minz: UC.newInput(LANG.cl_minz_s, { title: LANG.cl_minz_l, convert: UC.toFloat, show: () => env.poppedRec.adapt }),
flatz: UC.newInput(LANG.cl_flaz_s, { title: LANG.cl_flaz_l, convert: UC.toFloat, show: () => env.poppedRec.flat }),
power: UC.newInput(LANG.cl_powr_s, { title: LANG.cl_powr_l, convert: toFloat, bound: UC.bound(0, 1.0), show: () => !env.poppedRec.adapt }),
maxp: UC.newInput(LANG.cl_maxp_s, { title: LANG.cl_maxp_l, convert: toFloat, bound: UC.bound(0, 1.0), show: () => env.poppedRec.adapt }),
minp: UC.newInput(LANG.cl_minp_s, { title: LANG.cl_minp_l, convert: toFloat, bound: UC.bound(0, 1.0), show: () => env.poppedRec.adapt }),
maxz: UC.newInput(LANG.cl_maxz_s, { title: LANG.cl_maxz_l, convert: toFloat, show: () => env.poppedRec.adapt }),
minz: UC.newInput(LANG.cl_minz_s, { title: LANG.cl_minz_l, convert: toFloat, show: () => env.poppedRec.adapt }),
flatz: UC.newInput(LANG.cl_flaz_s, { title: LANG.cl_flaz_l, convert: toFloat, show: () => env.poppedRec.flat }),
sep: UC.newBlank({ class: "pop-sep" }),
adapt: UC.newBoolean(LANG.cl_adap_s, undefined, { title: LANG.cl_adap_l }),
adaptrp: UC.newBoolean(LANG.cl_adrp_s, undefined, { title: LANG.cl_adrp_l, show: () => env.poppedRec.adapt }),
@ -651,4 +653,75 @@ export function createPopOps() {
};
createPopOp('|', {}).inputs = {};
function isClear() {
return env.poppedRec.mode === 'clear';
}
function isTrace() {
return env.poppedRec.mode === 'trace';
}
function isSurface() {
return env.poppedRec.mode === 'surface';
}
function isSurfaceLinear() {
return env.poppedRec.mode === 'surface' && env.poppedRec.sr_type === 'linear';
}
createPopOp('area', {
spindle: 'camAreaSpindle',
tool: 'camAreaTool',
mode: 'camAreaMode',
tr_type: 'camAreaTrace',
sr_type: 'camAreaSurface',
sr_angle: 'camAreaAngle',
over: 'camAreaOver',
down: 'camAreaDown',
rate: 'camAreaSpeed',
plunge: 'camAreaPlunge',
expand: 'camAreaExpand',
smooth: 'camAreaSmooth',
refine: 'camAreaRefine',
outline: 'camAreaOutline',
ov_topz: 0,
ov_botz: 0,
ov_conv: '~camConventional',
tolerance: 'camTolerance',
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
mode: UC.newSelect(LANG.mo_menu, {}, "opmode"),
tr_type: UC.newSelect(LANG.cc_offs_s, { title: LANG.cc_offs_l, show: isTrace }, "traceoff"),
sr_type: UC.newSelect("pattern", { title: "pattern", show: isSurface }, "surftyp"),
sep: UC.newBlank({ class: "pop-sep" }),
menu: UC.newRow([
UC.newButton("edge", traceAdd),
UC.newButton("surface", surfaceAdd),
], { class: "ext-buttons f-row" }),
outline: UC.newBoolean(LANG.cp_outl_s, undefined, { title: LANG.cp_outl_l }),
expand: UC.newInput(LANG.cp_xpnd_s, { title: LANG.cp_xpnd_l, convert: toFloat, units }),
sep: UC.newBlank({ class: "pop-sep" }),
sr_angle: UC.newInput("step angle", { title: "step angle", convert: toFloat, bound: UC.bound(0, 360), show: isSurfaceLinear }),
over: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.001, 100.0), show: () => isClear() || isSurface() }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, bound: UC.bound(0, 100.0), units, show: () => isClear() || isTrace() }),
sep: UC.newBlank({ class: "pop-sep", show: isSurface }),
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: toInt, show: isSurface }),
smooth: UC.newInput(LANG.cp_smoo_s, { title: LANG.cp_smoo_l, convert: toInt, show: isSurface }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: toFloat, bound: UC.bound(0, 10.0), units, round: 4, show: isSurface }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("feeds & speeds"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
exp_end: UC.endExpand(),
exp: UC.newExpand("overrides"),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
};
}

View file

@ -16,9 +16,10 @@ export function surfaceAdd(ev) {
return surfaceDone();
}
clearPops();
let { surfaces, follow } = env.poppedRec;
let { edgeangle } = api.conf.get().controller;
alert = api.show.alert("analyzing surfaces...", 1000);
let surfaces = env.poppedRec.surfaces;
let radians = env.poppedRec.follow * DEG2RAD;
let radians = (follow ?? edgeangle) * DEG2RAD;
CAM.surface_prep(env.currentIndex * RAD2DEG, () => {
api.hide.alert(alert);
alert = api.show.alert("[esc] cancels surface selection");

View file

@ -87,6 +87,12 @@ export function addWidgetTab(widget, rec) {
}
}
export function recreateTabs() {
let widgets = api.widgets.all();
for (let w of widgets) clearTabs(w, true);
restoreTabs(widgets);
}
export function restoreTabs(widgets) {
widgets.forEach(widget => {
const tabs = api.widgets.annotate(widget.id).tab || [];

View file

@ -16,6 +16,11 @@ 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';
import { Widget, newWidget } from '../../core/widget.js';
import { space } from '../../../moto/space.js';
import { recreateTabs } from './cl-tab.js';
const { BufferGeometryUtils } = THREE;
const DEG2RAD = Math.PI / 180;
const RAD2DEG = 180 / Math.PI;
@ -184,6 +189,15 @@ const opAddHelical = () => {
opAdd(rec);
};
const opAddArea = () => {
traceDone();
surfaceDone();
let rec = env.popOp.area.new();
rec.areas = { /* widget.id: [ polygons... ] */ };
rec.surfaces = { /* widget.id: [ faces... ] */ };
opAdd(rec);
};
const opAddContour = (axis) => {
let rec = env.popOp.contour.new();
rec.axis = axis.toUpperCase();
@ -273,7 +287,7 @@ export function opRender() {
oplist.forEach((rec, i) => {
let title = '';
let clock = rec.type === '|';
let label = clock ? `` : rec.type;
let label = clock ? `` : rec.mode ?? rec.type;
let clazz = notime ? ["draggable", "notime"] : ["draggable"];
let notable = rec.note ? rec.note.split(' ').filter(v => v.charAt(0) === '#') : undefined;
if (clock) { clazz.push('clock'); title = ` title="end of ops chain\ndrag/drop like an op\nops after this are disabled"` }
@ -512,6 +526,10 @@ export function opRender() {
export function init() {
if (api.devel.enabled) {
$('op:area').classList.remove('hide');
}
api.event.on('tool.mesh.face-normal', normal => {
// console.log({ env.poppedRec });
env.poppedRec.degrees = (Math.atan2(normal.y, normal.z) * RAD2DEG).round(2);
@ -606,6 +624,20 @@ export function init() {
updateStock();
opRender();
api.uc.setVisible($('layer-animate'), env.isAnimate && env.isCamMode);
if (!env.isCamMode) {
return;
}
// remove tab synth
api.widgets.filter((widget) => {
if (widget.track.synth) {
space.world.remove(widget.mesh);
Widget.Groups.remove(widget);
}
return !widget.track.synth
});
});
api.event.on("settings.saved", (settings) => {
@ -745,6 +777,7 @@ export function init() {
case "trace": return opAddTrace();
case "pocket": return opAddPocket();
case "helical": return opAddHelical();
case "area": return opAddArea();
case "flip":
// only one flip op permitted
for (let op of env.current.process.ops) {
@ -786,8 +819,27 @@ export function init() {
// COMMON TAB/TRACE EVENT HANDLERS
api.event.on("slice.begin", () => {
if (env.isCamMode) {
clearPops();
if (!env.isCamMode) {
return;
}
clearPops();
recreateTabs();
for (let group of Widget.Groups.list()) {
let root = group[0];
if (root.tabs)
for (let tab of Object.values(root.tabs)) {
let geo = tab.box.geometry.clone();
if (geo.index) geo = geo.toNonIndexed();
geo.translate(tab.x, tab.y, tab.z);
let bbg = BufferGeometryUtils.mergeGeometries([ geo ]);
let sw = newWidget(null, group);
let fwp = group[0].track.pos;
sw.loadGeometry(bbg);
sw._move(fwp.x, fwp.y, fwp.z);
api.widgets.add(sw);
sw.track.synth = true;
sw.track.indexed = root.track.indexed;
}
}
});

View file

@ -104,7 +104,6 @@ function cylinderShow(onProgress,onDone){
cyls[face] = faces;
CAM.cylinder_show(widget);
}
onDone({faces:Object.keys(cyls).map(v => parseInt(v)),error});
});
}

View file

@ -0,0 +1,398 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { CamOp } from './op.js';
import { Tool } from './tool.js';
import { newSlice } from '../../core/slice.js';
import { newPoint } from '../../../geo/point.js';
import { newPolygon } from '../../../geo/polygon.js';
import { polygons as POLY } from '../../../geo/polygons.js';
import { util as base_util } from '../../../geo/base.js';
import { CAM } from './driver-be.js';
const DEG2RAD = Math.PI / 180;
const clib = self.ClipperLib;
const ctyp = clib.ClipType;
const ptyp = clib.PolyType;
const cfil = clib.PolyFillType;
class OpArea extends CamOp {
constructor(state, op) {
super(state, op);
}
async slice(progress) {
let { op, state } = this;
let { tool, mode, down, over, follow, expand, outline, refine, smooth } = op;
let { ov_topz, ov_botz, ov_conv } = op;
let { settings, widget, tabs, color } = state;
let { addSlices, setToolDiam, cutTabs, healPolys, shadowAt, workarea } = state;
let areaTool = new Tool(settings, tool);
let toolDiam = areaTool.fluteDiameter();
let toolOver = areaTool.hasTaper() ? over : toolDiam * over;
let zTop = ov_topz ? workarea.bottom_stock + ov_topz : workarea.top_stock;
let zBottom = ov_botz ? workarea.bottom_stock + ov_botz : workarea.bottom_part;
// also updates tab offsets
setToolDiam(toolDiam);
// selected area polygons: surfaces and edges
let { devel, edgeangle } = settings.controller;
let stack = [];
let surfaces = this.surfaces = [];
let areas = this.areas = [ stack ];
let polys = [];
function newArea() {
if (stack.length) {
stack = [];
areas.push(stack);
}
}
function newLayer() {
stack.push(newSlice());
return stack.peek();
}
// gather area selections
for (let arr of (op.areas[widget.id] ?? [])) {
polys.push(newPolygon().fromArray(arr));
}
// connect open poly edge segments into closed loops (when possible)
// surface and edge selections produce open polygons by default
polys = POLY.nest(healPolys(polys));
// gather surface selections
let vert = widget.getGeoVertices({ unroll: true, translate: true }).map(v => v.round(4));
let faces = CAM.surface_find(widget, (op.surfaces[widget.id] ?? []), (follow ?? edgeangle ?? 5) * DEG2RAD);
let fpoly = [];
let fminz = Infinity;
for (let face of faces) {
let i = face * 9;
fpoly.push(newPolygon()
.add(vert[i++], vert[i++], fminz = Math.min(fminz, vert[i++]))
.add(vert[i++], vert[i++], fminz = Math.min(fminz, vert[i++]))
.add(vert[i++], vert[i++], fminz = Math.min(fminz, vert[i++]))
);
}
// remove invalid edges (eg. when vertical walls are the only selection)
fpoly = fpoly.filter(p => p.area() > 0.001);
// add in unioned surface areas
polys.push(...POLY.setZ(POLY.union(fpoly, 0.00001, true), fminz));
// todo: implement `refine` and `smooth`
// expand selections (flattens z variable polys)
if (Math.abs(expand) > 0) {
let nupolys = polys.filter(p => p.open); // set aside open
for (let p of polys.filter(p => !p.open)) {
let expanded = POLY.expand([ p ], expand);
if (expanded) {
POLY.setZ(expanded, p.minZ());
nupolys.push(...expanded.flat());
}
}
polys = nupolys;
}
// process each area separately
let proc = 0;
let pinc = 1 / polys.length;
for (let area of polys) {
let bounds = area.getBounds3D();
if (devel) newLayer().output()
.setLayer("area", { line: 0xff8800 }, false)
.addPolys([ area ]);
newArea();
if (outline) {
// remove inner voids when processing outline only
area.inner = undefined;
}
if (mode === 'clear') {
let zs = down ? base_util.lerp(zTop, zBottom, down) : [ bounds.min.z ];
let zroc = 0;
let zinc = 1 / zs.length;
let lzo;
outer: for (;;)
for (let z of zs) {
let slice = newLayer();
let layers = slice.output();
let outs = [];
let clip = [];
let shadow = shadowAt(z);
POLY.subtract([ area ], shadow, clip, undefined, undefined, 0);
POLY.offset(clip, [ -toolDiam / 2, -toolOver ], {
count: 999, outs, flat: true, z, minArea: 0
});
if (outs.length === 0) {
if (bounds && lzo > bounds.min.z) {
// try a bottom layer matching bottom of selection
zs = [ bounds.min.z ];
bounds = undefined;
continue outer;
}
// terminate z descent when no further output possible
break outer;
}
// cut tabs when present
if (tabs) outs = cutTabs(tabs, outs);
slice.camLines = outs;
zroc += zinc;
lzo = z;
progress(proc + (pinc * zroc), 'clear');
if (devel) layers
.setLayer("shadow", { line: 0x0088ff }, false)
.addPolys(shadow);
layers
.setLayer("clear", { line: 0x88ff00 }, false)
.addPolys(outs);
// of the last output still cuts, we need an escape
if (z === zs.peek()) {
break outer;
}
}
proc += pinc;
progress(proc, 'clear');
} else
if (mode === 'trace') {
let { tr_type } = op;
let zs = down ? base_util.lerp(zTop, bounds.min.z, down) : [ bounds.min.z ];
let zroc = 0;
let zinc = 1 / zs.length;
let lzo;
for (let z of zs) {
let slice = newLayer();
let layers = slice.output();
let outs = [];
if (tr_type === 'none') {
area = area.clone(true);
outs = [ zs.length > 1 ? area.setZ(z) : area ];
} else {
POLY.offset([ area ], tr_type === 'inside' ? [ -toolDiam / 2 ] : [ toolDiam / 2 ], {
count: 1, outs, flat: true, z, minArea: 0
});
}
if (outs.length === 0) {
// terminate z descent when no further output possible
break;
}
// cut tabs when present
if (tabs) outs = cutTabs(tabs, outs);
slice.camLines = outs;
zroc += zinc;
lzo = z;
progress(proc + (pinc * zroc), 'trace');
layers
.setLayer("trace", { line: 0x88ff00 }, false)
.addPolys(outs);
}
proc += pinc;
progress(proc, 'trace');
} else
if (mode === 'surface') {
let { sr_type, sr_angle, tolerance } = op;
let resolution = tolerance || 0.05;
let raster = await self.get_raster_gpu({ mode: "tracing", resolution });
let surface = [];
let paths = [];
// prepare paths
if (sr_type === 'linear') {
// scan the area bounding box with rays at defined angle
let scan = scanBoxAtAngle(bounds, sr_angle * DEG2RAD, toolOver);
let lines = scan.map(line => {
let { a, b } = line;
return [ newPoint(a.x, a.y, 0).toClipper(), newPoint(b.x, b.y, 0).toClipper() ]
});
// use clipper to clip lines to the area polygon
let clip = new clib.Clipper();
let ctre = new clib.PolyTree();
clip.AddPaths(lines, ptyp.ptSubject, false);
clip.AddPaths(POLY.toClipper([ area ]), ptyp.ptClip, true);
if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
for (let node of ctre.m_AllPolys) {
paths.push(POLY.fromClipperNode(node, 0));
}
}
// convert resulting poly lines to raster float32 array groups
paths = paths.map(poly => poly.points.map(p => [ p.x, p.y ]).flat().toFloat32());
} else
if (sr_type === 'offset') {
// todo: progressive inset from perimeter
console.log({ sr_offset: toolOver });
}
// prepare tool mesh points
let toolBounds = new THREE.Box3()
.expandByPoint({ x: -toolDiam/2, y: -toolDiam/2, z: 0 })
.expandByPoint({ x: toolDiam/2, y: toolDiam/2, z: 0 });
let toolPos = areaTool.generateProfile(resolution).profile.slice();
for (let i=0; i<toolPos.length; i+= 3) {
toolBounds.expandByPoint({ x: toolPos[i], y: toolPos[i+1], z: toolPos[i+2] });
}
let toolData = { positions: toolPos, bounds: toolBounds };
// prepare terrain and raster paths over terrain
let vertices = widget.getGeoVertices({ unroll: true, translate: true });
let wbounds = bounds.clone().expandByVector({ x: toolDiam/2, y: toolDiam/2, z: 0 });
wbounds.min.z = zBottom;
wbounds.max.z = zTop;
await raster.loadTool({
sparseData: toolData
});
await raster.loadTerrain({
triangles: vertices,
boundsOverride: wbounds
});
let output = await raster.generateToolpaths({
paths,
step: toolOver / 2,
zFloor: zBottom - 1,
onProgress(pct) { console.log({ pct }); onupdate(pct/100, 100) }
});
raster.terminate();
// todo: port clever bits from topo3 for inside/outside detection and tab clipping
// convert terrain raster output back to open polylines
for (let path of output.paths) {
path = newPolygon().fromArray([1, ...path]);
surface.push(path);
newLayer().output()
.setLayer("linear", { line: 0x00ff00 }, false)
.addPolys([ path ]);
}
// output this surface
surfaces.push(surface);
}
}
addSlices(areas.flat());
}
prepare(ops, progress) {
let { op, state, areas, surfaces } = this;
let { getPrintPoint, newLayer, pocket, polyEmit, setTool, setSpindle, tip2tipEmit } = ops;
let { process } = state.settings;
setTool(op.tool, op.rate);
setSpindle(op.spindle);
let printPoint = getPrintPoint();
// process surface paths
for (let surface of surfaces) {
let array = surface.map(poly => { return {
el: poly,
first: poly.first(),
last: poly.last()
} });
tip2tipEmit(array, printPoint, (next, first, count) => {
printPoint = polyEmit(next.el, 0, 1, printPoint, {});
newLayer();
});
}
// skip areas when processing surfaces
if (surfaces.length) {
return;
}
// process areas as pockets
while (areas?.length) {
let min = {
dist: Infinity,
area: undefined
};
for (let area of areas.filter(p => !p.used)) {
// skip devel / debug only areas
let topPolys = area[0].camLines;
if (!topPolys) continue;
// select poly with largest area
let poly = topPolys.slice().sort((a,b) => b.area() - a.area())[0];
if (!poly) continue;
// compute move distance to top poly for efficient routing
let find = poly.findClosestPointTo(printPoint);
if (find.distance < min.dist) {
min.area = area;
min.dist = find.distance;
}
}
// if we have a next-closest top poly, pocket that
if (min.area) {
min.area.used = true;
pocket({
cutdir: op.ov_conv,
depthFirst: process.camDepthFirst,
easeDown: op.down && process.easeDown ? op.down : 0,
progress: (n,m) => progress(n/m, "area"),
slices: min.area.filter(slice => slice.camLines)
});
} else {
break;
}
}
}
}
// box2: THREE.Box2
// angle: radians (direction of each scan ray)
// step: spacing between parallel rays (world units)
function scanBoxAtAngle(box2, angle, step) {
const cx = (box2.min.x + box2.max.x) * 0.5;
const cy = (box2.min.y + box2.max.y) * 0.5;
const w = box2.max.x - box2.min.x;
const h = box2.max.y - box2.min.y;
// ray direction
const dx = Math.cos(angle);
const dy = Math.sin(angle);
// normal between rays (perpendicular to ray dir)
const nx = -dy;
const ny = dx;
// extent of the box along the normal
const extentN = Math.abs(nx) * w + Math.abs(ny) * h;
// length of each ray across the box (along ray dir)
const extentD = Math.abs(dx) * w + Math.abs(dy) * h;
// how many rays to cover the box; +1 so edges are covered
const count = Math.max(1, Math.ceil(extentN / step) + 1);
const halfSpan = step * (count - 1) * 0.5;
const halfD = extentD * 0.5;
const rays = [];
for (let i = 0; i < count; i++) {
// offset along normal
const o = -halfSpan + i * step;
const ox = cx + nx * o;
const oy = cy + ny * o;
// segment endpoints for this ray inside (or slightly outside) the box
const ax = ox - dx * halfD;
const ay = oy - dy * halfD;
const bx = ox + dx * halfD;
const by = oy + dy * halfD;
rays.push({
a: new THREE.Vector2(ax, ay),
b: new THREE.Vector2(bx, by),
});
}
return rays;
}
export { OpArea };

View file

@ -7,278 +7,277 @@ 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
constructor(state, op) {
super(state, op);
}
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 /2;
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, {
async slice(progress) {
let { op, state } = this;
let { settings, addSlices, widget, updateToolDiams } = state;
let {
cylinders,
forceStartAng,
startAng,
fromTop,
offOver,
offset,
down,
thru,
finish,
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});
reverse,
entry,
entryOffset,
} = op;
let { color } = state;
let { stock } = settings
//if not already very close to final point
if( ofCircle >= 0.001 ){
//circle down to final point
poly.addPoints(
arcToPath(startPoint.setZ(currentZ), finalEnd, numSegs , {
clockwise,
radius,
center,
})
);
}
let tool = new Tool(settings, op.tool),
toolDiam = tool.fluteDiameter(),
sliceOut = (this.sliceOut = []),
startAngle = forceStartAng ? startAng : 0;
//if starting at the bottom, reverse poly
if ( reverse ){
poly.reverse();
}
updateToolDiams(toolDiam);
// console.log({cylinders});
let faces = cylinders[widget.id] ?? [];
if (faces.faces) faces = faces.faces;
let polys = [];
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),
)
}
//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
}
//for each point
//rotate by start angle
// add to hole center
let invert = ((clockwise && !reverse ) || (!clockwise && reverse) ) ? -1 : 1
let entryAngle = reverse ? finalAngle : startAngle
entry = entry.map(p => {
// TODO: there's totally a way to do this with a matrix
// logical XOR
p.y *= invert
p.rotate(entryAngle)
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;
}
let { zmin, zmax, diam, center, interior, faces } = res;
// console.log({ zmin, zmax, diam, center, interior });
let radius = diam / 2;
zmax = fromTop ? stock.z : zmax;
progress(i/faces.length, "Helical intrerpolation");
polys.push(poly);
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});
//if not already very close to final point
if (ofCircle >= 0.001) {
//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
//rotate by start angle
// add to hole center
let invert = ((clockwise && !reverse) || (!clockwise && reverse)) ? -1 : 1
let entryAngle = reverse ? finalAngle : startAngle
entry = entry.map(p => {
// TODO: there's totally a way to do this with a matrix
// logical XOR
p.y *= invert
p.rotate(entryAngle)
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);
}
let slice = newSlice(0);
async prepare(ops, progress) {
let {
setTool,
setSpindle,
polyEmit,
setPrintPoint,
getPrintPoint,
} = ops;
let { tool, spindle, rate, feed } = this.op;
slice.camTrace = { tool: op.tool, rate: op.feed, plunge: op.rate };
slice.camLines = polys;
setTool(tool, feed, rate);
setSpindle(spindle);
slice
.output()
.setLayer("Helical", { face: color, line: color })
.addPolys(slice.camLines);
addSlices(slice);
sliceOut.push(slice);
}
let [polys] = this.sliceOut.map((s) => s.camLines);
let pp = getPrintPoint();
polys = polys.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);
for (;;) {
let closestDist = Infinity,
closestI,
closest = null,
dist,
poly;
let [polys] = this.sliceOut.map((s) => s.camLines);
let pp = getPrintPoint();
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;
}
}
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;
pp = polyEmit(poly, 0, 1, poly.points[0])
}
}
if (!closest) break;
poly = polys[closestI]
polys[closestI] = null;
//emit
pp = polyEmit(poly,0,1,poly.points[0])
setPrintPoint(pp);
}
setPrintPoint(pp);
}
}

View file

@ -17,7 +17,7 @@ export class OpIndex extends CamOp {
throw 'index op requires indexed stock';
}
let { widget, updateSlicer, computeShadows, setAxisIndex } = state;
this.degrees = setAxisIndex(op.degrees, op.absolute);
this.degrees = await setAxisIndex(op.degrees, op.absolute);
// force recompute of topo
widget.topo = undefined;
updateSlicer();

View file

@ -1,39 +1,41 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { OpXRay } from './op-xray.js';
import { OpShadow } from './op-shadow.js';
import { OpLevel } from './op-level.js';
import { OpRough } from './op-rough.js';
import { OpOutline } from './op-outline.js';
import { OpContour } from './op-contour.js';
import { OpPocket } from './op-pocket.js';
import { OpLathe } from './op-lathe.js';
import { OpTrace } from './op-trace.js';
import { OpDrill } from './op-drill.js';
import { OpRegister } from './op-register.js';
import { OpLaserOn } from './op-laser-on.js';
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 { OpArea } from './op-area.js';
import { OpContour } from './op-contour.js';
import { OpDrill } from './op-drill.js';
import { OpGCode } from './op-gcode.js';
import { OpHelical } from './op-helical.js';
import { OpIndex } from './op-index.js';
import { OpLaserOff } from './op-laser-off.js';
import { OpLaserOn } from './op-laser-on.js';
import { OpLathe } from './op-lathe.js';
import { OpLevel } from './op-level.js';
import { OpOutline } from './op-outline.js';
import { OpPocket } from './op-pocket.js';
import { OpRegister } from './op-register.js';
import { OpRough } from './op-rough.js';
import { OpShadow } from './op-shadow.js';
import { OpTrace } from './op-trace.js';
import { OpXRay } from './op-xray.js';
export const ops = {
"xray": OpXRay,
"shadow": OpShadow,
"level": OpLevel,
"rough": OpRough,
"outline": OpOutline,
"area": OpArea,
"contour": OpContour,
"pocket": OpPocket,
"lathe": OpLathe,
"trace": OpTrace,
"drill": OpDrill,
"register": OpRegister,
"helical": OpHelical,
"laser on": OpLaserOn,
"laser off": OpLaserOff,
"flip": CamOp,
"gcode": OpGCode,
"helical": OpHelical,
"index": OpIndex,
"flip": CamOp
"laser off": OpLaserOff,
"laser on": OpLaserOn,
"lathe": OpLathe,
"level": OpLevel,
"outline": OpOutline,
"pocket": OpPocket,
"register": OpRegister,
"rough": OpRough,
"shadow": OpShadow,
"trace": OpTrace,
"xray": OpXRay
};

View file

@ -26,7 +26,7 @@ export async function cam_prepare(widall, settings, update) {
print.output = [];
let point;
widgets.forEach((widget, index) => {
widgets.filter(w => !w.isSynth()).forEach((widget, index) => {
point = prepEach(widget, settings, print, point, (progress, msg) => {
update((index * weight + progress * weight) * 0.75, msg || "prepare");
});
@ -337,7 +337,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// 1. move to safe z of current point preserving angle
// 2. move to safe z of new point preserving old angle
// 3. move to safe z of new point with new angle
if (lop && lop !== currentOp && lastPoint) {
if (lop !== currentOp && lastPoint) {
// compensate for applyWidgetMovement() applied to lastPoint
let lpo = lastPoint.clone().move({ x: -wmx, y: -wmy, z: -zadd });
camOut(lpo.clone().setZ(zsafe).setA(lastPoint.a), 0);

View file

@ -20,6 +20,10 @@ import { CAM } from './driver-be.js';
* @param {Function} output
*/
export async function cam_slice(settings, widget, onupdate, ondone) {
if (widget.track.synth) return ondone();
let tabW = widget.group.filter(w => w != widget);
let proc = settings.process,
sliceAll = widget.slices = [],
camOps = widget.camops = [],
@ -87,7 +91,7 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
bottom_z, bottom_cut
}, 3);
// console.log({ track, bounds, stock, workarea });
// console.log({ bounds, stock, track, workarea });
return structuredClone(workarea);
};
@ -95,26 +99,6 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
// initial setup
var_compute();
if (tabs) {
// make tab polygons
tabs.forEach(tab => {
let zero = newPoint(0, 0, 0),
point = newPoint(tab.pos.x, tab.pos.y, tab.pos.z),
poly = newPolygon().centerRectangle(zero, tab.dim.x, tab.dim.y),
[rx, ry, rz, rw] = tab.rot,
m4 = new THREE.Matrix4().makeRotationFromQuaternion(
new THREE.Quaternion(rx, ry, rz, rw)
);
poly.points = poly.points
.map(p => new THREE.Vector3(p.x, p.y, p.z).applyMatrix4(m4))
.map(v => newPoint(v.x, v.y, v.z));
poly.move(point);
tab.poly = poly;
// tslice.output().setLayer("tabs", 0xff0000).addPoly(poly);
// sliceAll.push(tslice);
});
}
function error(msg) {
ondone(msg);
}
@ -155,7 +139,6 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
alert(`invalid z bottom ${(zMin / units).round(3)} >= bounds z max ${(zMax / units).round(3)}`);
}
let mark = Date.now();
let opList = [];
let opSum = 0;
let opTot = 0;
@ -174,6 +157,7 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
isIndexed,
ops: opList,
setAxisIndex,
setToolDiam,
settings,
shadowAt,
slicer,
@ -190,6 +174,30 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
};
let tracker = setSliceTracker({ rotation: 0 });
async function updateTab(tab) {
tab.setAxisIndex(isIndexed ? -axisIndex : 0)
let ts = new cam_slicer(tab);
let si = Object.keys(ts.zList).map(k => parseFloat(k));
si = [ ...si.map(v => v-0.01), ...si.map(v => v+0.01) ];
let zt = Math.max(...si);
let zb = Math.min(...si);
let shadow;
await ts.slice(si, { each: data => {
if (shadow) {
shadow = POLY.union([...shadow, ...data.polys]);
} else {
shadow = data.polys;
}
}});
return {
top: zt,
poly: shadow[0],
pos: { z: (zt+zb)/2 },
dim: { z: (zt-zb) },
NEW: true
}
}
function updateSlicer() {
slicer = state.slicer = new cam_slicer(widget);
}
@ -201,6 +209,15 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
});
}
function setToolDiam(toolDiam) {
updateToolDiams(toolDiam);
if (tabs) {
tabs.forEach(tab => {
tab.off = POLY.expand([ tab.poly ], toolDiam / 2).flat();
});
}
}
function updateToolDiams(toolDiam) {
minToolDiam = Math.min(minToolDiam, toolDiam);
maxToolDiam = Math.max(maxToolDiam, toolDiam);
@ -212,30 +229,23 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
return cached;
}
// find closest shadow above and use to speed up delta shadow gen
let minZabove;
let zover = Object.keys(shadows).map(v => parseFloat(v)).filter(v => v > z);
for (let zkey of zover) {
if (minZabove && zkey < minZabove) {
minZabove = zkey;
} else {
minZabove = zkey;
}
}
let minZabove = Math.min(Infinity, ...zover);
let shadow = computeShadowAt(widget, z, minZabove);
if (minZabove) {
// const merge = shadow.length;
// const plus = shadows[minZabove].length;
// const mark = Date.now();
if (minZabove < Infinity) {
shadow = POLY.union([...shadow, ...shadows[minZabove]], 0.001, true);
// console.log({merge, plus, equals: shadow.length, time: Date.now() - mark});
}
return shadows[z] = POLY.setZ(shadow, z);
}
function setAxisIndex(degrees = 0, absolute = true) {
async function setAxisIndex(degrees = 0, absolute = true) {
axisIndex = absolute ? degrees : (axisIndex || 0) + degrees;
axisRotation = (Math.PI / 180) * axisIndex;
widget.setAxisIndex(isIndexed ? -axisIndex : 0);
state.tabs = tabs = [];
for (let tab of tabW) {
tabs.push(await updateTab(tab));
}
return isIndexed ? -axisIndex : 0;
}
@ -300,7 +310,7 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
}
// call slice() function on all ops in order
setAxisIndex();
await setAxisIndex();
updateSlicer();
for (let op of opList) {
let weight = op.weight();
@ -326,12 +336,6 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
zTop,
workarea: workover
});
// console.log({
// op,
// workover,
// bounds: structuredClone(bounds),
// stock: structuredClone(stock)
// });
await op.slice((progress, message) => {
onupdate((opSum + (progress * weight)) / opTot, message || op.type());
});
@ -595,7 +599,7 @@ export function cylinder_poly_find(widget, face) {
let zmid = (zmin + zmax) / 2;
let cylVerts = new Float32Array(faces.length * 9);
let opts = {
dedup: false,
dedup: true,
edges: false,
over: true
};
@ -648,7 +652,7 @@ export function cylinder_poly_find(widget, face) {
*/
export async function holes(settings, widget, individual, rec, onProgress) {
let { tool, mark, precision } = rec //TODO: display some visual difference if mark is selected
let { tool, precision } = rec //TODO: display some visual difference if mark is selected
let toolDiam = new Tool(settings, tool).fluteDiameter()
let diam = individual ? 1 : toolDiam; // sets default diameter when select individual used

View file

@ -222,7 +222,7 @@ export class Topo {
if (poly.length > 1) {
lines.push(poly);
}
// raize output points when inside tab boundaries
// raise output points when inside tab boundaries
if (!inside && tabsOn && clipTab.length)
for (let poly of lines) {
for (let p of poly.points) {

View file

@ -2024,6 +2024,18 @@ details[open] summary::after {
width: 100%;
/* background-color: #f8f8f8; */
}
#device-action {
align-items: center;
}
#device-action a {
right: 0;
position: absolute;
border-bottom: 1px dotted;
}
#device-action a:hover {
color: #888;
border-bottom: 1px transparent;
}
#device-action button, #tool-action button {
flex-direction: column;
justify-content: center;

View file

@ -461,6 +461,7 @@
<div title="2.5D tracing along X or Y axis&#13;used for complex part features">contour</div>
<div title="clear areas above&#13;selected surfaces">pocket</div>
<div title="follow a helix&#13;around circular paths">helical</div>
<div title="perform configurable operations on selected areas" id="op:area" class="hide">area</div>
</div>
</div>
</div>
@ -545,6 +546,7 @@
<div class="grow"></div>
<button id="device-save" lk="save">save</button>
<div class="grow"></div>
<a href="https://docs.grid.space/kiri-moto/gcode-macros" target="docs">gcode macro docs</a>
</div>
</div>

View file

@ -1,114 +0,0 @@
/** atom3dp css */
button {
color: #000;
border: 1px solid rgba(20,20,20,0.5);
background-color: rgba(210,210,210,0.8);
padding: 2px 7px 3px 7px;
margin-bottom: 2px !important;
}
button:hover {
border-color: #000;
background-color: #fff;
}
label {
border-bottom: 1px solid rgba(220,220,225,1);
}
.dialog {
background-color: rgba(220,220,220,0.75);
border-color: #bbb;
color: #000;
}
#device-cols > div {
background-color: rgba(250,250,250,0.75);
}
#device-list select {
color: #000;
}
#device-list option[local="1"] {
color: #00f;
}
#device-labels span {
background-color: rgba(170,221,255,0.75);
border: 1px dashed #aaa;
color: black;
}
#tool-cols > div {
background-color: rgba(250,250,250,0.75);
}
#tool-list select {
color: #000;
}
#tool-list option[local="1"] {
color: #00f;
}
#tool-labels span {
background-color: rgba(170,221,255,0.75);
border: 1px dashed #aaa;
color: black;
}
#catalog span {
margin-left: 0;
margin-right: 0;
background-color: rgba(170,221,255,0.75);
border: 1px dashed #aaa;
color: black;
}
#catalog button {
background-color: rgba(250,250,250,0.8);
}
#catalog button:hover {
background-color: rgba(200,200,200,0.8);
}
#settings span {
margin-left: 0;
margin-right: 0;
background-color: rgba(170,221,255,0.75);
border: 1px dashed #aaa;
color: black;
}
#settings button {
background-color: rgba(250,250,250,0.8);
}
#settings button:hover {
background-color: rgba(200,200,200,0.8);
}
.grouphead {
color: black;
border-top-left-radius: 3px;
border-top-right-radius: 0;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 0;
border: 1px dashed #aaa;
margin-top: 5px;
margin-bottom: 3px;
padding: 3px 2px 2px 3px;
background-color: rgba(170,221,255,0.75);
font-weight: bold;
font-size: 12px;
}
#control-left {
border-top: 0;
border-bottom: 0;
background-color: rgba(220,220,225,0.25);
}
#control-right {
color: #000;
border-top: 0;
border-bottom: 0;
background-color: rgba(220,220,225,0.25);
}
#appid span {
x-background-color: rgba(220,220,225,0.5) !important;
}
#selection span {
background-color: rgba(220,220,225,0.5) !important;
}
#layer-view span {
background-color: rgba(220,220,225,0.5) !important;
}

View file

@ -1,32 +0,0 @@
/** atom3dp css */
button {
color: #000;
border: 1px solid rgba(20,20,20,0.5);
}
label {
border-bottom: 1px solid rgba(200,200,0,0.75);
}
.grouphead {
color: white;
background-color: rgba(0,0,0,0.85);
}
#control-left {
background-color: rgba(240,240,20,0.5);
}
#control-right {
color: #000;
background-color: rgba(240,240,20,0.5);
}
#appid span {
background-color: rgba(240,240,20,0.5) !important;
}
#selection span {
background-color: rgba(240,240,20,0.5) !important;
}
#layer-view span {
background-color: rgba(240,240,20,0.5) !important;
}
#modeCAM {
display: none !important;
}