From 4b67307b129f408efc9df9f686a09d0737a80d89 Mon Sep 17 00:00:00 2001 From: xuzhen1994 Date: Tue, 16 Jul 2024 09:34:44 +0800 Subject: [PATCH 1/7] feature: different travel color to distinguish air-cutting and actual-cutting in CNC different travel color to distinguish air-cutting and actual-cutting in CNC. --- src/webgl-preview.ts | 49 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/webgl-preview.ts b/src/webgl-preview.ts index 4c28ecad..ae8cc734 100644 --- a/src/webgl-preview.ts +++ b/src/webgl-preview.ts @@ -77,7 +77,7 @@ export type GCodePreviewOptions = { renderTravel?: boolean; startLayer?: number; topLayerColor?: ColorRepresentation; - travelColor?: ColorRepresentation; + travelColor?: ColorRepresentation | ColorRepresentation[]; toolColors?: Record; disableGradient?: boolean; extrusionWidth?: number; @@ -147,7 +147,8 @@ export class WebGLPreview { // colors private _backgroundColor = new Color(0xe0e0e0); - private _travelColor = new Color(0x990000); + static readonly defaultTravelColor = [new Color('green'), new Color('red')]; + private _travelColor: Color | Color[] = WebGLPreview.defaultTravelColor; private _topLayerColor?: Color; private _lastSegmentColor?: Color; private _toolColors: Record = {}; @@ -188,7 +189,7 @@ export class WebGLPreview { this.extrusionColor = opts.extrusionColor; } if (opts.travelColor !== undefined) { - this.travelColor = new Color(opts.travelColor); + this.travelColor = opts.travelColor; } if (opts.topLayerColor !== undefined) { this.topLayerColor = new Color(opts.topLayerColor); @@ -277,6 +278,21 @@ export class WebGLPreview { return this._extrusionColor[this.state.t] ?? WebGLPreview.defaultExtrusionColor; } + // get travel color based on current state + get currentTravelColor(): Color { + if (this._travelColor === undefined) { + this._travelColor = WebGLPreview.defaultTravelColor; + } + if (this._travelColor instanceof Color) { + return this._travelColor; + } + + const airCutting = this.state.z >= 0; + const color = airCutting ? this._travelColor[0] : this._travelColor[1]; + + return color ?? WebGLPreview.defaultTravelColor[0]; + } + get backgroundColor(): Color { return this._backgroundColor; } @@ -286,10 +302,19 @@ export class WebGLPreview { this.scene.background = this._backgroundColor; } - get travelColor(): Color { + get travelColor(): Color | Color[] { return this._travelColor; } - set travelColor(value: number | string | Color) { + set travelColor(value: number | string | Color | ColorRepresentation[]) { + if (Array.isArray(value)) { + this._travelColor = []; + // loop over the object and convert all colors to Color + for (const [index, color] of value.entries()) { + this._travelColor[index] = new Color(color); + } + + return; + } this._travelColor = new Color(value); } @@ -477,9 +502,11 @@ export class WebGLPreview { t: this.state.t }; + const travelLineSize = currentLayer.travel.length; if (index >= this.minLayerIndex) { const extrude = (g.params.e ?? 0) > 0 || this.nonTravelmoves.indexOf(cmd.gcode) > -1; const moving = next.x != this.state.x || next.y != this.state.y || next.z != this.state.z; + if (moving) { if ((extrude && this.renderExtrusion) || (!extrude && this.renderTravel)) { if (cmd.gcode == 'g2' || cmd.gcode == 'g3' || cmd.gcode == 'g02' || cmd.gcode == 'g03') { @@ -491,6 +518,12 @@ export class WebGLPreview { } } + if (this.renderTravel) { + const travelColor = this.currentTravelColor; + const newTravelLines = currentLayer.travel.slice(travelLineSize, currentLayer.travel.length); + this.addLine(newTravelLines, travelColor.getHex()); + } + // update this.state this.state.x = next.x; this.state.y = next.y; @@ -536,10 +569,6 @@ export class WebGLPreview { } } } - - if (this.renderTravel) { - this.addLine(layer.travel, this._travelColor.getHex()); - } } setInches(): void { @@ -554,7 +583,7 @@ export class WebGLPreview { drawBuildVolume(): void { if (!this.buildVolume) return; - this.scene.add(new GridHelper(this.buildVolume.x, 10, this.buildVolume.y, 10)); + this.scene.add(new GridHelper(this.buildVolume.x, 60, this.buildVolume.y, 60)); const geometryBox = LineBox(this.buildVolume.x, this.buildVolume.z, this.buildVolume.y, 0x888888); From ac0c4023954ac89c7cbb83a152a81c95fc1647ef Mon Sep 17 00:00:00 2001 From: xuzhen1994 Date: Fri, 19 Jul 2024 17:05:12 +0800 Subject: [PATCH 2/7] add isActualCutting option and travel distance calculate add isActualCutting option to support user custom actual-cutting logic. add cutting distance calculation includes total-cutting, actual-cutting and air-cutting for a gcode file. --- src/webgl-preview.ts | 107 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 10 deletions(-) diff --git a/src/webgl-preview.ts b/src/webgl-preview.ts index ae8cc734..80121a0f 100644 --- a/src/webgl-preview.ts +++ b/src/webgl-preview.ts @@ -43,6 +43,19 @@ type Arc = GVector3 & { r: number; i: number; j: number }; type Point = GVector3; type BuildVolume = GVector3; + +type IsActualCutting = (startState: State, endState: State) => boolean; +type TravelDistance = { + total: number; + air: number; + actual: number; +}; +type TravelDuration = { + total: number; + air: number; + actual: number; +}; + export class State { x: number; y: number; @@ -93,6 +106,8 @@ export type GCodePreviewOptions = { targetId?: string; /** @experimental */ devMode?: boolean | DevModeOptions; + + isActualCutting: IsActualCutting; }; const target = { @@ -132,6 +147,8 @@ export class WebGLPreview { nonTravelmoves: string[] = []; disableGradient = false; + isActualCutting: IsActualCutting; + // gcode processing state private state: State = State.initial; private beyondFirstMove = false; // TODO: move to state @@ -153,6 +170,14 @@ export class WebGLPreview { private _lastSegmentColor?: Color; private _toolColors: Record = {}; + // cutting statistics + private _totalTravelDistance?: number[] = []; + private _airTravelDistance?: number[] = []; + private _actualTravelDistance?: number[] = []; + private _totalTravelDuration?: number[] = []; + private _airTravelDuration?: number[] = []; + private _actualTravelDuration?: number[] = []; + // debug private devMode?: boolean | DevModeOptions = false; private _lastRenderTime = 0; @@ -203,6 +228,11 @@ export class WebGLPreview { this._toolColors[parseInt(key)] = new Color(value); } } + if (opts.isActualCutting) { + this.isActualCutting = opts.isActualCutting; + } else { + this.isActualCutting = (start, end) => end.z < 0; + } if (opts.disableGradient !== undefined) { this.disableGradient = opts.disableGradient; @@ -279,7 +309,7 @@ export class WebGLPreview { } // get travel color based on current state - get currentTravelColor(): Color { + getCurrentTravelColor(startState: State, endState: State): Color { if (this._travelColor === undefined) { this._travelColor = WebGLPreview.defaultTravelColor; } @@ -287,8 +317,8 @@ export class WebGLPreview { return this._travelColor; } - const airCutting = this.state.z >= 0; - const color = airCutting ? this._travelColor[0] : this._travelColor[1]; + const actualCutting = this.isActualCutting(startState, endState); + const color = actualCutting ? this._travelColor[0] : this._travelColor[1]; return color ?? WebGLPreview.defaultTravelColor[0]; } @@ -332,6 +362,26 @@ export class WebGLPreview { this._lastSegmentColor = value !== undefined ? new Color(value) : undefined; } + get travelDistance(): TravelDistance { + const total = this._totalTravelDistance.reduce((pre, cur) => pre + cur, 0); + const air = this._airTravelDistance.reduce((pre, cur) => pre + cur, 0); + const actual = this._actualTravelDistance.reduce((pre, cur) => pre + cur, 0); + return { total, air, actual }; + } + + initTravelDistance(layerIdx: number): void { + this._totalTravelDistance[layerIdx] = 0; + this._airTravelDistance[layerIdx] = 0; + this._actualTravelDistance[layerIdx] = 0; + } + + get travelDuration(): TravelDuration { + const total = this._totalTravelDuration.reduce((pre, cur) => pre + cur, 0); + const air = this._airTravelDuration.reduce((pre, cur) => pre + cur, 0); + const actual = this._actualTravelDuration.reduce((pre, cur) => pre + cur, 0); + return { total, air, actual }; + } + /** * @internal Do not use externally. */ @@ -472,6 +522,7 @@ export class WebGLPreview { z: this.state.z, height: l.height }; + this.initTravelDistance(index); for (const cmd of l.commands) { if (cmd.gcode == 'g20') { @@ -515,15 +566,13 @@ export class WebGLPreview { this.addLineSegment(currentLayer, this.state, next, extrude); } } + if (this.renderTravel) { + const newTravelLines = currentLayer.travel.slice(travelLineSize, currentLayer.travel.length); + this.doRenderTravel(newTravelLines, this.state, next, index); + } } } - if (this.renderTravel) { - const travelColor = this.currentTravelColor; - const newTravelLines = currentLayer.travel.slice(travelLineSize, currentLayer.travel.length); - this.addLine(newTravelLines, travelColor.getHex()); - } - // update this.state this.state.x = next.x; this.state.y = next.y; @@ -571,6 +620,42 @@ export class WebGLPreview { } } + /** @internal */ + doRenderTravel(newLines: number[], curState: State, nextState: State, layerIdx: number): void { + if (!newLines || newLines.length < 3) { + return; + } + const travelColor = this.getCurrentTravelColor(curState, nextState); + this.addLine(newLines, travelColor.getHex()); + this.calcTravelDistanceAndDuration(newLines, curState, nextState, layerIdx); + } + + /** @internal */ + calcTravelDistanceAndDuration(newLines: number[], curState: State, nextState: State, layerIdx: number): void { + let [totalDistance, airDistance, actualDistance] = [0, 0, 0]; + let { x, y, z } = curState; + for (let index = 0; index <= newLines.length - 3; ) { + const nextPoint = newLines.slice(index, index + 3); + const [nextX, nextY, nextZ] = nextPoint; + const distance = Math.sqrt(Math.pow(nextX - x, 2) + Math.pow(nextY - y, 2) + Math.pow(nextZ - z, 2)); + totalDistance += distance; + const actualCutting = this.isActualCutting(curState, nextState); + if (actualCutting) { + actualDistance += distance; + } else { + airDistance += distance; + } + + x = nextX; + y = nextY; + z = nextZ; + index += 3; + } + this._totalTravelDistance[layerIdx] += totalDistance; + this._actualTravelDistance[layerIdx] += actualDistance; + this._airTravelDistance[layerIdx] += airDistance; + } + setInches(): void { if (this.beyondFirstMove) { console.warn('Switching units after movement is already made is discouraged and is not supported.'); @@ -583,7 +668,9 @@ export class WebGLPreview { drawBuildVolume(): void { if (!this.buildVolume) return; - this.scene.add(new GridHelper(this.buildVolume.x, 60, this.buildVolume.y, 60)); + this.scene.add( + new GridHelper(this.buildVolume.x, this.buildVolume.x / 10, this.buildVolume.y, this.buildVolume.y / 10) + ); const geometryBox = LineBox(this.buildVolume.x, this.buildVolume.z, this.buildVolume.y, 0x888888); From b0ad5bf2287ae62523d68ffbdb63bacf868db832 Mon Sep 17 00:00:00 2001 From: xuzhen1994 Date: Fri, 19 Jul 2024 17:12:53 +0800 Subject: [PATCH 3/7] change default travel color --- src/webgl-preview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webgl-preview.ts b/src/webgl-preview.ts index 80121a0f..6af27a00 100644 --- a/src/webgl-preview.ts +++ b/src/webgl-preview.ts @@ -164,7 +164,7 @@ export class WebGLPreview { // colors private _backgroundColor = new Color(0xe0e0e0); - static readonly defaultTravelColor = [new Color('green'), new Color('red')]; + static readonly defaultTravelColor = [new Color('red'), new Color('green')]; private _travelColor: Color | Color[] = WebGLPreview.defaultTravelColor; private _topLayerColor?: Color; private _lastSegmentColor?: Color; From a5187271de2bcaac425ee7e67da896bd959fa322 Mon Sep 17 00:00:00 2001 From: xuzhen1994 Date: Tue, 23 Jul 2024 18:46:10 +0800 Subject: [PATCH 4/7] add travel duration calculate add travel duration calculate --- src/webgl-preview.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/webgl-preview.ts b/src/webgl-preview.ts index 6af27a00..e97fd6aa 100644 --- a/src/webgl-preview.ts +++ b/src/webgl-preview.ts @@ -65,10 +65,10 @@ export class State { i: number; j: number; t: number; // tool index - // feedrate? + f: number; // feedrate static get initial(): State { const state = new State(); - Object.assign(state, { x: 0, y: 0, z: 0, r: 0, e: 0, i: 0, j: 0, t: 0 }); + Object.assign(state, { x: 0, y: 0, z: 0, r: 0, e: 0, i: 0, j: 0, t: 0, f: 20000 }); return state; } } @@ -382,6 +382,12 @@ export class WebGLPreview { return { total, air, actual }; } + initTravelDuration(layerIdx: number): void { + this._totalTravelDuration[layerIdx] = 0; + this._airTravelDuration[layerIdx] = 0; + this._actualTravelDuration[layerIdx] = 0; + } + /** * @internal Do not use externally. */ @@ -523,6 +529,7 @@ export class WebGLPreview { height: l.height }; this.initTravelDistance(index); + this.initTravelDuration(index); for (const cmd of l.commands) { if (cmd.gcode == 'g20') { @@ -550,6 +557,7 @@ export class WebGLPreview { e: g.params.e ?? this.state.e, i: g.params.i ?? this.state.i, j: g.params.j ?? this.state.j, + f: g.params.f ?? this.state.f, t: this.state.t }; @@ -577,6 +585,7 @@ export class WebGLPreview { this.state.x = next.x; this.state.y = next.y; this.state.z = next.z; + this.state.f = next.f; // if (next.e) state.e = next.e; // where not really tracking e as distance (yet) but we only check if some commands are extruding (positive e) if (!this.beyondFirstMove) this.beyondFirstMove = true; } @@ -633,17 +642,23 @@ export class WebGLPreview { /** @internal */ calcTravelDistanceAndDuration(newLines: number[], curState: State, nextState: State, layerIdx: number): void { let [totalDistance, airDistance, actualDistance] = [0, 0, 0]; - let { x, y, z } = curState; + let [totalDuration, airDuration, actualDuration] = [0, 0, 0]; + let { x, y, z, f } = curState; for (let index = 0; index <= newLines.length - 3; ) { const nextPoint = newLines.slice(index, index + 3); const [nextX, nextY, nextZ] = nextPoint; const distance = Math.sqrt(Math.pow(nextX - x, 2) + Math.pow(nextY - y, 2) + Math.pow(nextZ - z, 2)); totalDistance += distance; + const duration = (distance / f) * 60; + + totalDuration += duration; const actualCutting = this.isActualCutting(curState, nextState); if (actualCutting) { actualDistance += distance; + actualDuration += duration; } else { airDistance += distance; + airDuration += duration; } x = nextX; @@ -654,6 +669,10 @@ export class WebGLPreview { this._totalTravelDistance[layerIdx] += totalDistance; this._actualTravelDistance[layerIdx] += actualDistance; this._airTravelDistance[layerIdx] += airDistance; + + this._totalTravelDuration[layerIdx] += totalDuration; + this._actualTravelDuration[layerIdx] += actualDuration; + this._airTravelDuration[layerIdx] += airDuration; } setInches(): void { From 3cdac698407c11324408df2b4236af485decc295 Mon Sep 17 00:00:00 2001 From: Remco Veldkamp Date: Sat, 27 Jul 2024 14:07:35 +0200 Subject: [PATCH 5/7] wip --- demo/gcodes/cnc.gcode | 183 ++++++++++++++++++++++++++++++++++++++++++ demo/index.html | 1 + demo/js/demo.js | 20 +++++ src/webgl-preview.ts | 110 +++++++++++++------------ 4 files changed, 262 insertions(+), 52 deletions(-) create mode 100644 demo/gcodes/cnc.gcode diff --git a/demo/gcodes/cnc.gcode b/demo/gcodes/cnc.gcode new file mode 100644 index 00000000..c8989ebc --- /dev/null +++ b/demo/gcodes/cnc.gcode @@ -0,0 +1,183 @@ +T1 +G43 H1 +M03 S18000 +G00 X176.604 Y554.890 +G00 Z10.000 +F4000 +G01 Z-12.100 +F12000 +G02 X183.675 Y561.961 I3.536 J3.536 +G02 X176.604 Y554.890 I-3.536 J-3.536 +F4000 +G00 Z20.000 +G00 X-4.000 Y785.175 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X-0.000 Y789.175 I4.000 J0.000 +G01 X380.139 Y789.175 +G02 X384.139 Y785.175 I0.000 J-4.000 +G01 X384.139 Y331.675 +G02 X380.139 Y327.675 I-4.000 J0.000 +F4000 +G00 Z20.000 +G00 X457.500 Y323.675 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X461.500 Y327.675 I4.000 J0.000 +G01 X915.000 Y327.675 +G02 X919.000 Y323.675 I-0.000 J-4.000 +F4000 +G00 Z20.000 +G00 X457.500 Y256.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X453.500 Y260.000 I-4.000 J0.000 +F4000 +G00 Z20.000 +G00 X457.500 Y176.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X461.500 Y172.000 I4.000 J0.000 +F4000 +G00 Z20.000 +G00 X457.500 Y168.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X453.500 Y172.000 I-4.000 J0.000 +F4000 +G00 Z20.000 +G00 X457.500 Y88.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X461.500 Y84.000 I4.000 J0.000 +F4000 +G00 Z20.000 +G00 X457.500 Y80.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X453.500 Y84.000 I-4.000 J0.000 +F4000 +G00 Z20.000 +G00 X457.500 Y0.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X453.500 Y-4.000 I-4.000 J0.000 +F4000 +G00 Z20.000 +G00 X461.500 Y-4.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X457.500 Y-0.000 I-0.000 J4.000 +G01 X457.500 Y80.000 +G02 X461.500 Y84.000 I4.000 J0.000 +G01 X915.000 Y84.000 +G03 X919.000 Y88.000 I0.000 J4.000 +F4000 +G00 Z20.000 +G00 X915.000 Y84.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X919.000 Y80.000 I-0.000 J-4.000 +F4000 +G00 Z20.000 +G00 X915.000 Y172.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X919.000 Y176.000 I-0.000 J4.000 +F4000 +G00 Z20.000 +G00 X919.000 Y256.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X915.000 Y260.000 I-4.000 J0.000 +F4000 +G00 Z20.000 +G00 X919.000 Y264.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X915.000 Y260.000 I-4.000 J0.000 +G01 X461.500 Y260.000 +G03 X457.500 Y256.000 I-0.000 J-4.000 +G01 X457.500 Y176.000 +G02 X453.500 Y172.000 I-4.000 J0.000 +G01 X0.000 Y172.000 +G03 X-4.000 Y168.000 I-0.000 J-4.000 +F4000 +G00 Z20.000 +G00 X0.000 Y172.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X-4.000 Y176.000 I-0.000 J4.000 +F4000 +G00 Z20.000 +G00 X-0.000 Y260.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X-4.000 Y264.000 I0.000 J4.000 +F4000 +G00 Z20.000 +G00 X-4.000 Y323.675 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X-0.000 Y327.675 I4.000 J0.000 +F4000 +G00 Z20.000 +G00 X-4.000 Y331.675 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X0.000 Y327.675 I4.000 J0.000 +G01 X453.500 Y327.675 +G02 X457.500 Y323.675 I0.000 J-4.000 +G01 X457.500 Y264.000 +G02 X453.500 Y260.000 I-4.000 J0.000 +G01 X-0.000 Y260.000 +G03 X-4.000 Y256.000 I0.000 J-4.000 +F4000 +G00 Z20.000 +G00 X-4.000 Y88.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G03 X-0.000 Y84.000 I4.000 J0.000 +F4000 +G00 Z20.000 +G00 X-4.000 Y80.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X-0.000 Y84.000 I4.000 J0.000 +G01 X453.500 Y84.000 +G03 X457.500 Y88.000 I0.000 J4.000 +G01 X457.500 Y168.000 +G02 X461.500 Y172.000 I4.000 J0.000 +G01 X915.000 Y172.000 +G02 X919.000 Y168.000 I0.000 J-4.000 +F4000 +G00 Z20.000 +G00 X461.500 Y260.000 +G00 Z10.000 +G01 Z-12.100 +F12000 +G02 X457.500 Y264.000 I0.000 J4.000 +F4000 +G00 Z20.000 +M05 +M17 +M30 \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index 2ce1e78d..f938f2b6 100644 --- a/demo/index.html +++ b/demo/index.html @@ -37,6 +37,7 @@

GCode Preview

File: