Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LaosCutter.java 28.29 KiB
/**
 * This file is part of VisiCut.
 * Copyright (C) 2011 Thomas Oster <thomas.oster@rwth-aachen.de>
 * RWTH Aachen University - 52062 Aachen, Germany
 * 
 *     VisiCut 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.
 * 
 *    VisiCut 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 VisiCut.  If not, see <http://www.gnu.org/licenses/>.
 **/
package com.t_oster.liblasercut.drivers;

import com.t_oster.liblasercut.BlackWhiteRaster;
import com.t_oster.liblasercut.IllegalJobException;
import com.t_oster.liblasercut.LaserCutter;
import com.t_oster.liblasercut.LaserJob;
import com.t_oster.liblasercut.LaserProperty;
import com.t_oster.liblasercut.ProgressListener;
import com.t_oster.liblasercut.Raster3dPart;
import com.t_oster.liblasercut.RasterPart;
import com.t_oster.liblasercut.VectorCommand;
import com.t_oster.liblasercut.VectorPart;
import com.t_oster.liblasercut.platform.Point;
import com.t_oster.liblasercut.platform.Util;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.apache.commons.net.tftp.TFTP;
import org.apache.commons.net.tftp.TFTPClient;

/**
 * This class implements a driver for the LAOS Lasercutter board.
 * Currently it supports the simple code and the G-Code, which may be used in
 * the future.
 * 
 * @author Thomas Oster <thomas.oster@rwth-aachen.de>
 */
public class LaosCutter extends LaserCutter
{

  private static final String SETTING_HOSTNAME = "Hostname / IP";
  private static final String SETTING_PORT = "Port";
  private static final String SETTING_BEDWIDTH = "Laserbed width";
  private static final String SETTING_BEDHEIGHT = "Laserbed height";
  private static final String SETTING_FLIPX = "X axis goes right to left (yes/no)";
  private static final String SETTING_FLIPY = "Y axis goes bottom to top (yes/no)";
  private static final String SETTING_MMPERSTEP = "mm per Step (for SimpleMode)";
  private static final String SETTING_TFTP = "Use TFTP instead of TCP";
  private static final String SETTING_RASTER_WHITESPACE = "Additional space per Raster line";
  private static final String SETTING_NATIVERASTER = "Use native (LAOS) rastermode";
  private static final String SETTING_UNIDIR = "Engrave unidirectional";
  
  private boolean unidir = false;
  
  public void setEngraveUnidirectional(boolean uni)
  {
    this.unidir = uni;
  }
  
  public boolean isEngraveUnidirectional()
  {
    return this.unidir;
  }
  
  private boolean useLaosRastermode = true;

  /**
   * Get the value of useLaosRastermode
   *
   * @return the value of useLaosRastermode
   */
  public boolean isUseLaosRastermode()
  {
    return useLaosRastermode;
  }

  /**
   * Set the value of useLaosRastermode
   *
   * @param useLaosRastermode new value of useLaosRastermode
   */
  public void setUseLaosRastermode(boolean useLaosRastermode)
  {
    this.useLaosRastermode = useLaosRastermode;
  }

  
  private double addSpacePerRasterLine = 5;

  /**
   * Get the value of addSpacePerRasterLine
   *
   * @return the value of addSpacePerRasterLine
   */
  public double getAddSpacePerRasterLine()
  {
    return addSpacePerRasterLine;
  }

  /**
   * Set the value of addSpacePerRasterLine
   * This is a space (in mm) for the laserhead to gain
   * speed before the first 'black' pixel in every line
   *
   * @param addSpacePerRasterLine new value of addSpacePerRasterLine
   */
  public void setAddSpacePerRasterLine(double addSpacePerRasterLine)
  {
    this.addSpacePerRasterLine = addSpacePerRasterLine;
  }

  
  @Override
  public String getModelName()
  {
    return "LAOS";
  }
  protected boolean useTftp = true;

  /**
   * Get the value of useTftp
   *
   * @return the value of useTftp
   */
  public boolean isUseTftp()
  {
    return useTftp;
  }

  /**
   * Set the value of useTftp
   *
   * @param useTftp new value of useTftp
   */
  public void setUseTftp(boolean useTftp)
  {
    this.useTftp = useTftp;
  }
  protected boolean flipXaxis = false;

  /**
   * Get the value of flipXaxis
   *
   * @return the value of flipXaxis
   */
  public boolean isFlipXaxis()
  {
    return flipXaxis;
  }

  /**
   * Set the value of flipXaxis
   *
   * @param flipXaxis new value of flipXaxis
   */
  public void setFlipXaxis(boolean flipXaxis)
  {
    this.flipXaxis = flipXaxis;
  }
  
  protected boolean flipYaxis = false;

  /**
   * Get the value of flipYaxis
   *
   * @return the value of flipYaxis
   */
  public boolean isFlipYaxis()
  {
    return flipYaxis;
  }

  /**
   * Set the value of flipYaxis
   *
   * @param flipYaxis new value of flipYaxis
   */
  public void setFlipYaxis(boolean flipYaxis)
  {
    this.flipYaxis = flipYaxis;
  }
  
  protected String hostname = "192.168.123.111";

  /**
   * Get the value of hostname
   *
   * @return the value of hostname
   */
  public String getHostname()
  {
    return hostname;
  }

  /**
   * Set the value of hostname
   *
   * @param hostname new value of hostname
   */
  public void setHostname(String hostname)
  {
    this.hostname = hostname;
  }
  protected int port = 69;

  /**
   * Get the value of port
   *
   * @return the value of port
   */
  public int getPort()
  {
    return port;
  }

  /**
   * Set the value of port
   *
   * @param port new value of port
   */
  public void setPort(int port)
  {
    this.port = port;
  }
  protected double mmPerStep = 0.001;

  /**
   * Get the value of mmPerStep
   *
   * @return the value of mmPerStep
   */
  public double getMmPerStep()
  {
    return mmPerStep;
  }

  /**
   * Set the value of mmPerStep
   *
   * @param mmPerStep new value of mmPerStep
   */
  public void setMmPerStep(double mmPerStep)
  {
    this.mmPerStep = mmPerStep;
  }

  private int px2steps(double px, double dpi)
  {
    return (int) (Util.px2mm(px, dpi) / this.mmPerStep);
  }

  private byte[] generateVectorGCode(VectorPart vp, int resolution) throws UnsupportedEncodingException
  {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(result, true, "US-ASCII");
    int power = 100;
    int speed = 50;
    int frequency = 500;
    double focus = 0;
    //reset saved values, so the first ones get verbosed
    this.currentPower = -1;
    this.currentSpeed = -1;
    this.currentFrequency = -1;
    for (VectorCommand cmd : vp.getCommandList())
    {
      switch (cmd.getType())
      {
        case MOVETO:
          move(out, cmd.getX(), cmd.getY(), resolution);
          break;
        case LINETO:
          line(out, cmd.getX(), cmd.getY(), power, speed, frequency, resolution);
          break;
        case SETPOWER:
          power = cmd.getPower();
          break;
        case SETFOCUS:
          out.printf(Locale.US, "2 %d\n", (int) Util.mm2px(cmd.getFocus(), resolution));
          break;
        case SETSPEED:
          speed = cmd.getSpeed();
          break;
        case SETFREQUENCY:
          frequency = cmd.getFrequency();
          break;
      }
    }
    return result.toByteArray();
  }

  private void move(PrintStream out, int x, int y, int resolution)
  {
    out.printf("0 %d %d\n", px2steps(isFlipXaxis() ? Util.mm2px(bedWidth, resolution) - x : x, resolution), px2steps(isFlipYaxis() ? Util.mm2px(bedHeight, resolution) - y : y, resolution));
  }
  private int currentPower = -1;
  private int currentSpeed = -1;
  private int currentFrequency = -1;

  private void line(PrintStream out, int x, int y, int power, int speed, int frequency, int resolution)
  {
    if (currentPower != power)
    {
      out.printf("7 101 %d\n", power * 100);
      currentPower = power;
    }
    if (currentSpeed != speed)
    {
      out.printf("7 100 %d\n", speed * 100);
      currentSpeed = speed;
    }
    if (currentFrequency != frequency)
    {
      out.printf("7 102 %d\n", frequency);
      currentFrequency = frequency;
    }
    out.printf("1 %d %d\n", px2steps(isFlipXaxis() ? Util.mm2px(bedWidth, resolution) - x : x, resolution), px2steps(isFlipYaxis() ? Util.mm2px(bedHeight, resolution) - y : y, resolution));
  }

  private byte[] generatePseudoRaster3dGCode(Raster3dPart rp, int resolution) throws UnsupportedEncodingException
  {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(result, true, "US-ASCII");
    boolean dirRight = true;
    for (int raster = 0; raster < rp.getRasterCount(); raster++)
    {
      Point rasterStart = rp.getRasterStart(raster);
      LaserProperty prop = rp.getLaserProperty(raster);
      //Set focus
      out.printf(Locale.US, "2 %d\n", (int) Util.mm2px(prop.getFocus(), resolution));
      for (int line = 0; line < rp.getRasterHeight(raster); line++)
      {
        Point lineStart = rasterStart.clone();
        lineStart.y += line;
        List<Byte> bytes = rp.getRasterLine(raster, line);
        //remove heading zeroes
        while (bytes.size() > 0 && bytes.get(0) == 0)
        {
          bytes.remove(0);
          lineStart.x += 1;
        }
        //remove trailing zeroes
        while (bytes.size() > 0 && bytes.get(bytes.size() - 1) == 0)
        {
          bytes.remove(bytes.size() - 1);
        }
        if (bytes.size() > 0)
        {
          if (dirRight)
          {
            //move to the first nonempyt point of the line
            move(out, 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)
                {
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                else
                {
                  line(out, lineStart.x + pix - 1, lineStart.y, prop.getPower() * (0xFF & old) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                old = bytes.get(pix);
              }
            }
            //last point is also not "white"
            line(out, lineStart.x + bytes.size() - 1, lineStart.y, prop.getPower() * (0xFF & bytes.get(bytes.size() - 1)) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
          }
          else
          {
            //move to the last nonempty point of the line
            move(out, 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)
                {
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                else
                {
                  line(out, lineStart.x + pix + 1, lineStart.y, prop.getPower() * (0xFF & old) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                old = bytes.get(pix);
              }
            }
            //last point is also not "white"
            line(out, lineStart.x, lineStart.y, prop.getPower() * (0xFF & bytes.get(0)) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
          }
        }
        if (!this.isEngraveUnidirectional())
        {
          dirRight = !dirRight;
        }
      }
    }
    return result.toByteArray();
  }

  private byte[] generatePseudoRasterGCode(RasterPart rp, int resolution) throws UnsupportedEncodingException
  {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(result, true, "US-ASCII");
    boolean dirRight = true;
    for (int raster = 0; raster < rp.getRasterCount(); raster++)
    {
      Point rasterStart = rp.getRasterStart(raster);
      LaserProperty prop = rp.getLaserProperty(raster);
      //Set focus
      out.printf(Locale.US, "2 %d\n", (int) Util.mm2px(prop.getFocus(), resolution));
      for (int line = 0; line < rp.getRasterHeight(raster); line++)
      {
        Point lineStart = rasterStart.clone();
        lineStart.y += line;
        //Convert BlackWhite line into line of 0 and 255 bytes
        BlackWhiteRaster bwr = rp.getImages()[raster];
        List<Byte> bytes = new LinkedList<Byte>();
        boolean lookForStart = true;
        for (int x = 0; x < bwr.getWidth(); x++)
        {
          if (lookForStart)
          {
            if (bwr.isBlack(x, line))
            {
              lookForStart = false;
              bytes.add((byte) 255);
            }
            else
            {
              lineStart.x += 1;
            }
          }
          else
          {
            bytes.add(bwr.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
            move(out, Math.max(0, (int) (lineStart.x-Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution);
            //move to the first nonempyt point of the line
            move(out, 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)
                {
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                else
                {
                  line(out, lineStart.x + pix - 1, lineStart.y, prop.getPower() * (0xFF & old) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                old = bytes.get(pix);
              }
            }
            //last point is also not "white"
            line(out, lineStart.x + bytes.size() - 1, lineStart.y, prop.getPower() * (0xFF & bytes.get(bytes.size() - 1)) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
            //add some space to the right
            move(out, 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
            move(out, 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
            move(out, 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)
                {
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                else
                {
                  line(out, lineStart.x + pix + 1, lineStart.y, prop.getPower() * (0xFF & old) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
                  move(out, lineStart.x + pix, lineStart.y, resolution);
                }
                old = bytes.get(pix);
              }
            }
            //last point is also not "white"
            line(out, lineStart.x, lineStart.y, prop.getPower() * (0xFF & bytes.get(0)) / 255, prop.getSpeed(), prop.getFrequency(), resolution);
            //add some space to the left
            move(out, Math.max(0, (int) (lineStart.x-Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution);
          }
        }
        if (!this.isEngraveUnidirectional())
        {
          dirRight = !dirRight;
        }
      }
    }
    return result.toByteArray();
  }

  /**
   * This Method takes a raster-line represented by a list of bytes, 
   * where: byte0 ist the left-most byte, in one byte, the MSB is the
   * left-most bit, 0 representing laser off, 1 representing laser on.
   * The Output List of longs, where each value is the unsigned dword
   * of 4 bytes of the input each, where the first dword is the leftmost
   * dword and the LSB is the leftmost bit. If outputLeftToRight is false,
   * the first dword is the rightmost dword and the LSB of each dword is the
   * the Output is padded with zeroes on the right side, if leftToRight is true,
   * on the left-side otherwise
   * rightmost bit
   * @param line
   * @param outputLeftToRight
   * @return 
   */
  public List<Long> byteLineToDwords(List<Byte> line, boolean outputLeftToRight)
  {
    List<Long> result = new ArrayList<Long>();
    int s = line.size();
    for (int i=0;i<s;i++)
    {
      line.set(i, (byte) (Integer.reverse(0xFF&line.get(i))>>>24));
    }
    for(int i=0; i<s; i+=4)
    {
      result.add(
        (((long) (i+3 < s ? 0xFF&line.get(i+3) : 0))<<24) 
        + (((long) (i+2 < s ? 0xFF&line.get(i+2) : 0))<<16)
        + (((long) (i+1 < s ? 0xFF&line.get(i+1) : 0))<<8)
        + ((long) (0xFF&line.get(i)))
        );
    }
    if (!outputLeftToRight)
    {
      Collections.reverse(result);
      for(int i=0;i<result.size();i++)
      {   
        result.set(i, Long.reverse(result.get(i)) >>> 32);
      }
    }
    return result;
  }
  
  private byte[] generateLaosRasterCode(RasterPart rp, int resolution) throws UnsupportedEncodingException, IOException
  {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(result, true, "US-ASCII");
    boolean dirRight = true;
    for (int raster = 0; raster < rp.getRasterCount(); raster++)
    {
      Point rasterStart = rp.getRasterStart(raster);
      LaserProperty prop = rp.getLaserProperty(raster);
      //Set focus
      out.printf(Locale.US, "2 %d\n", (int) Util.mm2px(prop.getFocus(), resolution));
      for (int line = 0; line < rp.getRasterHeight(raster); line++)
      {
        Point lineStart = rasterStart.clone();
        lineStart.y += line;
        List<Byte> bytes = rp.getRasterLine(raster, line);
        //remove heading zeroes
        while (bytes.size() > 0 && bytes.get(0) == 0)
        {
          lineStart.x += 8;
          bytes.remove(0);
        }
        //remove trailing zeroes
        while (bytes.size() > 0 && bytes.get(bytes.size()-1) == 0)
        {
          bytes.remove(bytes.size()-1);
        }
        if (bytes.size() > 0)
        {
          //add space on the left side
          int space = (int) Util.mm2px(this.getAddSpacePerRasterLine(), resolution);
          while (space > 0 && lineStart.x >= 8)
          {
            bytes.add(0, (byte) 0);
            space -= 8;
            lineStart.x -=8;
          }
          //add space on the right side
          space = (int) Util.mm2px(this.getAddSpacePerRasterLine(), resolution);
          int max = (int) Util.mm2px(this.getBedWidth(), resolution);
          while (space > 0 && lineStart.x+(8*bytes.size()) < max-8)
          {
            bytes.add((byte) 0);
            space -= 8;
          }    
          if (dirRight)
          {
            //move to the first point of the line
            move(out, lineStart.x, lineStart.y, resolution);         
            List<Long> dwords = this.byteLineToDwords(bytes, true);
            out.printf("9 %s %s ", "1", ""+(dwords.size()*32));
            for(Long d:dwords)
            {
              out.print(" "+d);
            }
            out.printf("\n");
            line(out, lineStart.x + (dwords.size()*32), lineStart.y, prop.getPower(), prop.getSpeed(), prop.getFrequency(), resolution);
          }
          else
          {
            //move to the first point of the line
            List<Long> dwords = this.byteLineToDwords(bytes, false);
            move(out, lineStart.x+(dwords.size()*32), lineStart.y, resolution);         
            out.printf("9 %s %s ", "1", ""+(dwords.size()*32));
            for(Long d:dwords)
            {
              out.printf(" "+d);
            }
            out.printf("\n");
            line(out, lineStart.x, lineStart.y, prop.getPower(), prop.getSpeed(), prop.getFrequency(), resolution);  
          }
        }
        if (!this.isEngraveUnidirectional())
        {
          dirRight = !dirRight;
        }
      }
    }
    return result.toByteArray();
  }
  
  private byte[] generateInitializationCode() throws UnsupportedEncodingException
  {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(result, true, "US-ASCII");
    return result.toByteArray();
  }

  private byte[] generateShutdownCode() throws UnsupportedEncodingException
  {
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(result, true, "US-ASCII");
    //back to origin and shutdown
    //out.printf("0 0 0\n");
    //Set focus to 0
    out.printf(Locale.US, "2 %d\n", 0);
    return result.toByteArray();
  }

  @Override
  public void sendJob(LaserJob job, ProgressListener pl) throws IllegalJobException, Exception
  {
    pl.progressChanged(this, 0);
    this.currentFrequency = -1;
    this.currentPower = -1;
    this.currentSpeed = -1;
    BufferedOutputStream out;
    ByteArrayOutputStream buffer = null;
    pl.taskChanged(this, "checking job");
    checkJob(job);
    if (!useTftp)
    {
      pl.taskChanged(this, "connecting");
      Socket connection = new Socket();
      connection.connect(new InetSocketAddress(hostname, port), 3000);
      out = new BufferedOutputStream(connection.getOutputStream());
      pl.taskChanged(this, "sending");
    }
    else
    {
      buffer = new ByteArrayOutputStream();
      out = new BufferedOutputStream(buffer);
      pl.taskChanged(this, "buffering");
    }
    out.write(this.generateInitializationCode());
    pl.progressChanged(this, 20);
    if (job.contains3dRaster())
    {
      out.write(this.generatePseudoRaster3dGCode(job.getRaster3dPart(), job.getResolution()));
    }
    pl.progressChanged(this, 40);
    if (job.containsRaster())
    {
      if (this.isUseLaosRastermode())
      {
        out.write(this.generateLaosRasterCode(job.getRasterPart(), job.getResolution()));
      }
      else
      {
        out.write(this.generatePseudoRasterGCode(job.getRasterPart(), job.getResolution()));
      }
    }
    pl.progressChanged(this, 60);
    if (job.containsVector())
    {
      out.write(this.generateVectorGCode(job.getVectorPart(), job.getResolution()));
    }
    pl.progressChanged(this, 80);
    out.write(this.generateShutdownCode());
    out.close();
    if (this.isUseTftp())
    {
      pl.taskChanged(this, "connecting");
      TFTPClient tftp = new TFTPClient();
      tftp.setDefaultTimeout(5000);
      //open a local UDP socket
      tftp.open();
      pl.taskChanged(this, "sending");
      ByteArrayInputStream bain = new ByteArrayInputStream(buffer.toByteArray());
      tftp.sendFile(job.getName().replace(" ", "") +".lgc", TFTP.BINARY_MODE, bain, this.getHostname(), this.getPort());
      tftp.close();
      bain.close();
      pl.taskChanged(this, "sent.");
    }
    pl.progressChanged(this, 100);
  }
  private List<Integer> resolutions;

  @Override
  public List<Integer> getResolutions()
  {
    if (resolutions == null)
    {
      //TODO: Calculate possible resolutions
      //according to mm/step
      resolutions = Arrays.asList(new Integer[]
        {
          500
        });
    }
    return resolutions;
  }
  protected double bedWidth = 250;

  /**
   * Get the value of bedWidth
   *
   * @return the value of bedWidth
   */
  @Override
  public double getBedWidth()
  {
    return bedWidth;
  }

  /**
   * Set the value of bedWidth
   *
   * @param bedWidth new value of bedWidth
   */
  public void setBedWidth(double bedWidth)
  {
    this.bedWidth = bedWidth;
  }
  protected double bedHeight = 280;

  /**
   * Get the value of bedHeight
   *
   * @return the value of bedHeight
   */
  @Override
  public double getBedHeight()
  {
    return bedHeight;
  }

  /**
   * Set the value of bedHeight
   *
   * @param bedHeight new value of bedHeight
   */
  public void setBedHeight(double bedHeight)
  {
    this.bedHeight = bedHeight;
  }
  private List<String> settingAttributes;

  @Override
  public List<String> getSettingAttributes()
  {
    if (settingAttributes == null)
    {
      settingAttributes = new LinkedList<String>();
      settingAttributes.add(SETTING_HOSTNAME);
      settingAttributes.add(SETTING_PORT);
      settingAttributes.add(SETTING_NATIVERASTER);
      settingAttributes.add(SETTING_UNIDIR);
      settingAttributes.add(SETTING_BEDWIDTH);
      settingAttributes.add(SETTING_BEDHEIGHT);
      settingAttributes.add(SETTING_FLIPX);
      settingAttributes.add(SETTING_FLIPY);
      settingAttributes.add(SETTING_MMPERSTEP);
      settingAttributes.add(SETTING_TFTP);
      settingAttributes.add(SETTING_RASTER_WHITESPACE);
    }
    return settingAttributes;
  }

  @Override
  public String getSettingValue(String attribute)
  {
    if (SETTING_RASTER_WHITESPACE.equals(attribute))
    {
      return "" + this.getAddSpacePerRasterLine();
    }
    else if (SETTING_NATIVERASTER.equals(attribute))
    {
      return this.isUseLaosRastermode() ? "yes" : "no";
    }
    else if (SETTING_UNIDIR.equals(attribute))
    {
      return this.isEngraveUnidirectional() ? "yes" : "no";
    }
    else if (SETTING_HOSTNAME.equals(attribute))
    {
      return this.getHostname();
    }
    else if (SETTING_FLIPX.equals(attribute))
    {
      return this.isFlipXaxis() ? "yes" : "no";
    }
    else if (SETTING_FLIPY.equals(attribute))
    {
      return this.isFlipYaxis() ? "yes" : "no";
    }
    else if (SETTING_PORT.equals(attribute))
    {
      return "" + this.getPort();
    }
    else if (SETTING_BEDWIDTH.equals(attribute))
    {
      return "" + this.getBedWidth();
    }
    else if (SETTING_BEDHEIGHT.equals(attribute))
    {
      return "" + this.getBedHeight();
    }
    else if (SETTING_MMPERSTEP.equals(attribute))
    {
      return "" + this.getMmPerStep();
    }
    else if (SETTING_TFTP.equals(attribute))
    {
      return this.isUseTftp() ? "yes" : "no";
    }
    return null;
  }

  @Override
  public void setSettingValue(String attribute, String value)
  {
    if (SETTING_RASTER_WHITESPACE.equals(attribute))
    {
      this.setAddSpacePerRasterLine(Double.parseDouble(value));
    }
    else if (SETTING_NATIVERASTER.equals(attribute))
    {
      this.setUseLaosRastermode("yes".equals(value));
    }
    else if (SETTING_UNIDIR.endsWith(attribute))
    {
      this.setEngraveUnidirectional("yes".equals(value));
    }
    else if (SETTING_HOSTNAME.equals(attribute))
    {
      this.setHostname(value);
    }
    else if (SETTING_PORT.equals(attribute))
    {
      this.setPort(Integer.parseInt(value));
    }
    else if (SETTING_FLIPX.equals(attribute))
    {
      this.setFlipXaxis("yes".equals(value));
    }
    else if (SETTING_FLIPY.equals(attribute))
    {
      this.setFlipYaxis("yes".equals(value));
    }
    else if (SETTING_BEDWIDTH.equals(attribute))
    {
      this.setBedWidth(Double.parseDouble(value));
    }
    else if (SETTING_BEDHEIGHT.equals(attribute))
    {
      this.setBedHeight(Double.parseDouble(value));
    }
    else if (SETTING_MMPERSTEP.equals(attribute))
    {
      this.setMmPerStep(Double.parseDouble(value));
    }
    else if (SETTING_TFTP.contains(attribute))
    {
      this.setUseTftp("yes".equals(value));
    }
  }

  @Override
  public int estimateJobDuration(LaserJob job)
  {
    return 10000;
  }

  @Override
  public LaserCutter clone()
  {
    LaosCutter clone = new LaosCutter();
    clone.hostname = hostname;
    clone.port = port;
    clone.bedHeight = bedHeight;
    clone.bedWidth = bedWidth;
    clone.flipXaxis = flipXaxis;
    clone.flipXaxis = flipYaxis;
    clone.mmPerStep = mmPerStep;
    clone.useTftp = useTftp;
    clone.addSpacePerRasterLine = addSpacePerRasterLine;
    clone.unidir = unidir;
    clone.useLaosRastermode = useLaosRastermode;
    return clone;
  }
}