Skip to content
Snippets Groups Projects
Commit 3c340ea3 authored by Thomas Oster's avatar Thomas Oster
Browse files

Merge pull request #34 from tur0kk/develop

MakeBlock XYPlotter driver
parents eb10a017 971d66d1
No related branches found
No related tags found
No related merge requests found
......@@ -46,6 +46,7 @@ public class LibInfo
IModelaMill.class,
SampleDriver.class,
ExportSVG.class,
MakeBlockXYPlotter.class,
GenericGcodeDriver.class,
SmoothieBoard.class,
Marlin.class
......
/**
* 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;
}
}
}
/**
* 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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment