Newer
Older
/**
* 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;
/**
*
*/
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 double bedWidth = 300;
private double bedHeight = 210;
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,
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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();
}
private void generateShutdownGCode() throws Exception {
//back to origin and shutdown
toolOff();
}
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!");
}
}
}
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.setPower(prop.getPower());
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
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()));
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();
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
}
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();
String resp2 = this.receive();
this.checkResponse("Version", resp2, "ok");
}
}
}
private void send(String command) throws Exception {
if(!debug) {
if (this.hostname.startsWith("port://")) {
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");
}
@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);
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
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.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;