diff --git a/src/main/java/de/thomas_oster/liblasercut/LibInfo.java b/src/main/java/de/thomas_oster/liblasercut/LibInfo.java index e38b979b..aa26ce20 100644 --- a/src/main/java/de/thomas_oster/liblasercut/LibInfo.java +++ b/src/main/java/de/thomas_oster/liblasercut/LibInfo.java @@ -51,6 +51,7 @@ public static List> getSupportedDrivers() MakeBlockXYPlotter.class, GenericGcodeDriver.class, Grbl.class, + Falcon2.class, SmoothieBoard.class, Marlin.class, FullSpectrumCutter.class, diff --git a/src/main/java/de/thomas_oster/liblasercut/drivers/Falcon2.java b/src/main/java/de/thomas_oster/liblasercut/drivers/Falcon2.java new file mode 100644 index 00000000..a239cc6b --- /dev/null +++ b/src/main/java/de/thomas_oster/liblasercut/drivers/Falcon2.java @@ -0,0 +1,255 @@ +/* + This file is part of LibLaserCut. + Copyright (C) 2011 - 2014 Thomas Oster + Copyright (C) 2024 - 2025 Florian + + LibLaserCut is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LibLaserCut is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with LibLaserCut. If not, see . + + This implementation is based on the generic GRBL driver with some bits + from LAOS and LaserTools drivers. + */ +package de.thomas_oster.liblasercut.drivers; + +import de.thomas_oster.liblasercut.IllegalJobException; +import de.thomas_oster.liblasercut.LaserJob; +import de.thomas_oster.liblasercut.VectorCommand; +import de.thomas_oster.liblasercut.VectorPart; +import de.thomas_oster.liblasercut.properties.LaserProperty; +import de.thomas_oster.liblasercut.ProgressListener; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Locale; + +/** + * This class implements a driver for Creality Falcon 2. + * + * @author Florian Boor + */ +public class Falcon2 extends Grbl +{ + private static final String SETTING_SUPPORTS_VENTILATION = "Supports ventilation"; + + public Falcon2() + { + // The Falcon2 is more or less a common GRBL device with some + // uncommon behaviour and some extra features. + + //set some grbl-specific defaults + setLineend("CRLF"); + setIdentificationLine("Grbl"); + // Grbl uses "ok" flow control + setWaitForOKafterEachLine(true); + setPreJobGcode(getPreJobGcode()+",M3"); + // turn off laser before returning to home position + setPostJobGcode("M5,"+getPostJobGcode()); + // disable air assist + setPostJobGcode("M9,"+getPostJobGcode()); + // Grbl & MrBeam use 1000 for 100% PWM on the spindle/laser + setSpindleMax(1000.0f); + // Grbl doesn't turn off laser during G0 rapids + setBlankLaserDuringRapids(true); + // Grbl can take a while to answer if doing a lot of slow moves + setSerialTimeout(10000); + + // For Falcon 2 - similar models might differ + setBedWidth(400); + setBedHeight(415); + setMax_speed(6000); + setTravel_speed(6000); + + // Bed orientation + setFlipYaxis(true); + + // Devices are shipped with 'Air Assist' ventilation + setSupportsVentilation(true); + + // blank these so that connect automatically uses serial first + // No remote connectivity per default anyway. + setHttpUploadUrl(null); + setHost(null); + } + + private boolean supportsVentilation = false; + + public final boolean getSupportsVentilation() { + return supportsVentilation; + } + + public final void setSupportsVentilation(boolean supportsVentilation) { + this.supportsVentilation = supportsVentilation; + } + + @Override + public Object getProperty(String attribute) { + if (SETTING_SUPPORTS_VENTILATION.equals(attribute)) { + return this.getSupportsVentilation(); + } + else { + return super.getProperty(attribute); + } + } + + @Override + public void setProperty(String attribute, Object value) { + if (SETTING_SUPPORTS_VENTILATION.equals(attribute)) { + this.setSupportsVentilation((Boolean) value); + } + else { + super.setProperty(attribute, value); + } + } + + @Override + public LaosCutterProperty getLaserPropertyForVectorPart() { + return new LaosCutterProperty(true, !this.supportsVentilation, true, true); + } + + @Override + public LaosEngraveProperty getLaserPropertyForRasterPart() { + return new LaosEngraveProperty(true, !this.supportsVentilation, true, true); + } + + @Override + public LaosEngraveProperty getLaserPropertyForRaster3dPart() { + return new LaosEngraveProperty(true, !this.supportsVentilation, true, true); + } + + private Boolean currentVentilation = null; + private void setVentilation(PrintStream out, boolean ventilation) { + if (currentVentilation == null || !currentVentilation.equals(ventilation)) + { + //M08 enables, M09 disables + //TODO: add power controls + out.printf(Locale.US, "M%d\n", ventilation ? 8 : 9); + currentVentilation = ventilation; + System.out.printf("ventilation to: "+ventilation+" \n pjob "+getPostJobGcode()+"\n"); + } + } + + private void setCurrentProperty(PrintStream out, LaserProperty p) { + if (p instanceof LaosCutterProperty) { + LaosCutterProperty prop = (LaosCutterProperty) p; + if (this.supportsVentilation) { + setVentilation(out, prop.getVentilation()); + } + setSpeed(prop.getSpeed()); + setPower(prop.getPower()); + } + else { + throw new RuntimeException("This driver accepts LaosCutter properties only"); + } + } + + @Override + protected void writeVectorGCode(VectorPart vp, double resolution) throws UnsupportedEncodingException, IOException { + for (VectorCommand cmd : vp.getCommandList()) { + switch (cmd.getType()) { + // TODO: x,y should be changed to double because GCode has infinite vector resolution anyway + case MOVETO: + int x = (int) cmd.getX(); + int y = (int) cmd.getY(); + move(out, x, y, resolution); + break; + case LINETO: + x = (int) cmd.getX(); + y = (int) cmd.getY(); + line(out, x, y, resolution); + break; + case SETPROPERTY: + LaosCutterProperty p = (LaosCutterProperty) cmd.getProperty(); + this.setCurrentProperty(out, p); + break; + } + } + } + + @Override + public void saveJob(OutputStream fileOutputStream, LaserJob job) throws UnsupportedOperationException, IllegalJobException, Exception { + currentVentilation = null; + super.saveJob(fileOutputStream,job); + } + + @Override + public void sendJob(LaserJob job, ProgressListener pl, List warnings) throws IllegalJobException, Exception { + currentVentilation = null; + super.sendJob(job, pl, warnings); + } + + @Override + public String getModelName() { + return "Creality Falcon2"; + } + +protected String waitForIdentificationLineInternal(ProgressListener pl) throws IOException { + if (getIdentificationLine() != null && getIdentificationLine().length() > 0) { + String line = ""; + for (int trials = 4; trials > 0; trials--) { + line = waitForLine(); + if (line.startsWith(getIdentificationLine())) { + return null; + } + } + return line; + } + return null; + } + + /** + * Initializes Grbl, handling issuing of soft-reset and initial homing + * (if desired & required). + * @param pl Progress listener to update during connect/homing process + * @return null on success, string if resulting string is not the expected id + * @throws java.io.IOException + */ + @Override + protected String waitForIdentificationLine(ProgressListener pl) throws IOException { + // flush serial buffer + while (in.ready()) { in.readLine(); } + + // send reset character to Grbl to get it to print out its welcome message + pl.taskChanged(this, "Sending soft reset"); + out.write(0x18); + + String error = waitForIdentificationLineInternal(pl); + + if (error != null) + return error; + + // check if board is locked and if so home/unlock + if (getAutoHome() == true) { + pl.taskChanged(this, "Homing"); + sendLineWithoutWait("$H"); + + // wait for "ok" or "[Caution: Unlocked]" followed by "ok" + String line = waitForLine(); + if (!line.equals("ok")) + line = waitForLine(); + if (!line.equals("ok")) + throw new IOException("Homing cycle failed to complete"); + } + + return null; + } + + @Override + public Falcon2 clone() { + Falcon2 clone = new Falcon2(); + clone.copyProperties(this); + clone.supportsVentilation = supportsVentilation; + return clone; + } +} diff --git a/test-output/de.thomas_oster.liblasercut.drivers.Falcon2.out b/test-output/de.thomas_oster.liblasercut.drivers.Falcon2.out new file mode 100644 index 00000000..3835dd0d --- /dev/null +++ b/test-output/de.thomas_oster.liblasercut.drivers.Falcon2.out @@ -0,0 +1,80 @@ +G21 +G90 +M3 +M3 +M8 +G0X0.508000Y414.492000S0 +G1X25.4Y364.2S1000F6000 +G1X50.8Y415 +G0X101.600000Y415.000000S0 +G1X76.2Y409.92S1000F6000 +G1X76.1492Y394.68 +G1X76.1492Y392.14 +G1X76.0984Y389.6 +G1X76.1492Y384.52 +G1X76.2Y369.28 +G1X101.6Y364.2 +G0X0.000000Y413.120400S0 +G1X0.6604Y413.1204S0F6000 +G1X0.762Y413.1204S1000 +G1X0.8636Y413.1204 +G1X0.9144Y413.1204 +G1X1.0668Y413.1204 +G1X1.1176Y413.1204 +G1X6.096Y413.1204S0 +G0X0.000000Y413.069600S0 +G1X0.6604Y413.0696S0F6000 +G1X0.8128Y413.0696S1000 +G1X0.8636Y413.0696 +G1X0.9652Y413.0696 +G1X1.0668Y413.0696 +G1X1.1176Y413.0696 +G1X6.096Y413.0696S0 +G0X0.000000Y413.018800S0 +G1X0.6604Y413.0188S0F6000 +G1X1.1176Y413.0188S1000 +G1X6.096Y413.0188S0 +G0X0.000000Y412.968000S0 +G1X0.6604Y412.968S0F6000 +G1X0.762Y412.968S1000 +G1X1.1176Y412.968 +G1X6.096Y412.968S0 +G0X0.000000Y412.917200S0 +G1X0.6604Y412.9172S0F6000 +G1X1.1176Y412.9172S1000 +G1X6.096Y412.9172S0 +G0X0.000000Y412.866400S0 +G1X0.6604Y412.8664S0F6000 +G1X0.762Y412.8664S1000 +G1X1.1176Y412.8664 +G1X6.096Y412.8664S0 +G0X0.000000Y411.088400S0 +G1X2.2352Y411.0884S0F6000 +G1X2.3368Y411.0884S1000 +G1X2.3876Y411.0884S0 +G1X2.54Y411.0884S1000 +G1X7.5184Y411.0884S0 +G0X0.000000Y411.037600S0 +G1X2.286Y411.0376S0F6000 +G1X2.3368Y411.0376S1000 +G1X2.4384Y411.0376S0 +G1X2.54Y411.0376S1000 +G1X7.5184Y411.0376S0 +G0X0.000000Y410.986800S0 +G1X2.1336Y410.9868S0F6000 +G1X2.1844Y410.9868S750 +G1X2.2352Y410.9868S0 +G1X2.5908Y410.9868S750 +G1X7.5692Y410.9868S0 +G0X0.000000Y410.936000S0 +G1X2.2352Y410.936S0F6000 +G1X2.5908Y410.936S500 +G1X7.5692Y410.936S0 +G0X0.000000Y410.834400S0 +G1X2.1336Y410.8344S0F6000 +G1X2.2352Y410.8344S1000 +G1X7.2136Y410.8344S0 +M9 +M5 +M5 +G0X0Y0