Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/kiri/app/conf/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ function device_from_code(code,mode) {
originCenter: valueOf(set.origin_center, false),
extrudeAbs: valueOf(set.extrude_abs, false),
spindleMax: valueOf(set.spindle_max, 0),
useIndexed: valueOf(set.use_indexed, false),
indexedAxis: valueOf(set.indexed_axis, "A"),
indexedAxisAlign: valueOf(set.indexed_axis_align, "X"),
gcodeFan: valueOf(cmd.fan_power || code.fan_power, []),
gcodeFeature: valueOf(cmd.feature || code.feature, []),
gcodeTrack: valueOf(cmd.progress || code.progress, []),
Expand Down Expand Up @@ -412,6 +415,8 @@ export const conf = {
maxHeight: 300,
useLaser: false,
useIndexed: false,
indexedAxis: "A",
indexedAxisAlign: "X",
originCenter: false,
spindleMax: 0,
gcodePre: [],
Expand Down Expand Up @@ -593,6 +598,7 @@ export const conf = {
camStockIndexed: false,
camStockIndexGrid: true,
camStockOffset: true,
camStockRound: false,
camStockX: 5,
camStockY: 5,
camStockZ: 5,
Expand Down
8 changes: 8 additions & 0 deletions src/kiri/app/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ const LISTS = {
{ name: "lines" },
// { name: "surface" }
],
indexedaxis: [
{ name: "A" },
{ name: "B" }
],
indexedalign: [
{ name: "X" },
{ name: "Y" }
],
trace: [
{ name: "follow" },
{ name: "clear" }
Expand Down
2 changes: 2 additions & 0 deletions src/kiri/app/devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ function setDeviceCode(code, devicename) {
ui.maxHeight,
ui.useLaser,
ui.useIndexed,
ui.indexedAxis,
ui.indexedAxisAlign,
ui.resolutionX,
ui.resolutionY,
ui.deviceOrigin,
Expand Down
7 changes: 7 additions & 0 deletions src/kiri/app/init/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export function onBooleanClick(el) {
api.view.set_arrange();
}
api.conf.update();
// toggling the 4th axis can change whether the editor shows X/Y swapped
// (Y-aligned rotary), so refresh the platform axes/labels
if (el === ui.useIndexed) {
api.platform.update_origin();
}
DOC.activeElement.blur();
api.event.emit("boolean.click");
api.devices.update_laser_state();
Expand Down Expand Up @@ -267,6 +272,8 @@ export function init_input() {
laserMaxPower: newInput(LANG.ou_maxp_s, {title:LANG.ou_maxp_l, modes:LASER, size:7, text:true}),
useLaser: newBoolean(LANG.dv_lazr_s, onBooleanClick, {title:LANG.dv_lazr_l, modes:CAM}),
useIndexed: newBoolean(LANG.dv_4tha_s, onBooleanClick, {title:LANG.dv_4tha_l, modes:CAM}),
indexedAxis: newSelect(LANG.dv_4tax_s, {title:LANG.dv_4tax_l, modes:CAM, show:() => ui.useIndexed.checked}, "indexedaxis"),
indexedAxisAlign: newSelect(LANG.dv_4tal_s, {title:LANG.dv_4tal_l, modes:CAM, show:() => ui.useIndexed.checked, action: settingsOps.update_device}, "indexedalign"),
gcodeFExt: newInput(LANG.dv_fext_s, {title:LANG.dv_fext_l, modes:CAM_LZR, size:7, text:true}),
gcodeEd: newGroup(LANG.dv_gr_gco, $('dg'), {group:"dgcp", inline, modes:GCODE}),
gcodeMacros: newRow([
Expand Down
40 changes: 38 additions & 2 deletions src/kiri/app/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ function get_mode() {
*
* @param {boolean} [update_bounds=true] - Whether to recalculate bounds first
*/
// default editor axis line colors (see moto/space.js grid.colorX/colorY)
const AXIS_COLOR_X = 0xff6666; // red
const AXIS_COLOR_Y = 0x6666ff; // blue
// track last applied axis-swap state to avoid rebuilding the grid every call
let lastAxisSwap;

// CAM with a Y-aligned rotary swaps the X/Y axis LETTERS in gcode output
// (indexedAxisAlign, see export.js). To keep the editor consistent, present the
// X/Y axes swapped on screen too (display only): swap the axis line colors,
// ruler labels, and the device width/depth field labels. Nothing in the
// slicing pipeline changes — the part still lies along internal X.
function applyAxisSwap(swap) {
if (swap === lastAxisSwap) {
return;
}
lastAxisSwap = swap;
space.platform.setGridColor({
colorX: swap ? AXIS_COLOR_Y : AXIS_COLOR_X,
colorY: swap ? AXIS_COLOR_X : AXIS_COLOR_Y
});
const setLabel = (el, text) => {
const label = el && el.parentElement && el.parentElement.querySelector('label');
if (label) label.textContent = text;
};
setLabel(api.ui.bedWidth, swap ? 'Y (width)' : 'X (width)');
setLabel(api.ui.bedDepth, swap ? 'X (depth)' : 'Y (depth)');
}

function update_origin(update_bounds = true) {
if (update_bounds) {
platform.update_bounds();
Expand Down Expand Up @@ -129,7 +157,11 @@ function update_origin(update_bounds = true) {
origin.x -= process.ctOriginOffX;
origin.y += process.ctOriginOffY;
}
space.platform.setRulers(ruler, ruler, 1 / api.view.unit_scale(), 'X', isBelt ? 'Z' : 'Y');
const axisSwap = MODE === CAM && device.useIndexed && device.indexedAxisAlign === 'Y';
applyAxisSwap(axisSwap);
space.platform.setRulers(ruler, ruler, 1 / api.view.unit_scale(),
axisSwap ? 'Y' : 'X',
isBelt ? 'Z' : (axisSwap ? 'X' : 'Y'));

let { x, y, z } = origin;
let oz = process.camStockIndexed ? z / 2 : z;
Expand Down Expand Up @@ -244,7 +276,7 @@ function update_top_z() {
function platformUpdateStock() {
const settings = current();
const { bounds, process, mode } = settings;
const { camStockX, camStockY, camStockZ, camStockOffset, camStockIndexed } = process;
const { camStockX, camStockY, camStockZ, camStockOffset, camStockIndexed, camStockRound } = process;
if (mode === 'CAM') {
let stock = settings.stock = {
x: camStockX,
Expand All @@ -257,6 +289,10 @@ function platformUpdateStock() {
stock.y += bounds.max.y - bounds.min.y;
stock.z += bounds.max.z - bounds.min.z;
}
// round bar: diameter = height (Z), so force a circular Y=Z cross-section
if (camStockRound && camStockIndexed) {
stock.y = stock.z;
}
stock.center = {
x: (bounds.max.x + bounds.min.x) / 2,
y: (bounds.max.y + bounds.min.y) / 2,
Expand Down
29 changes: 29 additions & 0 deletions src/kiri/dev/cam/Snapmaker.Artisan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"file-ext": "gcode",
"token-space": " ",
"strip-comments": true,
"pre":[
"G21 ; set units to MM (required)",
"G90 ; absolute position mode (required)"
],
"post":[
"M5 ; complete moves, stop spindle",
"M30 ; program end"
],
"tool-change":[],
"dwell":[
"G4 P{time} ; dwell for {time}ms"
],
"spindle":[
"M3 P{spindle} ; uniquely uses percentage not RPM"
],
"settings": {
"origin_center": false,
"bed_width": 400,
"bed_depth": 400,
"build_height": 100,
"spindle_max": 100,
"indexed_axis": "B",
"indexed_axis_align": "Y"
}
}
58 changes: 38 additions & 20 deletions src/kiri/mode/cam/app/cl-stock.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,17 @@ export function updateStock() {
if (x && y && z) {
UI.func.animate.classList.remove('disabled');
{
let geo = new THREE.BoxGeometry(1, 1, 1);
// round bar on a rotary: show a cylinder (axis along X) instead of a
// box. rotate the GEOMETRY (not the mesh) so the shared scale/position
// below still maps x->length, y/z->diameter with y==z normalized.
let round = process.camStockRound && env.isIndexed;
let geo;
if (round) {
geo = new THREE.CylinderGeometry(0.5, 0.5, 1, 64);
geo.rotateZ(Math.PI / 2);
} else {
geo = new THREE.BoxGeometry(1, 1, 1);
}
let mat = new THREE.MeshBasicMaterial({
color: 0x777777,
opacity: 0.05,
Expand All @@ -112,25 +122,33 @@ export function updateStock() {
});
env.camStock = new THREE.Mesh(geo, mat);
env.camStock.renderOrder = 2;
let lo = 0.5;
let lidat = [
lo, lo, lo, lo, lo, -lo,
lo, lo, lo, lo, -lo, lo,
lo, lo, lo, -lo, lo, lo,
-lo, -lo, -lo, -lo, -lo, lo,
-lo, -lo, -lo, -lo, lo, -lo,
-lo, -lo, -lo, lo, -lo, -lo,
lo, lo, -lo, -lo, lo, -lo,
lo, lo, -lo, lo, -lo, -lo,
lo, -lo, -lo, lo, -lo, lo,
lo, -lo, lo, -lo, -lo, lo,
-lo, -lo, lo, -lo, lo, lo,
-lo, lo, lo, -lo, lo, -lo
];
let ligeo = new THREE.BufferGeometry();
ligeo.setAttribute('position', new THREE.BufferAttribute(lidat.toFloat32(), 3));
let limat = new THREE.LineBasicMaterial({ color: 0xaaaaaa });
let lines = new THREE.LineSegments(ligeo, limat);
let lines;
if (round) {
// 30deg threshold keeps just the two end circles, not every facet seam
let ligeo = new THREE.EdgesGeometry(geo, 30);
let limat = new THREE.LineBasicMaterial({ color: 0xaaaaaa });
lines = new THREE.LineSegments(ligeo, limat);
} else {
let lo = 0.5;
let lidat = [
lo, lo, lo, lo, lo, -lo,
lo, lo, lo, lo, -lo, lo,
lo, lo, lo, -lo, lo, lo,
-lo, -lo, -lo, -lo, -lo, lo,
-lo, -lo, -lo, -lo, lo, -lo,
-lo, -lo, -lo, lo, -lo, -lo,
lo, lo, -lo, -lo, lo, -lo,
lo, lo, -lo, lo, -lo, -lo,
lo, -lo, -lo, lo, -lo, lo,
lo, -lo, lo, -lo, -lo, lo,
-lo, -lo, lo, -lo, lo, lo,
-lo, lo, lo, -lo, lo, -lo
];
let ligeo = new THREE.BufferGeometry();
ligeo.setAttribute('position', new THREE.BufferAttribute(lidat.toFloat32(), 3));
let limat = new THREE.LineBasicMaterial({ color: 0xaaaaaa });
lines = new THREE.LineSegments(ligeo, limat);
}
env.camStock.lines = lines;
env.camStock.add(lines);
SPACE.world.add(env.camStock);
Expand Down
1 change: 1 addition & 0 deletions src/kiri/mode/cam/app/init-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export function menu() {
camStockOffset: newBoolean(LANG.cs_offs_s, onBooleanClick, {title:LANG.cs_offs_l}),
camStockIndexed: newBoolean(LANG.cs_indx_s, onBooleanClick, {title:LANG.cs_indx_l}),
camStockIndexGrid: newBoolean(LANG.cs_ishg_s, onBooleanClick, {title:LANG.cs_ishg_l, show:() => ui.camStockIndexed.checked}),
camStockRound: newBoolean(LANG.cs_rond_s, onBooleanClick, {title:LANG.cs_rond_l, show:() => ui.camStockIndexed.checked}),
// separator: newBlank({ class:"set-sep", driven }),
// camStockManual: newRow([
// (ui.stockPlace = newButton('position', onButtonClick, { })),
Expand Down
12 changes: 9 additions & 3 deletions src/kiri/mode/cam/work/anim-3d.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ export function init(worker) {

stockSlices = [];
const { x, y, z } = stock;
const round = process.camStockRound && isIndexed;
const sliceCount = parseInt(settings.controller.animesh || 2000) / 100;
const sliceWidth = stock.x / sliceCount;

if (controller.manifold)
for (let i = 0; i < sliceCount; i++) {
let xmin = -(x / 2) + (i * sliceWidth) + sliceWidth / 2;
let slice = new Stock(sliceWidth, y, z).translate(xmin, 0, 0);
let slice = new Stock(sliceWidth, y, z, round).translate(xmin, 0, 0);
stockSlices.push(slice);
slice.updateMesh([]);
slice.send(send);
Expand Down Expand Up @@ -314,15 +315,20 @@ function toolUpdate(toolid, send) {
}

class Stock {
constructor(x, y, z) {
constructor(x, y, z, round) {
this.id = nextMeshID++;
this.vbuf = undefined;
this.ibuf = undefined;
this.ilen = 0;
this.sends = 0;
this.newbuf = true;
this.subtracts = 0;
this.mesh = CSG.Instance().Manifold.cube([x, y, z], true);
const M = CSG.Instance().Manifold;
// round bar: circular YZ cross-section (diameter = z) extruded along X.
// manifold cylinder axis is Z, so rotate 90deg about Y to lie along X.
this.mesh = round
? M.cylinder(x, z / 2, z / 2, 64, true).rotate([0, 90, 0])
: M.cube([x, y, z], true);
}

send(send) {
Expand Down
14 changes: 13 additions & 1 deletion src/kiri/mode/cam/work/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function cam_export(print, online) {
cmdToolChange = device.gcodeChange || ["M6 T{tool}"],
cmdSpindle = device.gcodeSpindle || ["M3 S{speed}"],
cmdDwell = device.gcodeDwell || ["G4 P{time}"],
axis = { X: 'X', Y: 'Y', Z: 'Z', A: 'A', F: 'F', R: 'R', I: 'I', J: 'J' },
axis = { X: 'X', Y: 'Y', Z: 'Z', A: device.indexedAxis === 'B' ? 'B' : 'A', F: 'F', R: 'R', I: 'I', J: 'J' },
dev = settings.device,
spro = settings.process,
maxZd = spro.camFastFeedZ,
Expand Down Expand Up @@ -79,6 +79,18 @@ export function cam_export(print, online) {
time: 0
};

// when the rotary is mounted along the machine Y axis (instead of the
// default X), swap the X/Y (and arc I/J) output letters for indexed jobs.
// kiri computes indexed toolpaths assuming an X-aligned rotary; relabeling
// the linear axes at emit time maps them onto a Y-aligned rotary. only
// applied in indexed mode so plain 3-axis output is never swapped.
if (device.useIndexed && device.indexedAxisAlign === 'Y') {
axis.X = 'Y';
axis.Y = 'X';
axis.I = 'J';
axis.J = 'I';
}

// console.log({ offset, origin, stock });

function section(section) {
Expand Down
30 changes: 28 additions & 2 deletions src/kiri/mode/cam/work/op-area.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ class OpArea extends CamOp {
let zs = flats ?
flats.filter(z => z <= zTop && z >= zBottom).map(v => v + zMov) :
down ? base_util.lerp(zTop, zBottom, down) : [ bounds.min.z ];
// round bar: derive the cylinder center/radius from the stock
// extent the loop iterates (same Z-frame as `z`), then drop
// z-levels where the circular cross-section is narrower than the
// tool so the clear descent isn't aborted by an empty top sliver
let roundR, roundCz;
if (op.round) {
// cylinder radius and center (rotary axis) come straight from
// the stock; cz is in the same Z-frame as the clear loop's `z`
roundR = op.round.r;
roundCz = op.round.cz;
zs = zs.filter(z => {
let d = z - roundCz;
return 2 * Math.sqrt(Math.max(0, roundR * roundR - d * d)) >= toolDiam;
});
}

let zroc = 0;
let zinc = 1 / zs.length;
let lzo;
Expand All @@ -191,11 +207,21 @@ class OpArea extends CamOp {
let outs = [];
let clip = [];
let firstOff = -(toolDiam / 2 + (op.leave_xy ?? 0));
// round bar: clip the working area to the cylinder chord at
// this height so only actual bar material is cleared
let region = [ area ];
if (op.round) {
let { cx, cy, len } = op.round;
let d = z - roundCz;
let half = Math.sqrt(Math.max(0, roundR * roundR - d * d));
let chord = newPolygon().centerRectangle({ x: cx, y: cy, z }, len, 2 * half);
region = POLY.trimTo([ area ], [ chord ]) || [];
}
// remove shadow from area
if (op.ignore) {
clip = [ area ];
clip = region;
} else {
POLY.subtract([ area ], shadow, clip, undefined, undefined, 0);
POLY.subtract(region, shadow, clip, undefined, undefined, 0);
}
//generate offsets to use
let offsets = [ firstOff ];
Expand Down
15 changes: 15 additions & 0 deletions src/kiri/mode/cam/work/op-rough.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ class OpRough extends CamOp {
shadowBase = [ newPolygon().centerRectangle(stock.center, stock.x, stock.y) ];
}

// round bar on a rotary: flag the cylindrical cross-section so the clear
// loop clips each Z level to the chord 2*sqrt(r^2-(z-cz)^2) instead of
// air-cutting the full bounding-box width. axis = X (length), circle in
// YZ, invariant across index angles. radius = half the stock height
// (diameter), centered on the stock center (the rotary axis).
let round = (state.settings.process.camStockRound && state.isIndexed) ? {
r: stock.z / 2,
cx: stock.center.x,
cy: stock.center.y,
cz: stock.center.z,
len: stock.x
} : undefined;

let areas = POLY.flatten(POLY.expand(shadowBase, tool.fluteDiameter() / 2 - 0.001));
let ops_list = this.ops_list = [ ];

Expand All @@ -49,6 +62,7 @@ class OpRough extends CamOp {
ov_botz: op.ov_botz,
ov_topz: op.ov_topz,
rotated: true,
round,
areas: { [widget.id]: areas.map(p => p.toArray()) },
surfaces: {}
}));
Expand All @@ -74,6 +88,7 @@ class OpRough extends CamOp {
ov_botz: op.ov_botz,
ov_topz: op.ov_topz,
rotated: true,
round,
areas: { [widget.id]: areas.map(p => p.toArray()) },
surfaces: {},
flats: Object.keys(slicer.zFlat).map(v => parseFloat(v)).sort((a,b) => b-a),
Expand Down
Loading