From f955e4be1a7c18791ef75fa2f68389917084cd48 Mon Sep 17 00:00:00 2001 From: yuta nakayama Date: Thu, 22 Jun 2023 21:25:36 +0800 Subject: [PATCH 1/4] add exportGcode method to download gcode file --- examples/2.5D-sketching/sketch.js | 10 +++++++++- examples/hollow-cube/sketch.js | 6 ++++++ examples/line-vase/sketch.js | 7 +++++++ examples/template/sketch.js | 6 ++++++ examples/vase/sketch.js | 16 ++++++++++++++-- lib/p5.fab.js | 16 ++++++++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/examples/2.5D-sketching/sketch.js b/examples/2.5D-sketching/sketch.js index b7d2e07..13fa20d 100644 --- a/examples/2.5D-sketching/sketch.js +++ b/examples/2.5D-sketching/sketch.js @@ -21,6 +21,12 @@ function setup() { stopButton.mousePressed(function() { fab.stopPrint(); // stop streaming the commands to printer }); + + let exportButton = createButton('export!'); + exportButton.position(20, 140); + exportButton.mousePressed(function() { + fab.exportGcode(); // export gcode to a file. + }); } function fabDraw() { @@ -55,10 +61,12 @@ function fabDraw() { r -= 0.1; } fab.presentPart(); + //console.log(fab.commands); } function draw() { orbitControl(2, 2, 0.1); background(255); fab.render(); -} \ No newline at end of file +} + diff --git a/examples/hollow-cube/sketch.js b/examples/hollow-cube/sketch.js index fe58dda..6010b09 100644 --- a/examples/hollow-cube/sketch.js +++ b/examples/hollow-cube/sketch.js @@ -21,6 +21,12 @@ function setup() { stopButton.mousePressed(function() { fab.stopPrint(); // stop streaming the commands to printer }); + + let exportButton = createButton('export!'); + exportButton.position(20, 140); + exportButton.mousePressed(function() { + fab.exportGcode(); // export gcode to a file. + }); } function fabDraw() { diff --git a/examples/line-vase/sketch.js b/examples/line-vase/sketch.js index 5d9820f..04d3539 100644 --- a/examples/line-vase/sketch.js +++ b/examples/line-vase/sketch.js @@ -22,6 +22,13 @@ function setup() { stopButton.mousePressed(function() { fab.stopPrint(); // stop streaming the commands to printer }); + + let exportButton = createButton('export!'); + exportButton.position(20, 140); + exportButton.mousePressed(function() { + fab.exportGcode(); // export gcode to a file. + }); + } diff --git a/examples/template/sketch.js b/examples/template/sketch.js index 548a902..040631d 100644 --- a/examples/template/sketch.js +++ b/examples/template/sketch.js @@ -22,6 +22,12 @@ function setup() { fab.stopPrint(); // stop streaming the commands to printer. }); + let exportButton = createButton('export!'); + exportButton.position(20, 140); + exportButton.mousePressed(function() { + fab.exportGcode(); // export gcode to a file. + }); + } diff --git a/examples/vase/sketch.js b/examples/vase/sketch.js index 17cf26f..daf95ee 100644 --- a/examples/vase/sketch.js +++ b/examples/vase/sketch.js @@ -6,17 +6,29 @@ function setup() { fab = createFab(); // add a buttons to connect to the printer & to print! - connectButton = createButton('connect!'); + let connectButton = createButton('connect!'); connectButton.position(20, 20); connectButton.mousePressed(function() { fab.serial.requestPort(); }); - printButton = createButton('print!'); + let printButton = createButton('print!'); printButton.position(20, 60); printButton.mousePressed(function() { fab.print(); }); + + let stopButton = createButton('stop!'); + stopButton.position(20, 100); + stopButton.mousePressed(function() { + fab.stopPrint(); // stop streaming the commands to printer. + }); + + let exportButton = createButton('export!'); + exportButton.position(20, 140); + exportButton.mousePressed(function() { + fab.exportGcode(); // export gcode to a file. + }); } function draw() { diff --git a/lib/p5.fab.js b/lib/p5.fab.js index f3a9245..1ffbc27 100644 --- a/lib/p5.fab.js +++ b/lib/p5.fab.js @@ -289,6 +289,22 @@ class Fab { }); } + exportGcode(fileName = new Date().toISOString().slice(0, 19).replace(/:/g, "")) { + let gcodeText = ""; + fab.commands.forEach(command => { + gcodeText += command + "\n"; + }); + + let element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(gcodeText)); + element.setAttribute('download', `${fileName}.gcode`); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + + } + render() { orbitControl(2, 2, 0.1); From f8fa8dc8a589eef5b5366e267a084a0155b400be Mon Sep 17 00:00:00 2001 From: yuta nakayama Date: Thu, 22 Jun 2023 22:58:35 +0800 Subject: [PATCH 2/4] add finishPrint sequence --- lib/p5.fab.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/p5.fab.js b/lib/p5.fab.js index f3a9245..56a1be1 100644 --- a/lib/p5.fab.js +++ b/lib/p5.fab.js @@ -425,6 +425,8 @@ class Fab { this.commandStream.unshift(cmd); } + + stopPrint() { this.commandStream = []; // clear commands this.isPrinting = false; @@ -460,6 +462,20 @@ class Fab { this.add(cmd); } + finishPrint() { + var finishCommands = [ + "G1 Z79 F600 ; Move print head further up", + "G1 Z150 F600 ; Move print head further up", + "M140 S0 ; turn off heatbed", + "M104 S0 ; turn off temperature", + "M107 ; turn off fan", + "M84 X Y E ; disable motors"]; + + finishCommands.forEach(cmd => { + this.add(cmd); + }); + } + waitCommand() { var cmd = "M400"; this.add(cmd); From 09f1dcfdcdb41697d13f9f8bcaff3cbdfe3c1683 Mon Sep 17 00:00:00 2001 From: yuta nakayama Date: Thu, 22 Jun 2023 23:26:46 +0800 Subject: [PATCH 3/4] add export gcode menu item to the editor --- editor/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/index.html b/editor/index.html index eff5e64..6d6fcc6 100755 --- a/editor/index.html +++ b/editor/index.html @@ -110,6 +110,7 @@

p5.fab

+ source From 9e4ec2ce84ce9d332a936220dfd9393842054e2e Mon Sep 17 00:00:00 2001 From: nkymut Date: Fri, 6 Dec 2024 15:34:19 +0800 Subject: [PATCH 4/4] fix: Ender3 v2 WebSerial connection issue #8 --- examples/control-panel/sketch.js | 2 +- lib/p5.fab.js | 359 ++++++++++++++----------------- 2 files changed, 162 insertions(+), 199 deletions(-) diff --git a/examples/control-panel/sketch.js b/examples/control-panel/sketch.js index 3c94670..4ef087c 100644 --- a/examples/control-panel/sketch.js +++ b/examples/control-panel/sketch.js @@ -13,7 +13,7 @@ function fabDraw() { } function connectPrinter() { - fab.serial.requestPort(); + fab.connectPrinter(); } function bSend(dir) { diff --git a/lib/p5.fab.js b/lib/p5.fab.js index 462f467..f935c0e 100644 --- a/lib/p5.fab.js +++ b/lib/p5.fab.js @@ -189,22 +189,30 @@ class Fab { this.serialResp = ""; this.callbacks = {}; - this.serial.on("portavailable", function () { - _fab.serial.open({ baudRate: _fab.baudRate }); + // Convert event handlers to async/await + this.serial.on("portavailable", async () => { + try { + await this.serial.open({ baudRate: this.baudRate }); + console.log("Port opened successfully"); + } catch (err) { + console.error("Failed to open port:", err); + } }); - this.serial.on("requesterror", function () { - console.log("error!"); + this.serial.on("requesterror", (err) => { + console.error("Error requesting port:", err); }); - this.serial.on("data", this.onData); + // Bind onData to preserve 'this' context + this.serial.on("data", () => this.onData()); - this.serial.on("open", function () { - console.log("port open"); + this.serial.on("open", () => { + console.log("Port open"); + this.emit("connected", this); }); if (config.autoConnect) { - this.serial.getPorts(); + this.connectPrinter(); } this.on("ok", this.serial_ok); @@ -262,6 +270,29 @@ class Fab { this.midiMode = false; this.midiSetup = null; this.midiDraw = null; + + // Add response types + this.RESPONSE_TYPES = { + OK: 'ok', + POSITION: 'Count', + AUTOHOME: 'Autohome complete', + PRINT_FINISHED: 'Print Finished' + }; + + // Add buffer management + this.messageBuffer = ''; + this.lastMessageTime = Date.now(); + } + + // Add new async connect method + async connectPrinter() { + try { + await this.serial.requestPort(); + // Port selection will trigger the "portavailable" event handler + } catch (err) { + console.error("Failed to request port:", err); + this.emit("error", err); + } } getStackTrace() { @@ -286,154 +317,44 @@ class Fab { console.log("print in progress, cant start a new print"); return; } - if (!this.isPrinting && _syncVizStream) { - this.commandStream = this.commands; - _syncVizStream = false; - } - // send first commands + // Create fresh copy of commands for printing + this.commandStream = [...this.commands]; + this.commands = []; // Clear commands after copying + + // Send first command if we have any if (this.commandStream.length > 0) { this.isPrinting = true; this.serial.write(this.commandStream[0] + "\n"); - console.log("starting print"); + console.log("sending:", this.commandStream[0]); this.commandStream.shift(); - } else { - console.log("print finished!"); - this.isPrinting = false; - } - - if (this.fabscribe) { - this.startTime = Date.now(); - - this.mediaRecorder = new MediaRecorder(this.videoStream, { mimeType: 'video/webm' }); - - this.mediaRecorder.addEventListener('dataavailable', function (e) { - _fab.blobsRecorded.push(e.data); - }); - - this.mediaRecorder.addEventListener('stop', function () { - // create local object URL from the recorded video blobs to download - let videoLocal = URL.createObjectURL(new Blob(_fab.blobsRecorded, { type: 'video/webm' })); - const videoLink = document.createElement("a"); - videoLink.href = videoLocal; - videoLink.download = 'test.webm'; - videoLink.click(); - }); - - // start recording with each recorded blob having 1 second video - fab.mediaRecorder.start(1000); } } - printStream() { - if (this.commandStream.length > 0) { - this.isPrinting = true; - let commandToSend = this.commandStream[0]; - if (this.midiMode) { - commandToSend = this.updateWithMidiValues(this.commandStream[0]); - } - this.serial.write(commandToSend + "\n"); - if (this.fabscribe) { - this.fabscription(commandToSend); + processMessage(message) { + // Log all messages for debugging + console.log('Received:', message); + + if (message.includes('ok')) { + // Continue printing if there are more commands + if (this.commandStream.length > 0) { + this.serial.write(this.commandStream[0] + "\n"); + console.log("sending:", this.commandStream[0]); + this.commandStream.shift(); + } else { + console.log("print finished!"); + this.isPrinting = false; } - this.commandStream.shift(); - } else { - console.log("print finished!"); - this.isPrinting = false; } - } - - fabscription(commandToSend) { - this.sentCommands.push(commandToSend); // add this sent commands - if (commandToSend != "M114 R") { - // this is to remove position logs from the gcode - // Might need to instead include & look for a special character - // in case the operator actually sends and M114 R - // but for now, its only used for transcription - this.sentCommandsFiltered.push(commandToSend); // add this sent commands - } - - // Confirm when buffer is first filled - if (this.autoHomeComplete && !this.bufferFilled) { - const bufferedCommands = ["G0", "G1", "G2", "G3", "G90", "G91", "M82", "M83"]; // TODO: Check this list for other firmware - let cmdType = this.commandStream[0].split(' ')[0]; - if (bufferedCommands.includes(cmdType)) { - let cmdSizeBytes = (new TextEncoder().encode(this.commandStream[0] + "\n")).length; - this.bufferFillSize = this.bufferFillSize + cmdSizeBytes; - console.log('buffer size is:', this.bufferFillSize); - - // get the next buffered command and see if that will put us over the buffer size - let checkFullBuffer = false; - let cmdCheckIndex = 1; - while (!checkFullBuffer) { - let nextCmd = this.commandStream[cmdCheckIndex]; - let nextCmdType = this.commandStream[0].split(' ')[0]; - if (bufferedCommands.includes(nextCmdType)) { - checkFullBuffer = true; - let nextCmdSizeBytes = (new TextEncoder().encode(nextCmd + "\n")).length; - let nextBufferSize = this.bufferFillSize + nextCmdSizeBytes; - if (nextBufferSize > this.bufferSize) { - console.log('command full after the next command added!'); - this.bufferFilled = true; - console.log('it took this many commands to fill the buffer:', this.numCommandsToFillBuffer); - - // pad the end of the print with the same # of commands for transcription to persist through end of print - for (let i = 0; i <= this.numCommandsToFillBuffer + 1; i++) { - this.commandStream.push(`G1 X100 Y100 Z${100 + i} ;;; cut this line`); // change this to something more robust? - this.commandStream.push("M118 Print Finished"); // for transcription - } - } - else { - cmdCheckIndex += 1; - } - } - } - } - this.numCommandsToFillBuffer += 1; + // Handle position reporting + if (message.includes('Count')) { + this.reportedPos = message.split('Count')[0].trim(); } } - updateWithMidiValues(cmd) { - // TODO: should first remove any comments - let moveCommands = ['G0', 'G1', 'G2', 'G3']; - let splitCmd = cmd.split(' '); - let code = splitCmd[0]; - - // record all midi values - // probably want to filter this by tag, to only show the value when the value is being applied to the print - // currently manually grabbing each value, should dynamically get all relevant knobs from midi.js - var currentTime = Date.now() - _fab.startTime; - - if (this.fabscribe) { - // grab all the user specified props for the midi controller and record them - for (const property in _midiController) { - if (property == 'debug') { - continue; - } - this.midiRecording[property] = this.midiRecording[property] || []; - this.midiRecording[property].push([currentTime, _midiController[property]]); - } - } - - - if (moveCommands.indexOf(code) > -1) { - // update the 'realtime' positions - // This is technically a bit in front of real position depending on buffer size - // consider using M114 R? - let moveCommand = new LinearMove(cmd); - - if (moveCommand.x) { this.realtimePosition.x = moveCommand.x }; - if (moveCommand.y) { this.realtimePosition.y = moveCommand.y }; - if (moveCommand.z) { this.realtimePosition.z = moveCommand.z }; - - if (this.midiDraw) { - moveCommand = this.midiDraw(moveCommand); // run the midiDraw function - cmd = moveCommand.toString(); - } - } - - return cmd + getPos() { + this.add("M114"); } on(event, cb) { @@ -453,56 +374,101 @@ class Fab { } onData() { - if (_fab.isPrinting) { - _fab.serialResp += _fab.serial.readString(); - - if (_fab.serialResp.slice(-1) == "\n") { - if (_fab.serialResp.search("ok") > -1) { - _fab.emit("ok", _fab); - let currentTime = Date.now() - _fab.startTime; - - // get position, if we didn't just do that, autohoming is complete, and the buffer is filled - // (if things are exectuted out of order, does this mess up?) - if (_fab.fabscribe) { - if (_fab.autoHomeComplete && _fab.bufferFilled) { - if (_fab.sentCommands[_fab.sentCommands.length - 1] != "M114 R") { - _fab.commandStream.unshift("M114 R"); - } - } - } - } + try { + // Read new data + const newData = this.serial.readString(); + if (!newData) return; - if (_fab.serialResp.search(" Count ") > -1) { - _fab.reportedPos = _fab.serialResp.split(" Count ")[0].trim(); - if (_fab.autoHomeComplete && _fab.bufferFilled) { - var logEntry = [Date.now() - _fab.startTime, _fab.reportedPos]; - _fab.log.push(logEntry); - } - } + // Update buffer and timestamp + this.messageBuffer += newData; + this.lastMessageTime = Date.now(); - if (_fab.serialResp.search("Autohome complete") > -1) { - // for transcription: toggle to start sending M114 R commands - _fab.autoHomeComplete = true; - var logEntry = [Date.now() - _fab.startTime, "AUTOHOMED"]; - _fab.log.push(logEntry); - //this is the keyword hosts like e.g. pronterface search for M114 respos (https://github.com/kliment/Printrun/issues/1103) - _fab.reportedPos = _fab.serialResp.split(" Count ")[0].trim(); - } + // Process messages + this.processMessageBuffer(); - if (_fab.serialResp.search("Print Finished") > -1) { - if (_fab.fabscribe) { - if (!_fab.hasDownloadedLog) { - _fab.mediaRecorder.stop(); - _fab.downloadFabscriptionLog(); - _fab.hasDownloadedLog = true; - } - } + } catch (error) { + console.error('Error in onData:', error); + this.emit('error', error); + } + } + + processMessageBuffer() { + // Split buffer into complete messages + const messages = this.messageBuffer.split('\n'); + + // Keep last incomplete message in buffer + this.messageBuffer = messages.pop() || ''; + + // Process each complete message + messages.forEach(message => { + if (message.trim()) { + this.processMessage(message.trim()); + } + }); + } + + getMessageType(message) { + if (message.includes(this.RESPONSE_TYPES.OK)) return 'ok'; + if (message.includes(this.RESPONSE_TYPES.POSITION)) return 'position'; + if (message.includes(this.RESPONSE_TYPES.AUTOHOME)) return 'autohome'; + if (message.includes(this.RESPONSE_TYPES.PRINT_FINISHED)) return 'print_finished'; + return 'unknown'; + } + + handleOkResponse() { + this.emit('ok', this); + + if (this.isPrinting) { + // Handle position query for fabscribe + if (this.fabscribe && this.autoHomeComplete && this.bufferFilled) { + if (this.sentCommands[this.sentCommands.length - 1] !== "M114 R") { + this.commandStream.unshift("M114 R"); } - _fab.serialResp = ""; } } } + handlePositionResponse(message) { + const position = message.split(" Count ")[0].trim(); + this.reportedPos = position; + + if (this.autoHomeComplete && this.bufferFilled) { + const logEntry = [Date.now() - this.startTime, position]; + this.log.push(logEntry); + } + } + + handleAutohomeResponse() { + this.autoHomeComplete = true; + if (this.fabscribe) { + const logEntry = [Date.now() - this.startTime, "AUTOHOMED"]; + this.log.push(logEntry); + } + } + + handlePrintFinished() { + if (this.fabscribe && !this.hasDownloadedLog) { + this.mediaRecorder.stop(); + this.downloadFabscriptionLog(); + this.hasDownloadedLog = true; + } + this.resetPrinterState(); + } + + handleUnknownResponse(message) { + console.log('Unknown printer response:', message); + } + + // Add message timeout checking + checkMessageTimeout() { + const TIMEOUT_MS = 5000; // 5 seconds + if (this.messageBuffer && (Date.now() - this.lastMessageTime > TIMEOUT_MS)) { + console.warn('Message timeout - clearing buffer'); + this.messageBuffer = ''; + this.emit('warning', 'Message timeout occurred'); + } + } + parseGcode() { this.vertices = []; _fab.commands.forEach((cmd) => { @@ -814,15 +780,10 @@ class Fab { } stopPrint() { - this.commandStream = []; // clear commands - this.isPrinting = false; - // this.serial.close(); - // this.serial.getPorts(); - fabDraw(); - + this.resetPrinterState(); if (this.fabscribe) { - fab.mediaRecorder.stop(); - fab.downloadFabscriptionLog(); + this.mediaRecorder.stop(); + this.downloadFabscriptionLog(); } } @@ -882,12 +843,6 @@ class Fab { this.add(cmd); } - getPos() { - var cmd = "M114_DETAIL"; - var cmd = "M114 D"; - this.add(cmd); - } - setPos() { var cmd = `G92 X${this.asyncPosition.x} Y${this.asyncPosition.y} Z${this.asyncPosition.z} E${this.asyncPosition.e}`; } @@ -1075,6 +1030,14 @@ class Fab { // fab.midiRecording.extrusionMultiplier.forEach((extrusionEntry) => midiWriter.print(extrusionEntry)); // midiWriter.close(); } + + // Add method to properly reset printer state + resetPrinterState() { + this.isPrinting = false; + this.commandStream = []; + this.serialResp = ""; + console.log("Printer state reset"); + } } function windowResized() {