diff --git a/src/com/t_oster/liblasercut/LibInfo.java b/src/com/t_oster/liblasercut/LibInfo.java index 2b7bcb40bcd8f3f25aafb7cb4b44aea944eebc86..1da3a5de245b9363e42443da4c2956bfb509a1c3 100644 --- a/src/com/t_oster/liblasercut/LibInfo.java +++ b/src/com/t_oster/liblasercut/LibInfo.java @@ -46,6 +46,7 @@ public class LibInfo IModelaMill.class, SampleDriver.class, ExportSVG.class, + MakeBlockXYPlotter.class, GenericGcodeDriver.class, SmoothieBoard.class, Marlin.class diff --git a/src/com/t_oster/liblasercut/drivers/MakeBlockXYPlotter.java b/src/com/t_oster/liblasercut/drivers/MakeBlockXYPlotter.java new file mode 100644 index 0000000000000000000000000000000000000000..3390f22e4a06f9c1ac1ce3f011fffc9edc6f3f04 --- /dev/null +++ b/src/com/t_oster/liblasercut/drivers/MakeBlockXYPlotter.java @@ -0,0 +1,586 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + **/ + +/** + * Author: Sven Jung <sven.jung@rwth-aachen.de> + */ + +package com.t_oster.liblasercut.drivers; + +import com.t_oster.liblasercut.IllegalJobException; +import com.t_oster.liblasercut.*; +import com.t_oster.liblasercut.LaserJob; +import com.t_oster.liblasercut.ProgressListener; +import com.t_oster.liblasercut.platform.Point; +import com.t_oster.liblasercut.platform.Util; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import purejavacomm.CommPortIdentifier; +import purejavacomm.NoSuchPortException; +import purejavacomm.PortInUseException; +import purejavacomm.SerialPort; + +/** + * + * @author Sven Jung + */ +public class MakeBlockXYPlotter extends LaserCutter +{ + private enum ToolState { + ON, OFF + } + + /* + * Internal Settings + */ + private final boolean debug = false; // print to command line + private static final String MODELNAME = "MakeBlockXYPlotter"; + private double addSpacePerRasterLine = 0.5; + private String hostname = ""; + private double bedWidth = 300; + private double bedHeight = 210; + private int delayRate = 5000; + private int powerRate = 255; + private String usedTool = "PEN"; // PEN, Laser + private List<Double> resolutions = Arrays.asList(new Double[]{ + 64d // fine liner + }); + + private int chosenDelay; + private int chosenPower; + private ToolState toolState; + + private PrintWriter w = null; + BufferedReader portReader = null; + private BufferedOutputStream out = null; + private SerialPort port = null; + + /* + * Global Settings + */ + private static final String SETTING_HOSTNAME = "Target port:// or file://"; + private static final String SETTING_RASTER_WHITESPACE = "Additional space per Raster line (mm)"; + private static final String SETTING_BEDWIDTH = "Laserbed width (mm)"; + private static final String SETTING_BEDHEIGHT = "Laserbed height (mm)"; + private static final String SETTING_DELAY_RATE = "Max. Delay Rate (abs. us)"; + private static final String SETTING_POWER_RATE = "Max. Power Rate (abs. pwm)"; + private static final String SETTING_TOOL = "Tool (PEN, LASER)"; + private static String[] settingAttributes = new String[]{ + SETTING_HOSTNAME, + SETTING_RASTER_WHITESPACE, + SETTING_BEDWIDTH, + SETTING_BEDHEIGHT, + SETTING_DELAY_RATE, + SETTING_POWER_RATE, + SETTING_TOOL + }; + + + /** + * Get the value of MODELNAME + * + * @return the value of MODELNAME + */ + @Override + public String getModelName() { + return MODELNAME; + } + + @Override + public List<Double> getResolutions() { + return resolutions; + } + + @Override + public MakeBlockXYPlotterProperty getLaserPropertyForVectorPart() { + return new MakeBlockXYPlotterProperty(this.usedTool.equals("LASER")); // show power and speed only if laser + } + + @Override + public MakeBlockXYPlotterProperty getLaserPropertyForRasterPart() + { + return new MakeBlockXYPlotterProperty(this.usedTool.equals("LASER")); // show power and speed only if laser + } + + /** + * Get the value of bedWidth + * + * @return the value of bedWidth + */ + @Override + public double getBedWidth() + { + return bedWidth; + } + + /** + * Get the value of bedHeight + * + * @return the value of bedHeight + */ + @Override + public double getBedHeight() + { + return bedHeight; + } + + + private void generateInitializationGCode() throws Exception { + toolOff(); + this.sendCommand("G28 X Y");//move to 0 0 + } + + private void generateShutdownGCode() throws Exception { + //back to origin and shutdown + toolOff(); + this.sendCommand("G28 X Y");//move to 0 0 + } + + private void toolOff() throws Exception { + if(toolState != ToolState.OFF) { + if(usedTool.equals("PEN")) { + this.sendCommand("M1 90"); + this.sendCommand(String.format("M3 %d", 0)); // to ensure fastest speed + } else if(usedTool.equals("LASER")) { + this.sendCommand(String.format("M4 %d", 0)); + this.sendCommand(String.format("M3 %d", 0)); // to move faster with tool off + } else { + throw new Exception("Tool " + this.usedTool + " not supported!"); + } + toolState = ToolState.OFF; + } + } + + private void toolOn() throws Exception { + if(toolState != ToolState.ON) { + if(usedTool.equals("PEN")) { + this.sendCommand(String.format("M3 %d", 0)); // to ensure fastest speed + this.sendCommand("M1 130"); + } else if(usedTool.equals("LASER")) { + this.sendCommand(String.format("M3 %d", (int) ((double) delayRate * this.chosenDelay / 100))); + this.sendCommand(String.format("M4 %d", (int) ((double) powerRate * this.chosenPower / 100))); + } else { + throw new Exception("Tool " + this.usedTool + " not supported!"); + } + toolState = ToolState.ON; + } + } + + private void setDelay(int value) throws Exception{ + // saves just the chosen delay value + // delay of the plotter really set on toolOn(), to move faster with tool off + if(usedTool.equals("LASER")) { // property option only supported if laser + if (value != chosenDelay) { + chosenDelay = value; + } + } + } + + private void setPower(int value) throws Exception{ + // saves just the chosen power value + // power of the laser really set on toolOn() + if(usedTool.equals("LASER")) { // property option only supported if laser + if (value != chosenPower) { + chosenPower = value; + } + } + } + + private void move(int x, int y, double resolution) throws Exception{ + toolOff(); + this.sendCommand(String.format(Locale.US, "G0 X%f Y%f", Util.px2mm(x, resolution), Util.px2mm(y, resolution))); + } + + private void line(int x, int y, double resolution) throws Exception{ + toolOn(); + this.sendCommand(String.format(Locale.US, "G1 X%f Y%f", Util.px2mm(x, resolution), Util.px2mm(y, resolution))); + } + + private void generateVectorGCode(VectorPart vp, double resolution, ProgressListener pl, int startProgress, int maxProgress) throws UnsupportedEncodingException, Exception { + int i = 0; + int progress; + int max = vp.getCommandList().length; + for (VectorCommand cmd : vp.getCommandList()) { + switch (cmd.getType()) { + case MOVETO: + int x = cmd.getX(); + int y = cmd.getY(); + this.move(x, y, resolution); + break; + case LINETO: + x = cmd.getX(); + y = cmd.getY(); + this.line(x, y, resolution); + break; + case SETPROPERTY: // called once per part to set chosen properties + MakeBlockXYPlotterProperty p = (MakeBlockXYPlotterProperty) cmd.getProperty(); // only set with LASER tool + // ensure percent power + int pPercent = p.getPower(); + pPercent = pPercent<0?0:pPercent; + pPercent = pPercent>100?100:pPercent; + this.setPower(pPercent); + // ensure percent speed + int sPercent = p.getSpeed(); + sPercent = sPercent<0?0:sPercent; + sPercent = sPercent>100?100:sPercent; + int dPercent = 100-sPercent; // convert speed to delay + this.setDelay(dPercent); + break; + } + i++; + progress = (startProgress + (int) (i*(double) maxProgress/max)); + pl.progressChanged(this, progress); + } + } + + private void generatePseudoRasterGCode(RasterPart rp, double resolution, ProgressListener pl, int startProgress, int maxProgress) throws UnsupportedEncodingException, Exception { + int i = 0; + int progress; + int max = rp.getRasterHeight(); + + boolean dirRight = true; + Point rasterStart = rp.getRasterStart(); + + // called once per part to set chosen properties + PowerSpeedFocusProperty prop = (PowerSpeedFocusProperty) rp.getLaserProperty(); + this.setDelay(prop.getSpeed()); + this.setPower(prop.getPower()); + + for (int line = 0; line < rp.getRasterHeight(); line++) { + Point lineStart = rasterStart.clone(); + lineStart.y += line; + List<Byte> bytes = new LinkedList<Byte>(); + boolean lookForStart = true; + for (int x = 0; x < rp.getRasterWidth(); x++) { + if (lookForStart) { + if (rp.isBlack(x, line)) { + lookForStart = false; + bytes.add((byte) 255); + } else { + lineStart.x += 1; + } + } else { + bytes.add(rp.isBlack(x, line) ? (byte) 255 : (byte) 0); + } + } + //remove trailing zeroes + while (bytes.size() > 0 && bytes.get(bytes.size() - 1) == 0) { + bytes.remove(bytes.size() - 1); + } + if (bytes.size() > 0) { + if (dirRight) { + //add some space to the left + this.move(Math.max(0, (int) (lineStart.x - Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); + //move to the first nonempyt point of the line + this.move(lineStart.x, lineStart.y, resolution); + byte old = bytes.get(0); + for (int pix = 0; pix < bytes.size(); pix++) { + if (bytes.get(pix) != old) { + if (old == 0) { + this.move(lineStart.x + pix, lineStart.y, resolution); + } else { + this.setPower(prop.getPower() * (0xFF & old) / 255); + this.line(lineStart.x + pix - 1, lineStart.y, resolution); + this.move(lineStart.x + pix, lineStart.y, resolution); + } + old = bytes.get(pix); + } + } + //last point is also not "white" + this.setPower(prop.getPower() * (0xFF & bytes.get(bytes.size() - 1)) / 255); + this.line(lineStart.x + bytes.size() - 1, lineStart.y, resolution); + //add some space to the right + this.move(Math.min((int) Util.mm2px(bedWidth, resolution), (int) (lineStart.x + bytes.size() - 1 + Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); + } else { + //add some space to the right + this.move(Math.min((int) Util.mm2px(bedWidth, resolution), (int) (lineStart.x + bytes.size() - 1 + Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); + //move to the last nonempty point of the line + this.move(lineStart.x + bytes.size() - 1, lineStart.y, resolution); + byte old = bytes.get(bytes.size() - 1); + for (int pix = bytes.size() - 1; pix >= 0; pix--) { + if (bytes.get(pix) != old || pix == 0) { + if (old == 0) { + this.move(lineStart.x + pix, lineStart.y, resolution); + } else { + this.setPower(prop.getPower() * (0xFF & old) / 255); + this.line(lineStart.x + pix + 1, lineStart.y, resolution); + this.move(lineStart.x + pix, lineStart.y, resolution); + } + old = bytes.get(pix); + } + } + //last point is also not "white" + this.setPower(prop.getPower() * (0xFF & bytes.get(0)) / 255); + this.line(lineStart.x, lineStart.y, resolution); + //add some space to the left + this.move(Math.max(0, (int) (lineStart.x - Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); + } + } + dirRight = !dirRight; + + i = line + 1; + progress = (startProgress + (int) (i*(double) maxProgress/max)); + pl.progressChanged(this, progress); + } + } + + private void connect() throws NoSuchPortException, PortInUseException, Exception { + if(!this.debug){ + if (this.hostname.startsWith("port://")) { + String portString = this.hostname.replace("port://", ""); + + try{ + CommPortIdentifier cpi = CommPortIdentifier.getPortIdentifier(portString); + port = (SerialPort) cpi.open("VisiCut", 2000); + } + catch(Exception e) { + throw new Exception("Port '"+portString+"' is not available."); + } + + if (port == null) + { + throw new Exception("Error: Could not Open COM-Port '"+portString+"'"); + } + if (!(port instanceof SerialPort)) + { + throw new Exception("Port '"+portString+"' is not a serial port."); + } + port.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); + out = new BufferedOutputStream(port.getOutputStream()); + portReader = new BufferedReader(new InputStreamReader(port.getInputStream())); + + // wake up firmware + String command = "\r\n\r\n"; + out.write(command.getBytes("US-ASCII")); + out.flush(); + Thread.sleep(2000); + portReader.readLine(); // "ok" + portReader.readLine(); // "ok" + + this.checkVersion(); + } + else if (hostname.startsWith("file://")) { + String filename = this.hostname.replace("file://", ""); + try { + w = new PrintWriter(filename); + } + catch(Exception e) { + throw new Exception(String.format("No correct absolute file path: %s Exception %s", this.hostname, e)); + } + } + else { + throw new Exception(String.format("Unknown hostname: %s", this.hostname)); + } + } + } + + private void disconnect() throws Exception{ + if(w != null) { + w.close(); + w = null; + } + + if(out != null) { + out.close(); + out = null; + } + + if(port != null){ + port.close(); + port = null; + } + } + + private void checkResponse(String command, String response, String expectedAnswer) throws Exception { + if(!response.toLowerCase().contains(expectedAnswer.toLowerCase())) { + throw new Exception(String.format("Got wrong response to command \"%s\":\n\"%s\" instead of \"%s\"", command, response, expectedAnswer)); + } + } + private void sendCommand(String command) throws Exception { + this.send(command); + + if(!debug) { + if (this.hostname.startsWith("port://")) { + String resp = this.receive(); + this.checkResponse(command, resp, "ok"); + } + } + } + + private void checkVersion() throws Exception { + // check if firmware matches implemented protocol + this.send("M115"); + + if(!debug) { + if (this.hostname.startsWith("port://")) { + String resp = this.receive(); + this.checkResponse("Version", resp, "Version"); + String resp2 = this.receive(); + this.checkResponse("Version", resp2, "ok"); + } + } + } + + private void send(String command) throws Exception { + if(!debug) { + if (this.hostname.startsWith("port://")) { + // send + String sendString = command + "\n"; + out.write(sendString.getBytes("US-ASCII")); + out.flush(); + } + else if (hostname.startsWith("file://")) { + w.println(command); + } + else { + throw new Exception(String.format("Unknown hostname: %s", this.hostname)); + } + + } else { + System.out.println(command); + } + } + + private String receive() throws Exception{ + if(!debug) { + if (this.hostname.startsWith("port://")) { + String line; + try { + line = portReader.readLine(); + line = line.replace("\n", "").replace("\r", ""); + return line; + } catch(IOException e) { + throw new IOException("IO Exception, e.g. timeout"); + } + } + } + return ""; + } + + @Override + public void sendJob(LaserJob job, ProgressListener pl, List<String> warnings) throws IllegalJobException, Exception + { + this.chosenPower = 0; + this.chosenDelay = 0; + this.toolState = ToolState.ON; // assume worst case, set to OFF in initialization code + pl.progressChanged(this, 0); + pl.taskChanged(this, "checking job"); + checkJob(job); + job.applyStartPoint(); + pl.taskChanged(this, "connecting"); + this.connect(); + pl.taskChanged(this, "sending"); + this.generateInitializationGCode(); + int startProgress = 20; + pl.progressChanged(this, startProgress); + int i = 0; + int progress = startProgress; + int max = job.getParts().size(); + for (JobPart p : job.getParts()) + { + if (p instanceof Raster3dPart) + { + throw new Exception("Raster 3D parts are not implemented for " + this.getModelName()); + } + else if (p instanceof RasterPart) + { + this.generatePseudoRasterGCode((RasterPart) p, p.getDPI(), pl, progress, ((int) ((i+1)*(double) 80/max))); + } + else if (p instanceof VectorPart) + { + this.generateVectorGCode((VectorPart) p, p.getDPI(), pl, progress, ((int) ((i+1)*(double) 80/max))); + } + i++; + progress = (startProgress + (int) (i*(double) 80/max)); + pl.progressChanged(this, progress); + } + this.generateShutdownGCode(); + pl.taskChanged(this, "disconnecting"); + this.disconnect(); + pl.taskChanged(this, "sent"); + pl.progressChanged(this, 100); + } + + @Override + public LaserCutter clone() + { + MakeBlockXYPlotter clone = new MakeBlockXYPlotter(); + clone.addSpacePerRasterLine = addSpacePerRasterLine; + clone.hostname = hostname; + clone.bedWidth = bedWidth; + clone.bedHeight = bedHeight; + clone.delayRate = delayRate; + clone.powerRate = powerRate; + clone.usedTool = usedTool; + return clone; + } + + @Override + public String[] getPropertyKeys() { + return settingAttributes; + } + + @Override + public Object getProperty(String attribute) { + if (SETTING_HOSTNAME.equals(attribute)) { + return this.hostname; + } else if (SETTING_RASTER_WHITESPACE.equals(attribute)) { + return this.addSpacePerRasterLine; + } else if (SETTING_BEDWIDTH.equals(attribute)) { + return this.bedWidth; + } else if (SETTING_BEDHEIGHT.equals(attribute)) { + return this.bedHeight; + } else if (SETTING_DELAY_RATE.equals(attribute)) { + return this.delayRate; + } else if (SETTING_POWER_RATE.equals(attribute)) { + return this.powerRate; + } else if (SETTING_TOOL.equals(attribute)) { + return this.usedTool; + } + return null; + } + + @Override + public void setProperty(String attribute, Object value) { + if (SETTING_HOSTNAME.equals(attribute)) { + this.hostname = (String) value; + } else if (SETTING_RASTER_WHITESPACE.equals(attribute)) { + this.addSpacePerRasterLine = (Double) value; + } else if (SETTING_BEDWIDTH.equals(attribute)) { + this.bedWidth = (Double) value; + } else if (SETTING_BEDHEIGHT.equals(attribute)) { + this.bedHeight = (Double) value; + } else if (SETTING_DELAY_RATE.equals(attribute)) { + this.delayRate = (Integer) value; + } else if (SETTING_POWER_RATE.equals(attribute)) { + this.powerRate = (Integer) value; + } else if (SETTING_TOOL.equals(attribute)) { + this.usedTool = (String) value; + } + } + +} diff --git a/src/com/t_oster/liblasercut/drivers/MakeBlockXYPlotterProperty.java b/src/com/t_oster/liblasercut/drivers/MakeBlockXYPlotterProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..497313ead9be138746605acfd443b8f4ef1c81c2 --- /dev/null +++ b/src/com/t_oster/liblasercut/drivers/MakeBlockXYPlotterProperty.java @@ -0,0 +1,107 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + **/ + +/** + * Author: Sven Jung <sven.jung@rwth-aachen.de> + */ + +package com.t_oster.liblasercut.drivers; + +import com.t_oster.liblasercut.PowerSpeedFocusFrequencyProperty; +import java.util.Arrays; +import java.util.LinkedList; + +/** + * + * @author Sven + */ +public class MakeBlockXYPlotterProperty extends PowerSpeedFocusFrequencyProperty +{ + private boolean showPowerAndSpeed; + + public MakeBlockXYPlotterProperty(boolean showPowerAndSpeed) { + this.showPowerAndSpeed = showPowerAndSpeed; + } + + public MakeBlockXYPlotterProperty() { + this(false); + } + + @Override + public String[] getPropertyKeys() + { + LinkedList<String> result = new LinkedList<String>(); + result.addAll(Arrays.asList(super.getPropertyKeys())); + result.remove("focus"); + result.remove("frequency"); + + if(!showPowerAndSpeed) { + result.remove("power"); + result.remove("speed"); + } + + return result.toArray(new String[0]); + } + + @Override + public Object getProperty(String name) + { + return super.getProperty(name); + } + + @Override + public void setProperty(String name, Object value) + { + super.setProperty(name, value); + } + + @Override + public MakeBlockXYPlotterProperty clone() + { + MakeBlockXYPlotterProperty result = new MakeBlockXYPlotterProperty(); + for (String s:this.getPropertyKeys()) + { + result.setProperty(s, this.getProperty(s)); + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MakeBlockXYPlotterProperty other = (MakeBlockXYPlotterProperty) obj; + if (this.showPowerAndSpeed != other.showPowerAndSpeed) { + return false; + } + return super.equals(other); + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 59 * hash + (this.showPowerAndSpeed ? 1 : 0); + return hash; + } +}