diff --git a/editor/index.html b/editor/index.html
index 0afafba..7538e57 100755
--- a/editor/index.html
+++ b/editor/index.html
@@ -116,6 +116,7 @@
p5.fab
+
source
diff --git a/examples/2.5D-sketching/sketch.js b/examples/2.5D-sketching/sketch.js
index 2e5bc14..7266364 100644
--- a/examples/2.5D-sketching/sketch.js
+++ b/examples/2.5D-sketching/sketch.js
@@ -10,17 +10,23 @@ function setup() {
fab.serial.requestPort(); // choose the serial port to connect to
});
- let printButton = createButton("print!");
- printButton.position(20, 60);
- printButton.mousePressed(function () {
- fab.print(); // start streaming the commands to printer
+ let startButton = createButton('start!');
+ startButton.position(20, 60);
+ startButton.mousePressed(function () {
+ fab.print(); // start streaming commands to printer
});
- let stopButton = createButton("stop!");
+ 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 fabDraw() {
@@ -52,6 +58,7 @@ function fabDraw() {
r -= 0.1;
}
fab.presentPart();
+ //console.log(fab.commands);
}
function draw() {
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/examples/hollow-cube/sketch.js b/examples/hollow-cube/sketch.js
index 555ca43..668dfff 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 2ffab88..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) => {
@@ -575,6 +541,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() {
if (this.coordinateSystem == "delta") {
this.drawDeltaPrinter();
@@ -772,6 +754,8 @@ class Fab {
this.commandStream.unshift(cmd);
}
+
+
configure(config) {
this.coordinateSystem = config.coordinateSystem;
this.radius = config.radius;
@@ -796,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();
}
}
@@ -845,17 +824,25 @@ 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);
}
- 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}`;
}
@@ -1043,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() {